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* getSourceDefinition( 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;
161 /* called when this process is about to exit */
163 @brief Disconnect from the database.
165 This function is called when the server drone is about to terminate.
167 void osrfAppChildExit() {
168 osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
171 if (writehandle == dbhandle) same = 1;
173 dbi_conn_query(writehandle, "ROLLBACK;");
174 dbi_conn_close(writehandle);
177 if (dbhandle && !same)
178 dbi_conn_close(dbhandle);
180 // XXX add cleanup of readHandles whenever that gets used
186 @brief Initialize the application.
187 @return Zero if successful, or non-zero if not.
189 Load the IDL file into an internal data structure for future reference. Each non-virtual
190 class in the IDL corresponds to a table or view in the database. Ignore all virtual
191 tables and virtual fields.
193 Register a number of methods, some of them general-purpose and others specific for
196 The name of the application is given by the MODULENAME macro, whose value depends on
197 conditional compilation. The method names also incorporate MODULENAME, followed by a
198 dot, as a prefix. Some methods are registered or not registered depending on whether
199 the PCRUD macro is defined, and on whether the IDL includes permacrud entries for the
202 The general-purpose methods are as follows (minus their MODULENAME prefixes):
204 - json_query (not registered for PCRUD)
207 - transaction.rollback
212 For each non-virtual class, create up to eight class-specific methods:
214 - create (not for readonly classes)
216 - update (not for readonly classes)
217 - delete (not for readonly classes
218 - search (atomic and non-atomic versions)
219 - id_list (atomic and non-atomic versions)
221 For PCRUD, the full method names follow the pattern "MODULENAME.method_type.classname".
222 Otherwise they follow the pattern "MODULENAME.direct.XXX.method_type", where XXX is the
223 fieldmapper name from the IDL, with every run of one or more consecutive colons replaced
224 by a period. In addition, the names of atomic methods have a suffix of ".atomic".
226 This function is called when the registering the application, and is executed by the
227 listener before spawning the drones.
229 int osrfAppInitialize() {
231 osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
232 osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
234 if (!oilsIDLInit( osrf_settings_host_value("/IDL") ))
235 return 1; /* return non-zero to indicate error */
237 growing_buffer* method_name = buffer_init(64);
239 // Generic search thingy
240 buffer_add(method_name, MODULENAME);
241 buffer_add(method_name, ".json_query");
242 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
243 "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
246 // first we register all the transaction and savepoint methods
247 buffer_reset(method_name);
248 OSRF_BUFFER_ADD(method_name, MODULENAME);
249 OSRF_BUFFER_ADD(method_name, ".transaction.begin");
250 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
251 "beginTransaction", "", 0, 0 );
253 buffer_reset(method_name);
254 OSRF_BUFFER_ADD(method_name, MODULENAME);
255 OSRF_BUFFER_ADD(method_name, ".transaction.commit");
256 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
257 "commitTransaction", "", 0, 0 );
259 buffer_reset(method_name);
260 OSRF_BUFFER_ADD(method_name, MODULENAME);
261 OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
262 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
263 "rollbackTransaction", "", 0, 0 );
265 buffer_reset(method_name);
266 OSRF_BUFFER_ADD(method_name, MODULENAME);
267 OSRF_BUFFER_ADD(method_name, ".savepoint.set");
268 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
269 "setSavepoint", "", 1, 0 );
271 buffer_reset(method_name);
272 OSRF_BUFFER_ADD(method_name, MODULENAME);
273 OSRF_BUFFER_ADD(method_name, ".savepoint.release");
274 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
275 "releaseSavepoint", "", 1, 0 );
277 buffer_reset(method_name);
278 OSRF_BUFFER_ADD(method_name, MODULENAME);
279 OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
280 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
281 "rollbackSavepoint", "", 1, 0 );
283 static const char* global_method[] = {
291 const int global_method_count
292 = sizeof( global_method ) / sizeof ( global_method[0] );
294 unsigned long class_count = osrfHashGetCount( oilsIDL() );
295 osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
296 osrfLogDebug(OSRF_LOG_MARK,
297 "At most %lu methods will be generated",
298 (unsigned long) (class_count * global_method_count) );
300 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
301 osrfHash* idlClass = NULL;
303 // For each class in the IDL...
304 while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
306 const char* classname = osrfHashIteratorKey( class_itr );
307 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
309 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), MODULENAME )) {
310 osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on",
311 MODULENAME, classname);
315 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
316 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
320 // Look up some other attributes of the current class
321 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
322 if( !idlClass_fieldmapper ) {
323 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL",
329 // For PCRUD, ignore classes with no permacrud attribute
330 osrfHash* idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
331 if (!idlClass_permacrud) {
332 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no permacrud in IDL", classname );
336 const char* readonly = osrfHashGet(idlClass, "readonly");
339 for( i = 0; i < global_method_count; ++i ) { // for each global method
340 const char* method_type = global_method[ i ];
341 osrfLogDebug(OSRF_LOG_MARK,
342 "Using files to build %s class methods for %s", method_type, classname);
345 // Treat "id_list" or "search" as forms of "retrieve"
346 const char* tmp_method = method_type;
347 if ( *tmp_method == 'i' || *tmp_method == 's') { // "id_list" or "search"
348 tmp_method = "retrieve";
350 // Skip this method if there is no permacrud entry for it
351 if (!osrfHashGet( idlClass_permacrud, tmp_method ))
355 // No create, update, or delete methods for a readonly class
356 if ( str_is_true( readonly ) &&
357 ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd')
360 buffer_reset( method_name );
362 // Build the method name
364 // For PCRUD: MODULENAME.method_type.classname
365 buffer_fadd(method_name, "%s.%s.%s", MODULENAME, method_type, classname);
367 // For non-PCRUD: MODULENAME.MODULENAME.direct.XXX.method_type
368 // where XXX is the fieldmapper name from the IDL, with every run of
369 // one or more consecutive colons replaced by a period.
372 char* _fm = strdup( idlClass_fieldmapper );
373 part = strtok_r(_fm, ":", &st_tmp);
375 buffer_fadd(method_name, "%s.direct.%s", MODULENAME, part);
377 while ((part = strtok_r(NULL, ":", &st_tmp))) {
378 OSRF_BUFFER_ADD_CHAR(method_name, '.');
379 OSRF_BUFFER_ADD(method_name, part);
381 OSRF_BUFFER_ADD_CHAR(method_name, '.');
382 OSRF_BUFFER_ADD(method_name, method_type);
386 // For an id_list or search method we specify the OSRF_METHOD_STREAMING option.
387 // The consequence is that we implicitly create an atomic method in addition to
388 // the usual non-atomic method.
390 if (*method_type == 'i' || *method_type == 's') { // id_list or search
391 flags = flags | OSRF_METHOD_STREAMING;
394 osrfHash* method_meta = osrfNewHash();
395 osrfHashSet( method_meta, idlClass, "class");
396 osrfHashSet( method_meta, buffer_data( method_name ), "methodname" );
397 osrfHashSet( method_meta, strdup(method_type), "methodtype" );
399 // Register the method, with a pointer to an osrfHash to tell the method
400 // its name, type, and class.
401 osrfAppRegisterExtendedMethod(
403 OSRF_BUFFER_C_STR( method_name ),
404 "dispatchCRUDMethod",
411 } // end for each global method
412 } // end for each class in IDL
414 buffer_free( method_name );
415 osrfHashIteratorFree( class_itr );
420 static char* getSourceDefinition( osrfHash* class ) {
422 char* tabledef = osrfHashGet(class, "tablename");
425 tabledef = strdup(tabledef);
427 tabledef = osrfHashGet(class, "source_definition");
429 growing_buffer* tablebuf = buffer_init(128);
430 buffer_fadd( tablebuf, "(%s)", tabledef );
431 tabledef = buffer_release(tablebuf);
433 const char* classname = osrfHashGet( class, "classname" );
438 "%s ERROR No tablename or source_definition for class \"%s\"",
449 @brief Initialize a server drone.
450 @return Zero if successful, -1 if not.
452 Connect to the database. For each non-virtual class in the IDL, execute a dummy "SELECT * "
453 query to get the datatype of each column. Record the datatypes in the loaded IDL.
455 This function is called by a server drone shortly after it is spawned by the listener.
457 int osrfAppChildInit() {
459 osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
460 dbi_initialize(NULL);
461 osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
463 char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
464 char* user = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
465 char* host = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
466 char* port = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
467 char* db = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
468 char* pw = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
469 char* md = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion",
472 osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
473 writehandle = dbi_conn_new(driver);
476 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
479 osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
481 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
482 "port=%s, user=%s, pw=%s, db=%s", MODULENAME, host, port, user, pw, db );
484 if(host) dbi_conn_set_option(writehandle, "host", host );
485 if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
486 if(user) dbi_conn_set_option(writehandle, "username", user);
487 if(pw) dbi_conn_set_option(writehandle, "password", pw );
488 if(db) dbi_conn_set_option(writehandle, "dbname", db );
490 if(md) max_flesh_depth = atoi(md);
491 if(max_flesh_depth < 0) max_flesh_depth = 1;
492 if(max_flesh_depth > 1000) max_flesh_depth = 1000;
501 if (dbi_conn_connect(writehandle) < 0) {
503 if (dbi_conn_connect(writehandle) < 0) {
504 dbi_conn_error(writehandle, &err);
505 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
510 osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
512 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
513 osrfHash* class = NULL;
515 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
516 const char* classname = osrfHashIteratorKey( class_itr );
517 osrfHash* fields = osrfHashGet( class, "fields" );
519 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
520 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
524 char* tabledef = getSourceDefinition(class);
526 tabledef = strdup( "(null)" );
528 growing_buffer* sql_buf = buffer_init(32);
529 buffer_fadd( sql_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
533 char* sql = buffer_release(sql_buf);
534 osrfLogDebug(OSRF_LOG_MARK, "%s Investigatory SQL = %s", MODULENAME, sql);
536 dbi_result result = dbi_conn_query(writehandle, sql);
542 const char* columnName;
544 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
546 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
547 (char*) columnName );
549 /* fetch the fieldmapper index */
550 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
552 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", (char*)columnName);
554 /* determine the field type and storage attributes */
556 switch( dbi_result_get_field_type_idx(result, columnIndex) ) {
558 case DBI_TYPE_INTEGER : {
560 if ( !osrfHashGet(_f, "primitive") )
561 osrfHashSet(_f, "number", "primitive");
563 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
564 if( attr & DBI_INTEGER_SIZE8 )
565 osrfHashSet(_f, "INT8", "datatype");
567 osrfHashSet(_f, "INT", "datatype");
570 case DBI_TYPE_DECIMAL :
571 if ( !osrfHashGet(_f, "primitive") )
572 osrfHashSet(_f, "number", "primitive");
574 osrfHashSet(_f,"NUMERIC", "datatype");
577 case DBI_TYPE_STRING :
578 if ( !osrfHashGet(_f, "primitive") )
579 osrfHashSet(_f,"string", "primitive");
580 osrfHashSet(_f,"TEXT", "datatype");
583 case DBI_TYPE_DATETIME :
584 if ( !osrfHashGet(_f, "primitive") )
585 osrfHashSet(_f,"string", "primitive");
587 osrfHashSet(_f,"TIMESTAMP", "datatype");
590 case DBI_TYPE_BINARY :
591 if ( !osrfHashGet(_f, "primitive") )
592 osrfHashSet(_f,"string", "primitive");
594 osrfHashSet(_f,"BYTEA", "datatype");
599 "Setting [%s] to primitive [%s] and datatype [%s]...",
601 osrfHashGet(_f, "primitive"),
602 osrfHashGet(_f, "datatype")
606 } // end while loop for traversing result
607 dbi_result_free(result);
609 osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", (char*)classname);
611 } // end for each class in IDL
613 osrfHashIteratorFree( class_itr );
614 child_initialized = 1;
619 This function is a sleazy hack intended *only* for testing and
620 debugging. Any real server process should initialize the
621 database connection by calling osrfAppChildInit().
623 void set_cstore_dbi_conn( dbi_conn conn ) {
624 dbhandle = writehandle = conn;
627 void userDataFree( void* blob ) {
628 osrfHashFree( (osrfHash*)blob );
632 static void sessionDataFree( char* key, void* item ) {
633 if (!(strcmp(key,"xact_id"))) {
635 dbi_conn_query(writehandle, "ROLLBACK;");
642 int beginTransaction ( osrfMethodContext* ctx ) {
643 if(osrfMethodVerifyContext( ctx )) {
644 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
649 jsonObject* user = verifyUserPCRUD( ctx );
652 jsonObjectFree(user);
655 dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
657 osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
658 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error starting transaction" );
661 jsonObject* ret = jsonNewObject(ctx->session->session_id);
662 osrfAppRespondComplete( ctx, ret );
665 if (!ctx->session->userData) {
666 ctx->session->userData = osrfNewHash();
667 osrfHashSetCallback((osrfHash*)ctx->session->userData, &sessionDataFree);
670 osrfHashSet( (osrfHash*)ctx->session->userData, strdup( ctx->session->session_id ), "xact_id" );
671 ctx->session->userDataFree = &userDataFree;
677 int setSavepoint ( osrfMethodContext* ctx ) {
678 if(osrfMethodVerifyContext( ctx )) {
679 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
686 jsonObject* user = verifyUserPCRUD( ctx );
689 jsonObjectFree(user);
692 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
693 osrfAppSessionStatus(
695 OSRF_STATUS_INTERNALSERVERERROR,
696 "osrfMethodException",
698 "No active transaction -- required for savepoints"
703 const char* spName = jsonObjectGetString(jsonObjectGetIndex(ctx->params, spNamePos));
705 dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
709 "%s: Error creating savepoint %s in transaction %s",
712 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
714 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
715 "osrfMethodException", ctx->request, "Error creating savepoint" );
718 jsonObject* ret = jsonNewObject(spName);
719 osrfAppRespondComplete( ctx, ret );
725 int releaseSavepoint ( osrfMethodContext* ctx ) {
726 if(osrfMethodVerifyContext( ctx )) {
727 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
734 jsonObject* user = verifyUserPCRUD( ctx );
737 jsonObjectFree(user);
740 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
741 osrfAppSessionStatus(
743 OSRF_STATUS_INTERNALSERVERERROR,
744 "osrfMethodException",
746 "No active transaction -- required for savepoints"
751 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
753 dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
757 "%s: Error releasing savepoint %s in transaction %s",
760 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
762 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
763 "osrfMethodException", ctx->request, "Error releasing savepoint" );
766 jsonObject* ret = jsonNewObject(spName);
767 osrfAppRespondComplete( ctx, ret );
773 int rollbackSavepoint ( osrfMethodContext* ctx ) {
774 if(osrfMethodVerifyContext( ctx )) {
775 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
782 jsonObject* user = verifyUserPCRUD( ctx );
785 jsonObjectFree(user);
788 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
789 osrfAppSessionStatus(
791 OSRF_STATUS_INTERNALSERVERERROR,
792 "osrfMethodException",
794 "No active transaction -- required for savepoints"
799 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
801 dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
805 "%s: Error rolling back savepoint %s in transaction %s",
808 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
810 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
811 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
814 jsonObject* ret = jsonNewObject(spName);
815 osrfAppRespondComplete( ctx, ret );
821 int commitTransaction ( osrfMethodContext* ctx ) {
822 if(osrfMethodVerifyContext( ctx )) {
823 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
828 jsonObject* user = verifyUserPCRUD( ctx );
831 jsonObjectFree(user);
834 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
835 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
836 "osrfMethodException", ctx->request, "No active transaction to commit" );
840 dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
842 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
843 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
844 "osrfMethodException", ctx->request, "Error committing transaction" );
847 osrfHashRemove(ctx->session->userData, "xact_id");
848 jsonObject* ret = jsonNewObject(ctx->session->session_id);
849 osrfAppRespondComplete( ctx, ret );
855 int rollbackTransaction ( osrfMethodContext* ctx ) {
856 if(osrfMethodVerifyContext( ctx )) {
857 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
862 jsonObject* user = verifyUserPCRUD( ctx );
865 jsonObjectFree(user);
868 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
869 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
870 "osrfMethodException", ctx->request, "No active transaction to roll back" );
874 dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
876 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
877 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
878 "osrfMethodException", ctx->request, "Error rolling back transaction" );
881 osrfHashRemove(ctx->session->userData, "xact_id");
882 jsonObject* ret = jsonNewObject(ctx->session->session_id);
883 osrfAppRespondComplete( ctx, ret );
889 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
890 if(osrfMethodVerifyContext( ctx )) {
891 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
895 osrfHash* meta = (osrfHash*) ctx->method->userData;
896 osrfHash* class_obj = osrfHashGet( meta, "class" );
900 const char* methodtype = osrfHashGet(meta, "methodtype");
901 jsonObject * obj = NULL;
903 if (!strcmp(methodtype, "create")) {
904 obj = doCreate(ctx, &err);
905 osrfAppRespondComplete( ctx, obj );
907 else if (!strcmp(methodtype, "retrieve")) {
908 obj = doRetrieve(ctx, &err);
909 osrfAppRespondComplete( ctx, obj );
911 else if (!strcmp(methodtype, "update")) {
912 obj = doUpdate(ctx, &err);
913 osrfAppRespondComplete( ctx, obj );
915 else if (!strcmp(methodtype, "delete")) {
916 obj = doDelete(ctx, &err);
917 osrfAppRespondComplete( ctx, obj );
919 else if (!strcmp(methodtype, "search")) {
921 jsonObject* where_clause;
922 jsonObject* rest_of_query;
925 where_clause = jsonObjectGetIndex( ctx->params, 1 );
926 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
928 where_clause = jsonObjectGetIndex( ctx->params, 0 );
929 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
932 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
937 unsigned long res_idx = 0;
938 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
940 if(!verifyObjectPCRUD(ctx, cur)) continue;
942 osrfAppRespond( ctx, cur );
944 osrfAppRespondComplete( ctx, NULL );
946 } else if (!strcmp(methodtype, "id_list")) {
948 jsonObject* where_clause;
949 jsonObject* rest_of_query;
951 // We use the where clause without change. But we need
952 // to massage the rest of the query, so we work with a copy
953 // of it instead of modifying the original.
955 where_clause = jsonObjectGetIndex( ctx->params, 1 );
956 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
958 where_clause = jsonObjectGetIndex( ctx->params, 0 );
959 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
962 if ( rest_of_query ) {
963 jsonObjectRemoveKey( rest_of_query, "select" );
964 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
965 jsonObjectRemoveKey( rest_of_query, "flesh" );
966 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
968 rest_of_query = jsonNewObjectType( JSON_HASH );
971 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
973 // Build a SELECT list containing just the primary key,
974 // i.e. like { "classname":["keyname"] }
975 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
976 jsonObjectPush( col_list_obj, // Load array with name of primary key
977 jsonNewObject( osrfHashGet( class_obj, "primarykey" ) ) );
978 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
979 jsonObjectSetKey( select_clause, osrfHashGet( class_obj, "classname" ), col_list_obj );
981 jsonObjectSetKey( rest_of_query, "select", select_clause );
983 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
985 jsonObjectFree( rest_of_query );
989 unsigned long res_idx = 0;
990 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
992 if(!verifyObjectPCRUD(ctx, cur)) continue;
996 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
999 osrfAppRespondComplete( ctx, NULL );
1002 osrfAppRespondComplete( ctx, obj );
1005 jsonObjectFree(obj);
1010 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1013 osrfHash* meta = (osrfHash*) ctx->method->userData;
1014 osrfHash* class = osrfHashGet( meta, "class" );
1016 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1018 const char* temp_classname = param->classname;
1019 if( ! temp_classname )
1020 temp_classname = "(null)";
1022 growing_buffer* msg = buffer_init(128);
1025 "%s: %s method for type %s was passed a %s",
1027 osrfHashGet(meta, "methodtype"),
1028 osrfHashGet(class, "classname"),
1032 char* m = buffer_release(msg);
1033 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1042 ret = verifyObjectPCRUD( ctx, param );
1050 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1051 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1052 jsonObject* auth_object = jsonNewObject(auth);
1053 jsonObject* user = oilsUtilsQuickReq("open-ils.auth","open-ils.auth.session.retrieve",
1055 jsonObjectFree(auth_object);
1057 if (!user->classname || strcmp(user->classname, "au")) {
1059 growing_buffer* msg = buffer_init(128);
1062 "%s: permacrud received a bad auth token: %s",
1067 char* m = buffer_release(msg);
1068 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1072 jsonObjectFree(user);
1080 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1082 dbhandle = writehandle;
1084 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1085 osrfHash* class = osrfHashGet( method_metadata, "class" );
1086 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1089 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
1090 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1091 } else if ( *method_type == 'u' || *method_type == 'd' ) {
1092 fetch = 1; // MUST go to the db for the object for update and delete
1095 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
1098 // No permacrud for this method type on this class
1100 growing_buffer* msg = buffer_init(128);
1103 "%s: %s on class %s has no permacrud IDL entry",
1105 osrfHashGet(method_metadata, "methodtype"),
1106 osrfHashGet(class, "classname")
1109 char* m = buffer_release(msg);
1110 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN, "osrfMethodException", ctx->request, m );
1117 jsonObject* user = verifyUserPCRUD( ctx );
1121 int userid = atoi( oilsFMGetString( user, "id" ) );
1122 jsonObjectFree(user);
1124 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
1125 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
1126 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
1128 osrfStringArray* context_org_array = osrfNewStringArray(1);
1131 char* pkey_value = NULL;
1132 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1133 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions required, fetching top of the org tree" );
1135 // check for perm at top of org tree
1136 char* org_tree_root_id = org_tree_root( ctx );
1137 if( org_tree_root_id ) {
1138 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1139 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1141 osrfStringArrayFree( context_org_array );
1146 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, fetching context org ids" );
1147 const char* pkey = osrfHashGet(class, "primarykey");
1148 jsonObject *param = NULL;
1150 if (obj->classname) {
1151 pkey_value = oilsFMGetString( obj, pkey );
1152 if (!fetch) param = jsonObjectClone(obj);
1153 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s", pkey_value );
1155 pkey_value = jsonObjectToSimpleString( obj );
1157 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value of %s and retrieving from the database", pkey_value );
1161 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1162 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1163 jsonObjectFree(_tmp_params);
1165 param = jsonObjectExtractIndex(_list, 0);
1166 jsonObjectFree(_list);
1170 osrfLogDebug( OSRF_LOG_MARK, "Object not found in the database with primary key %s of %s", pkey, pkey_value );
1172 growing_buffer* msg = buffer_init(128);
1175 "%s: no object found with primary key %s of %s",
1181 char* m = buffer_release(msg);
1182 osrfAppSessionStatus(
1184 OSRF_STATUS_INTERNALSERVERERROR,
1185 "osrfMethodException",
1191 if (pkey_value) free(pkey_value);
1196 if (local_context->size > 0) {
1197 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified", local_context->size);
1199 const char* lcontext = NULL;
1200 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1201 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1204 "adding class-local field %s (value: %s) to the context org list",
1206 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1212 if (foreign_context) {
1213 unsigned long class_count = osrfHashGetCount( foreign_context );
1214 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count);
1216 if (class_count > 0) {
1218 osrfHash* fcontext = NULL;
1219 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1220 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1221 const char* class_name = osrfHashIteratorKey( class_itr );
1222 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1226 "%d foreign context fields(s) specified for class %s",
1227 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1231 char* foreign_pkey = osrfHashGet(fcontext, "field");
1232 char* foreign_pkey_value = oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1234 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1236 jsonObject* _list = doFieldmapperSearch(
1237 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1239 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1240 jsonObjectFree(_tmp_params);
1241 jsonObjectFree(_list);
1243 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1245 if (_fparam && jump_list) {
1246 const char* flink = NULL;
1248 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1249 free(foreign_pkey_value);
1251 osrfHash* foreign_link_hash = oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1253 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1254 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1256 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1258 _list = doFieldmapperSearch(
1260 osrfHashGet( oilsIDL(), osrfHashGet( foreign_link_hash, "class" ) ),
1266 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1267 jsonObjectFree(_tmp_params);
1268 jsonObjectFree(_list);
1274 growing_buffer* msg = buffer_init(128);
1277 "%s: no object found with primary key %s of %s",
1283 char* m = buffer_release(msg);
1284 osrfAppSessionStatus(
1286 OSRF_STATUS_INTERNALSERVERERROR,
1287 "osrfMethodException",
1293 osrfHashIteratorFree(class_itr);
1294 free(foreign_pkey_value);
1295 jsonObjectFree(param);
1300 free(foreign_pkey_value);
1303 const char* foreign_field = NULL;
1304 while ( (foreign_field = osrfStringArrayGetString(osrfHashGet(fcontext,"context"), j++)) ) {
1305 osrfStringArrayAdd( context_org_array, oilsFMGetString( _fparam, foreign_field ) );
1308 "adding foreign class %s field %s (value: %s) to the context org list",
1311 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1315 jsonObjectFree(_fparam);
1318 osrfHashIteratorFree( class_itr );
1322 jsonObjectFree(param);
1325 const char* context_org = NULL;
1326 const char* perm = NULL;
1329 if (permission->size == 0) {
1330 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1335 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1337 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1343 "Checking object permission [%s] for user %d on object %s (class %s) at org %d",
1347 osrfHashGet(class, "classname"),
1351 result = dbi_conn_queryf(
1353 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1356 osrfHashGet(class, "classname"),
1364 "Received a result for object permission [%s] for user %d on object %s (class %s) at org %d",
1368 osrfHashGet(class, "classname"),
1372 if (dbi_result_first_row(result)) {
1373 jsonObject* return_val = oilsMakeJSONFromResult( result );
1374 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1378 "Status of object permission [%s] for user %d on object %s (class %s) at org %d is %s",
1382 osrfHashGet(class, "classname"),
1387 if ( *has_perm == 't' ) OK = 1;
1388 jsonObjectFree(return_val);
1391 dbi_result_free(result);
1396 osrfLogDebug( OSRF_LOG_MARK, "Checking non-object permission [%s] for user %d at org %d", perm, userid, atoi(context_org) );
1397 result = dbi_conn_queryf(
1399 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1406 osrfLogDebug( OSRF_LOG_MARK, "Received a result for permission [%s] for user %d at org %d",
1407 perm, userid, atoi(context_org) );
1408 if ( dbi_result_first_row(result) ) {
1409 jsonObject* return_val = oilsMakeJSONFromResult( result );
1410 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1411 osrfLogDebug( OSRF_LOG_MARK, "Status of permission [%s] for user %d at org %d is [%s]",
1412 perm, userid, atoi(context_org), has_perm );
1413 if ( *has_perm == 't' ) OK = 1;
1414 jsonObjectFree(return_val);
1417 dbi_result_free(result);
1425 if (pkey_value) free(pkey_value);
1426 osrfStringArrayFree(context_org_array);
1432 * Look up the root of the org_unit tree. If you find it, return
1433 * a string containing the id, which the caller is responsible for freeing.
1434 * Otherwise return NULL.
1436 static char* org_tree_root( osrfMethodContext* ctx ) {
1438 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1439 static time_t last_lookup_time = 0;
1440 time_t current_time = time( NULL );
1442 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1443 // We successfully looked this up less than an hour ago.
1444 // It's not likely to have changed since then.
1445 return strdup( cached_root_id );
1447 last_lookup_time = current_time;
1450 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1451 jsonObject* result = doFieldmapperSearch(
1452 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1453 jsonObjectFree( where_clause );
1455 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1458 jsonObjectFree( result );
1460 growing_buffer* msg = buffer_init(128);
1461 OSRF_BUFFER_ADD( msg, MODULENAME );
1462 OSRF_BUFFER_ADD( msg,
1463 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1465 char* m = buffer_release(msg);
1466 osrfAppSessionStatus( ctx->session,
1467 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1470 cached_root_id[ 0 ] = '\0';
1474 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1475 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1477 jsonObjectFree( result );
1479 strcpy( cached_root_id, root_org_unit_id );
1480 return root_org_unit_id;
1484 Utility function: create a JSON_HASH with a single key/value pair.
1485 This function is equivalent to:
1487 jsonParseFmt( "{\"%s\":\"%s\"}", key, value )
1489 or, if value is NULL:
1491 jsonParseFmt( "{\"%s\":null}", key )
1493 ...but faster because it doesn't create and parse a JSON string.
1495 static jsonObject* single_hash( const char* key, const char* value ) {
1497 if( ! key ) key = "";
1499 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1500 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1506 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1508 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1510 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1511 jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1513 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1514 jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1517 if (!verifyObjectClass(ctx, target)) {
1522 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1524 char* trans_id = NULL;
1525 if( ctx->session && ctx->session->userData )
1526 trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
1529 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1531 osrfAppSessionStatus(
1533 OSRF_STATUS_BADREQUEST,
1534 "osrfMethodException",
1536 "No active transaction -- required for CREATE"
1542 // The following test is harmless but redundant. If a class is
1543 // readonly, we don't register a create method for it.
1544 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1545 osrfAppSessionStatus(
1547 OSRF_STATUS_BADREQUEST,
1548 "osrfMethodException",
1550 "Cannot INSERT readonly class"
1556 // Set the last_xact_id
1557 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1559 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
1560 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1563 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1565 dbhandle = writehandle;
1567 osrfHash* fields = osrfHashGet(meta, "fields");
1568 char* pkey = osrfHashGet(meta, "primarykey");
1569 char* seq = osrfHashGet(meta, "sequence");
1571 growing_buffer* table_buf = buffer_init(128);
1572 growing_buffer* col_buf = buffer_init(128);
1573 growing_buffer* val_buf = buffer_init(128);
1575 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1576 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1577 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1578 buffer_add(val_buf,"VALUES (");
1582 osrfHash* field = NULL;
1583 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1584 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1586 const char* field_name = osrfHashIteratorKey( field_itr );
1588 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1591 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1594 if (field_object && field_object->classname) {
1595 value = oilsFMGetString(
1597 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1599 } else if( field_object && JSON_BOOL == field_object->type ) {
1600 if( jsonBoolIsTrue( field_object ) )
1601 value = strdup( "t" );
1603 value = strdup( "f" );
1605 value = jsonObjectToSimpleString( field_object );
1611 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1612 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1615 buffer_add(col_buf, field_name);
1617 if (!field_object || field_object->type == JSON_NULL) {
1618 buffer_add( val_buf, "DEFAULT" );
1620 } else if ( !strcmp(get_primitive( field ), "number") ) {
1621 const char* numtype = get_datatype( field );
1622 if ( !strcmp( numtype, "INT8") ) {
1623 buffer_fadd( val_buf, "%lld", atoll(value) );
1625 } else if ( !strcmp( numtype, "INT") ) {
1626 buffer_fadd( val_buf, "%d", atoi(value) );
1628 } else if ( !strcmp( numtype, "NUMERIC") ) {
1629 buffer_fadd( val_buf, "%f", atof(value) );
1632 if ( dbi_conn_quote_string(writehandle, &value) ) {
1633 OSRF_BUFFER_ADD( val_buf, value );
1636 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1637 osrfAppSessionStatus(
1639 OSRF_STATUS_INTERNALSERVERERROR,
1640 "osrfMethodException",
1642 "Error quoting string -- please see the error log for more details"
1645 buffer_free(table_buf);
1646 buffer_free(col_buf);
1647 buffer_free(val_buf);
1657 osrfHashIteratorFree( field_itr );
1659 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1660 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1662 char* table_str = buffer_release(table_buf);
1663 char* col_str = buffer_release(col_buf);
1664 char* val_str = buffer_release(val_buf);
1665 growing_buffer* sql = buffer_init(128);
1666 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1671 char* query = buffer_release(sql);
1673 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1676 dbi_result result = dbi_conn_query(writehandle, query);
1678 jsonObject* obj = NULL;
1681 obj = jsonNewObject(NULL);
1684 "%s ERROR inserting %s object using query [%s]",
1686 osrfHashGet(meta, "fieldmapper"),
1689 osrfAppSessionStatus(
1691 OSRF_STATUS_INTERNALSERVERERROR,
1692 "osrfMethodException",
1694 "INSERT error -- please see the error log for more details"
1699 char* id = oilsFMGetString(target, pkey);
1701 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1702 growing_buffer* _id = buffer_init(10);
1703 buffer_fadd(_id, "%lld", new_id);
1704 id = buffer_release(_id);
1707 // Find quietness specification, if present
1708 const char* quiet_str = NULL;
1710 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1712 quiet_str = jsonObjectGetString( quiet_obj );
1715 if( str_is_true( quiet_str ) ) { // if quietness is specified
1716 obj = jsonNewObject(id);
1720 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1721 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1723 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1725 jsonObjectFree( where_clause );
1730 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1733 jsonObjectFree( list );
1746 * Fetch one row from a specified table, using a specified value
1747 * for the primary key
1749 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1759 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1761 const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos); // key value
1765 "%s retrieving %s object with primary key value of %s",
1767 osrfHashGet( class_def, "fieldmapper" ),
1768 jsonObjectGetString( id_obj )
1771 // Build a WHERE clause based on the key value
1772 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1775 osrfHashGet( class_def, "primarykey" ),
1776 jsonObjectClone( id_obj )
1779 jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
1781 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
1783 jsonObjectFree( where_clause );
1787 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
1788 jsonObjectFree( list );
1791 if(!verifyObjectPCRUD(ctx, obj)) {
1792 jsonObjectFree(obj);
1795 growing_buffer* msg = buffer_init(128);
1796 OSRF_BUFFER_ADD( msg, MODULENAME );
1797 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1799 char* m = buffer_release(msg);
1800 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1811 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1812 growing_buffer* val_buf = buffer_init(32);
1813 const char* numtype = get_datatype( field );
1815 if ( !strncmp( numtype, "INT", 3 ) ) {
1816 if (value->type == JSON_NUMBER)
1817 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1818 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1820 //const char* val_str = jsonObjectGetString( value );
1821 //buffer_fadd( val_buf, "%ld", atol(val_str) );
1822 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1825 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
1826 if (value->type == JSON_NUMBER)
1827 //buffer_fadd( val_buf, "%f", jsonObjectGetNumber(value) );
1828 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1830 //const char* val_str = jsonObjectGetString( value );
1831 //buffer_fadd( val_buf, "%f", atof(val_str) );
1832 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1836 // Presumably this was really intended ot be a string, so quote it
1837 char* str = jsonObjectToSimpleString( value );
1838 if ( dbi_conn_quote_string(dbhandle, &str) ) {
1839 OSRF_BUFFER_ADD( val_buf, str );
1842 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
1844 buffer_free(val_buf);
1849 return buffer_release(val_buf);
1852 static char* searchINPredicate (const char* class_alias, osrfHash* field,
1853 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1854 growing_buffer* sql_buf = buffer_init(32);
1860 osrfHashGet(field, "name")
1864 buffer_add(sql_buf, "IN (");
1865 } else if (!(strcasecmp(op,"not in"))) {
1866 buffer_add(sql_buf, "NOT IN (");
1868 buffer_add(sql_buf, "IN (");
1871 if (node->type == JSON_HASH) {
1872 // subquery predicate
1873 char* subpred = buildQuery( ctx, node, SUBSELECT );
1875 buffer_free( sql_buf );
1879 buffer_add(sql_buf, subpred);
1882 } else if (node->type == JSON_ARRAY) {
1883 // literal value list
1884 int in_item_index = 0;
1885 int in_item_first = 1;
1886 const jsonObject* in_item;
1887 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1892 buffer_add(sql_buf, ", ");
1895 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1896 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1897 MODULENAME, json_type( in_item->type ) );
1898 buffer_free(sql_buf);
1902 // Append the literal value -- quoted if not a number
1903 if ( JSON_NUMBER == in_item->type ) {
1904 char* val = jsonNumberToDBString( field, in_item );
1905 OSRF_BUFFER_ADD( sql_buf, val );
1908 } else if ( !strcmp( get_primitive( field ), "number") ) {
1909 char* val = jsonNumberToDBString( field, in_item );
1910 OSRF_BUFFER_ADD( sql_buf, val );
1914 char* key_string = jsonObjectToSimpleString(in_item);
1915 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1916 OSRF_BUFFER_ADD( sql_buf, key_string );
1919 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1921 buffer_free(sql_buf);
1927 if( in_item_first ) {
1928 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1929 buffer_free( sql_buf );
1933 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1934 MODULENAME, json_type( node->type ) );
1935 buffer_free(sql_buf);
1939 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1941 return buffer_release(sql_buf);
1944 // Receive a JSON_ARRAY representing a function call. The first
1945 // entry in the array is the function name. The rest are parameters.
1946 static char* searchValueTransform( const jsonObject* array ) {
1948 if( array->size < 1 ) {
1949 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
1953 // Get the function name
1954 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
1955 if( func_item->type != JSON_STRING ) {
1956 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
1957 MODULENAME, json_type( func_item->type ) );
1961 growing_buffer* sql_buf = buffer_init(32);
1963 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1964 OSRF_BUFFER_ADD( sql_buf, "( " );
1966 // Get the parameters
1967 int func_item_index = 1; // We already grabbed the zeroth entry
1968 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1970 // Add a separator comma, if we need one
1971 if( func_item_index > 2 )
1972 buffer_add( sql_buf, ", " );
1974 // Add the current parameter
1975 if (func_item->type == JSON_NULL) {
1976 buffer_add( sql_buf, "NULL" );
1978 char* val = jsonObjectToSimpleString(func_item);
1979 if ( dbi_conn_quote_string(dbhandle, &val) ) {
1980 OSRF_BUFFER_ADD( sql_buf, val );
1983 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1984 buffer_free(sql_buf);
1991 buffer_add( sql_buf, " )" );
1993 return buffer_release(sql_buf);
1996 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
1997 const jsonObject* node, const char* op) {
1999 if( ! is_good_operator( op ) ) {
2000 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2004 char* val = searchValueTransform(node);
2008 growing_buffer* sql_buf = buffer_init(32);
2013 osrfHashGet(field, "name"),
2020 return buffer_release(sql_buf);
2023 // class_alias is a class name or other table alias
2024 // field is a field definition as stored in the IDL
2025 // node comes from the method parameter, and may represent an entry in the SELECT list
2026 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
2027 growing_buffer* sql_buf = buffer_init(32);
2029 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
2030 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
2032 if(transform_subcolumn) {
2033 if( ! is_identifier( transform_subcolumn ) ) {
2034 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2035 MODULENAME, transform_subcolumn );
2036 buffer_free( sql_buf );
2039 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2042 if (field_transform) {
2044 if( ! is_identifier( field_transform ) ) {
2045 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2046 MODULENAME, field_transform );
2047 buffer_free( sql_buf );
2051 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class_alias, osrfHashGet(field, "name"));
2052 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2055 if( array->type != JSON_ARRAY ) {
2056 osrfLogError( OSRF_LOG_MARK,
2057 "%s: Expected JSON_ARRAY for function params; found %s",
2058 MODULENAME, json_type( array->type ) );
2059 buffer_free( sql_buf );
2062 int func_item_index = 0;
2063 jsonObject* func_item;
2064 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2066 char* val = jsonObjectToSimpleString(func_item);
2069 buffer_add( sql_buf, ",NULL" );
2070 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
2071 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2072 OSRF_BUFFER_ADD( sql_buf, val );
2074 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
2076 buffer_free(sql_buf);
2083 buffer_add( sql_buf, " )" );
2086 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
2089 if (transform_subcolumn)
2090 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2092 return buffer_release(sql_buf);
2095 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2096 const jsonObject* node, const char* op ) {
2098 if( ! is_good_operator( op ) ) {
2099 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2103 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2104 if( ! field_transform )
2107 int extra_parens = 0; // boolean
2109 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2110 if ( ! value_obj ) {
2111 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2113 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
2114 free(field_transform);
2118 } else if ( value_obj->type == JSON_ARRAY ) {
2119 value = searchValueTransform( value_obj );
2121 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
2122 free( field_transform );
2125 } else if ( value_obj->type == JSON_HASH ) {
2126 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2128 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
2129 free(field_transform);
2133 } else if ( value_obj->type == JSON_NUMBER ) {
2134 value = jsonNumberToDBString( field, value_obj );
2135 } else if ( value_obj->type == JSON_NULL ) {
2136 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
2137 free(field_transform);
2139 } else if ( value_obj->type == JSON_BOOL ) {
2140 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
2141 free(field_transform);
2144 if ( !strcmp( get_primitive( field ), "number") ) {
2145 value = jsonNumberToDBString( field, value_obj );
2147 value = jsonObjectToSimpleString( value_obj );
2148 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2149 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
2151 free(field_transform);
2157 const char* left_parens = "";
2158 const char* right_parens = "";
2160 if( extra_parens ) {
2165 growing_buffer* sql_buf = buffer_init(32);
2169 "%s%s %s %s %s %s%s",
2180 free(field_transform);
2182 return buffer_release(sql_buf);
2185 static char* searchSimplePredicate (const char* op, const char* class_alias,
2186 osrfHash* field, const jsonObject* node) {
2188 if( ! is_good_operator( op ) ) {
2189 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2195 // Get the value to which we are comparing the specified column
2196 if (node->type != JSON_NULL) {
2197 if ( node->type == JSON_NUMBER ) {
2198 val = jsonNumberToDBString( field, node );
2199 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2200 val = jsonNumberToDBString( field, node );
2202 val = jsonObjectToSimpleString(node);
2207 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2208 // Value is not numeric; enclose it in quotes
2209 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2210 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2216 // Compare to a null value
2217 val = strdup( "NULL" );
2218 if (strcmp( op, "=" ))
2224 growing_buffer* sql_buf = buffer_init(32);
2225 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2226 char* pred = buffer_release( sql_buf );
2233 static char* searchBETWEENPredicate (const char* class_alias,
2234 osrfHash* field, const jsonObject* node) {
2236 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2237 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2239 if( NULL == y_node ) {
2240 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2243 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2244 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2251 if ( !strcmp( get_primitive( field ), "number") ) {
2252 x_string = jsonNumberToDBString(field, x_node);
2253 y_string = jsonNumberToDBString(field, y_node);
2256 x_string = jsonObjectToSimpleString(x_node);
2257 y_string = jsonObjectToSimpleString(y_node);
2258 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2259 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2260 MODULENAME, x_string, y_string);
2267 growing_buffer* sql_buf = buffer_init(32);
2268 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2269 class_alias, osrfHashGet(field, "name"), x_string, y_string );
2273 return buffer_release(sql_buf);
2276 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2277 jsonObject* node, osrfMethodContext* ctx ) {
2280 if (node->type == JSON_ARRAY) { // equality IN search
2281 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2282 } else if (node->type == JSON_HASH) { // other search
2283 jsonIterator* pred_itr = jsonNewIterator( node );
2284 if( !jsonIteratorHasNext( pred_itr ) ) {
2285 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2286 MODULENAME, osrfHashGet(field, "name") );
2288 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2290 // Verify that there are no additional predicates
2291 if( jsonIteratorHasNext( pred_itr ) ) {
2292 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2293 MODULENAME, osrfHashGet(field, "name") );
2294 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2295 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2296 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2297 pred = searchINPredicate( class_info->alias, field, pred_node, pred_itr->key, ctx );
2298 else if ( pred_node->type == JSON_ARRAY )
2299 pred = searchFunctionPredicate( class_info->alias, field, pred_node, pred_itr->key );
2300 else if ( pred_node->type == JSON_HASH )
2301 pred = searchFieldTransformPredicate( class_info, field, pred_node, pred_itr->key );
2303 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2305 jsonIteratorFree(pred_itr);
2307 } else if (node->type == JSON_NULL) { // IS NULL search
2308 growing_buffer* _p = buffer_init(64);
2311 "\"%s\".%s IS NULL",
2312 class_info->class_name,
2313 osrfHashGet(field, "name")
2315 pred = buffer_release(_p);
2316 } else { // equality search
2317 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2336 field : call_number,
2352 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2354 const jsonObject* working_hash;
2355 jsonObject* freeable_hash = NULL;
2357 if (join_hash->type == JSON_HASH) {
2358 working_hash = join_hash;
2359 } else if (join_hash->type == JSON_STRING) {
2360 // turn it into a JSON_HASH by creating a wrapper
2361 // around a copy of the original
2362 const char* _tmp = jsonObjectGetString( join_hash );
2363 freeable_hash = jsonNewObjectType(JSON_HASH);
2364 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2365 working_hash = freeable_hash;
2369 "%s: JOIN failed; expected JSON object type not found",
2375 growing_buffer* join_buf = buffer_init(128);
2376 const char* leftclass = left_info->class_name;
2378 jsonObject* snode = NULL;
2379 jsonIterator* search_itr = jsonNewIterator( working_hash );
2381 while ( (snode = jsonIteratorNext( search_itr )) ) {
2382 const char* right_alias = search_itr->key;
2384 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2386 class = right_alias;
2388 const ClassInfo* right_info = add_joined_class( right_alias, class );
2392 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2396 jsonIteratorFree( search_itr );
2397 buffer_free( join_buf );
2399 jsonObjectFree( freeable_hash );
2402 osrfHash* links = right_info->links;
2403 const char* table = right_info->source_def;
2405 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2406 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2408 if (field && !fkey) {
2409 // Look up the corresponding join column in the IDL.
2410 // The link must be defined in the child table,
2411 // and point to the right parent table.
2412 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2413 const char* reltype = NULL;
2414 const char* other_class = NULL;
2415 reltype = osrfHashGet( idl_link, "reltype" );
2416 if( reltype && strcmp( reltype, "has_many" ) )
2417 other_class = osrfHashGet( idl_link, "class" );
2418 if( other_class && !strcmp( other_class, leftclass ) )
2419 fkey = osrfHashGet( idl_link, "key" );
2423 "%s: JOIN failed. No link defined from %s.%s to %s",
2429 buffer_free(join_buf);
2431 jsonObjectFree(freeable_hash);
2432 jsonIteratorFree(search_itr);
2436 } else if (!field && fkey) {
2437 // Look up the corresponding join column in the IDL.
2438 // The link must be defined in the child table,
2439 // and point to the right parent table.
2440 osrfHash* left_links = left_info->links;
2441 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2442 const char* reltype = NULL;
2443 const char* other_class = NULL;
2444 reltype = osrfHashGet( idl_link, "reltype" );
2445 if( reltype && strcmp( reltype, "has_many" ) )
2446 other_class = osrfHashGet( idl_link, "class" );
2447 if( other_class && !strcmp( other_class, class ) )
2448 field = osrfHashGet( idl_link, "key" );
2452 "%s: JOIN failed. No link defined from %s.%s to %s",
2458 buffer_free(join_buf);
2460 jsonObjectFree(freeable_hash);
2461 jsonIteratorFree(search_itr);
2465 } else if (!field && !fkey) {
2466 osrfHash* left_links = left_info->links;
2468 // For each link defined for the left class:
2469 // see if the link references the joined class
2470 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2471 osrfHash* curr_link = NULL;
2472 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2473 const char* other_class = osrfHashGet( curr_link, "class" );
2474 if( other_class && !strcmp( other_class, class ) ) {
2476 // In the IDL, the parent class doesn't know then names of the child
2477 // columns that are pointing to it, so don't use that end of the link
2478 const char* reltype = osrfHashGet( curr_link, "reltype" );
2479 if( reltype && strcmp( reltype, "has_many" ) ) {
2480 // Found a link between the classes
2481 fkey = osrfHashIteratorKey( itr );
2482 field = osrfHashGet( curr_link, "key" );
2487 osrfHashIteratorFree( itr );
2489 if (!field || !fkey) {
2490 // Do another such search, with the classes reversed
2492 // For each link defined for the joined class:
2493 // see if the link references the left class
2494 osrfHashIterator* itr = osrfNewHashIterator( links );
2495 osrfHash* curr_link = NULL;
2496 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2497 const char* other_class = osrfHashGet( curr_link, "class" );
2498 if( other_class && !strcmp( other_class, leftclass ) ) {
2500 // In the IDL, the parent class doesn't know then names of the child
2501 // columns that are pointing to it, so don't use that end of the link
2502 const char* reltype = osrfHashGet( curr_link, "reltype" );
2503 if( reltype && strcmp( reltype, "has_many" ) ) {
2504 // Found a link between the classes
2505 field = osrfHashIteratorKey( itr );
2506 fkey = osrfHashGet( curr_link, "key" );
2511 osrfHashIteratorFree( itr );
2514 if (!field || !fkey) {
2517 "%s: JOIN failed. No link defined between %s and %s",
2522 buffer_free(join_buf);
2524 jsonObjectFree(freeable_hash);
2525 jsonIteratorFree(search_itr);
2531 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2533 if ( !strcasecmp(type,"left") ) {
2534 buffer_add(join_buf, " LEFT JOIN");
2535 } else if ( !strcasecmp(type,"right") ) {
2536 buffer_add(join_buf, " RIGHT JOIN");
2537 } else if ( !strcasecmp(type,"full") ) {
2538 buffer_add(join_buf, " FULL JOIN");
2540 buffer_add(join_buf, " INNER JOIN");
2543 buffer_add(join_buf, " INNER JOIN");
2546 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2547 table, right_alias, right_alias, field, left_info->alias, fkey);
2549 // Add any other join conditions as specified by "filter"
2550 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2552 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2553 if ( filter_op && !strcasecmp("or",filter_op) ) {
2554 buffer_add( join_buf, " OR " );
2556 buffer_add( join_buf, " AND " );
2559 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2561 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2562 OSRF_BUFFER_ADD( join_buf, jpred );
2567 "%s: JOIN failed. Invalid conditional expression.",
2570 jsonIteratorFree( search_itr );
2571 buffer_free( join_buf );
2573 jsonObjectFree( freeable_hash );
2578 buffer_add(join_buf, " ) ");
2580 // Recursively add a nested join, if one is present
2581 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2583 char* jpred = searchJOIN( join_filter, right_info );
2585 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2586 OSRF_BUFFER_ADD( join_buf, jpred );
2589 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2590 jsonIteratorFree( search_itr );
2591 buffer_free( join_buf );
2593 jsonObjectFree( freeable_hash );
2600 jsonObjectFree(freeable_hash);
2601 jsonIteratorFree(search_itr);
2603 return buffer_release(join_buf);
2608 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2609 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2610 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2612 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2614 search_hash is the JSON expression of the conditions.
2615 meta is the class definition from the IDL, for the relevant table.
2616 opjoin_type indicates whether multiple conditions, if present, should be
2617 connected by AND or OR.
2618 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2619 to pass it to other functions -- and all they do with it is to use the session
2620 and request members to send error messages back to the client.
2624 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
2625 int opjoin_type, osrfMethodContext* ctx ) {
2629 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2632 class_info->class_def,
2637 growing_buffer* sql_buf = buffer_init(128);
2639 jsonObject* node = NULL;
2642 if ( search_hash->type == JSON_ARRAY ) {
2643 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2644 if( 0 == search_hash->size ) {
2647 "%s: Invalid predicate structure: empty JSON array",
2650 buffer_free( sql_buf );
2654 unsigned long i = 0;
2655 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
2659 if (opjoin_type == OR_OP_JOIN)
2660 buffer_add(sql_buf, " OR ");
2662 buffer_add(sql_buf, " AND ");
2665 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2667 buffer_free( sql_buf );
2671 buffer_fadd(sql_buf, "( %s )", subpred);
2675 } else if ( search_hash->type == JSON_HASH ) {
2676 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2677 jsonIterator* search_itr = jsonNewIterator( search_hash );
2678 if( !jsonIteratorHasNext( search_itr ) ) {
2681 "%s: Invalid predicate structure: empty JSON object",
2684 jsonIteratorFree( search_itr );
2685 buffer_free( sql_buf );
2689 while ( (node = jsonIteratorNext( search_itr )) ) {
2694 if (opjoin_type == OR_OP_JOIN)
2695 buffer_add(sql_buf, " OR ");
2697 buffer_add(sql_buf, " AND ");
2700 if ( '+' == search_itr->key[ 0 ] ) {
2702 // This plus sign prefixes a class name or other table alias;
2703 // make sure the table alias is in scope
2704 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
2705 if( ! alias_info ) {
2708 "%s: Invalid table alias \"%s\" in WHERE clause",
2712 jsonIteratorFree( search_itr );
2713 buffer_free( sql_buf );
2717 if ( node->type == JSON_STRING ) {
2718 // It's the name of a column; make sure it belongs to the class
2719 const char* fieldname = jsonObjectGetString( node );
2720 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
2723 "%s: Invalid column name \"%s\" in WHERE clause for table alias \"%s\"",
2728 jsonIteratorFree( search_itr );
2729 buffer_free( sql_buf );
2733 buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
2735 // It's something more complicated
2736 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
2738 jsonIteratorFree( search_itr );
2739 buffer_free( sql_buf );
2743 buffer_fadd(sql_buf, "( %s )", subpred);
2746 } else if ( '-' == search_itr->key[ 0 ] ) {
2747 if ( !strcasecmp("-or",search_itr->key) ) {
2748 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
2750 jsonIteratorFree( search_itr );
2751 buffer_free( sql_buf );
2755 buffer_fadd(sql_buf, "( %s )", subpred);
2757 } else if ( !strcasecmp("-and",search_itr->key) ) {
2758 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2760 jsonIteratorFree( search_itr );
2761 buffer_free( sql_buf );
2765 buffer_fadd(sql_buf, "( %s )", subpred);
2767 } else if ( !strcasecmp("-not",search_itr->key) ) {
2768 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2770 jsonIteratorFree( search_itr );
2771 buffer_free( sql_buf );
2775 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2777 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2778 char* subpred = buildQuery( ctx, node, SUBSELECT );
2780 jsonIteratorFree( search_itr );
2781 buffer_free( sql_buf );
2785 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2787 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2788 char* subpred = buildQuery( ctx, node, SUBSELECT );
2790 jsonIteratorFree( search_itr );
2791 buffer_free( sql_buf );
2795 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2797 } else { // Invalid "minus" operator
2800 "%s: Invalid operator \"%s\" in WHERE clause",
2804 jsonIteratorFree( search_itr );
2805 buffer_free( sql_buf );
2811 const char* class = class_info->class_name;
2812 osrfHash* fields = class_info->fields;
2813 osrfHash* field = osrfHashGet( fields, search_itr->key );
2816 const char* table = class_info->source_def;
2819 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2822 table ? table : "?",
2825 jsonIteratorFree(search_itr);
2826 buffer_free(sql_buf);
2830 char* subpred = searchPredicate( class_info, field, node, ctx );
2832 buffer_free(sql_buf);
2833 jsonIteratorFree(search_itr);
2837 buffer_add( sql_buf, subpred );
2841 jsonIteratorFree(search_itr);
2844 // ERROR ... only hash and array allowed at this level
2845 char* predicate_string = jsonObjectToJSON( search_hash );
2848 "%s: Invalid predicate structure: %s",
2852 buffer_free(sql_buf);
2853 free(predicate_string);
2857 return buffer_release(sql_buf);
2860 /* Build a JSON_ARRAY of field names for a given table alias
2862 static jsonObject* defaultSelectList( const char* table_alias ) {
2867 ClassInfo* class_info = search_all_alias( table_alias );
2868 if( ! class_info ) {
2871 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
2878 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
2879 osrfHash* field_def = NULL;
2880 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
2881 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2882 const char* field_name = osrfHashIteratorKey( field_itr );
2883 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2884 jsonObjectPush( array, jsonNewObject( field_name ) );
2887 osrfHashIteratorFree( field_itr );
2892 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
2893 // The jsonObject must be a JSON_HASH with an single entry for "union",
2894 // "intersect", or "except". The data associated with this key must be an
2895 // array of hashes, each hash being a query.
2896 // Also allowed but currently ignored: entries for "order_by" and "alias".
2897 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
2899 if( ! combo || combo->type != JSON_HASH )
2900 return NULL; // should be impossible; validated by caller
2902 const jsonObject* query_array = NULL; // array of subordinate queries
2903 const char* op = NULL; // name of operator, e.g. UNION
2904 const char* alias = NULL; // alias for the query (needed for ORDER BY)
2905 int op_count = 0; // for detecting conflicting operators
2906 int excepting = 0; // boolean
2907 int all = 0; // boolean
2908 jsonObject* order_obj = NULL;
2910 // Identify the elements in the hash
2911 jsonIterator* query_itr = jsonNewIterator( combo );
2912 jsonObject* curr_obj = NULL;
2913 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
2914 if( ! strcmp( "union", query_itr->key ) ) {
2917 query_array = curr_obj;
2918 } else if( ! strcmp( "intersect", query_itr->key ) ) {
2921 query_array = curr_obj;
2922 } else if( ! strcmp( "except", query_itr->key ) ) {
2926 query_array = curr_obj;
2927 } else if( ! strcmp( "order_by", query_itr->key ) ) {
2930 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
2933 order_obj = curr_obj;
2934 } else if( ! strcmp( "alias", query_itr->key ) ) {
2935 if( curr_obj->type != JSON_STRING ) {
2936 jsonIteratorFree( query_itr );
2939 alias = jsonObjectGetString( curr_obj );
2940 } else if( ! strcmp( "all", query_itr->key ) ) {
2941 if( obj_is_true( curr_obj ) )
2945 osrfAppSessionStatus(
2947 OSRF_STATUS_INTERNALSERVERERROR,
2948 "osrfMethodException",
2950 "Malformed query; unexpected entry in query object"
2954 "%s: Unexpected entry for \"%s\" in%squery",
2959 jsonIteratorFree( query_itr );
2963 jsonIteratorFree( query_itr );
2965 // More sanity checks
2966 if( ! query_array ) {
2968 osrfAppSessionStatus(
2970 OSRF_STATUS_INTERNALSERVERERROR,
2971 "osrfMethodException",
2973 "Expected UNION, INTERSECT, or EXCEPT operator not found"
2977 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
2980 return NULL; // should be impossible...
2981 } else if( op_count > 1 ) {
2983 osrfAppSessionStatus(
2985 OSRF_STATUS_INTERNALSERVERERROR,
2986 "osrfMethodException",
2988 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
2992 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
2996 } if( query_array->type != JSON_ARRAY ) {
2998 osrfAppSessionStatus(
3000 OSRF_STATUS_INTERNALSERVERERROR,
3001 "osrfMethodException",
3003 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3007 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3010 json_type( query_array->type )
3013 } if( query_array->size < 2 ) {
3015 osrfAppSessionStatus(
3017 OSRF_STATUS_INTERNALSERVERERROR,
3018 "osrfMethodException",
3020 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3024 "%s:%srequires multiple queries as operands",
3029 } else if( excepting && query_array->size > 2 ) {
3031 osrfAppSessionStatus(
3033 OSRF_STATUS_INTERNALSERVERERROR,
3034 "osrfMethodException",
3036 "EXCEPT operator has too many queries as operands"
3040 "%s:EXCEPT operator has too many queries as operands",
3044 } else if( order_obj && ! alias ) {
3046 osrfAppSessionStatus(
3048 OSRF_STATUS_INTERNALSERVERERROR,
3049 "osrfMethodException",
3051 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3055 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3061 // So far so good. Now build the SQL.
3062 growing_buffer* sql = buffer_init( 256 );
3064 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3065 // Add a layer of parentheses
3066 if( flags & SUBCOMBO )
3067 OSRF_BUFFER_ADD( sql, "( " );
3069 // Traverse the query array. Each entry should be a hash.
3070 int first = 1; // boolean
3072 jsonObject* query = NULL;
3073 while((query = jsonObjectGetIndex( query_array, i++ ) )) {
3074 if( query->type != JSON_HASH ) {
3076 osrfAppSessionStatus(
3078 OSRF_STATUS_INTERNALSERVERERROR,
3079 "osrfMethodException",
3081 "Malformed query under UNION, INTERSECT or EXCEPT"
3085 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3088 json_type( query->type )
3097 OSRF_BUFFER_ADD( sql, op );
3099 OSRF_BUFFER_ADD( sql, "ALL " );
3102 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3106 "%s: Error building query under%s",
3114 OSRF_BUFFER_ADD( sql, query_str );
3117 if( flags & SUBCOMBO )
3118 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3120 if ( !(flags & SUBSELECT) )
3121 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3123 return buffer_release( sql );
3126 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3127 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3128 // or "except" to indicate the type of query.
3129 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3133 osrfAppSessionStatus(
3135 OSRF_STATUS_INTERNALSERVERERROR,
3136 "osrfMethodException",
3138 "Malformed query; no query object"
3140 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", MODULENAME );
3142 } else if( query->type != JSON_HASH ) {
3144 osrfAppSessionStatus(
3146 OSRF_STATUS_INTERNALSERVERERROR,
3147 "osrfMethodException",
3149 "Malformed query object"
3153 "%s: Query object is %s instead of JSON_HASH",
3155 json_type( query->type )
3160 // Determine what kind of query it purports to be, and dispatch accordingly.
3161 if( jsonObjectGetKey( query, "union" ) ||
3162 jsonObjectGetKey( query, "intersect" ) ||
3163 jsonObjectGetKey( query, "except" ) ) {
3164 return doCombo( ctx, query, flags );
3166 // It is presumably a SELECT query
3168 // Push a node onto the stack for the current query. Every level of
3169 // subquery gets its own QueryFrame on the Stack.
3172 // Build an SQL SELECT statement
3175 jsonObjectGetKey( query, "select" ),
3176 jsonObjectGetKey( query, "from" ),
3177 jsonObjectGetKey( query, "where" ),
3178 jsonObjectGetKey( query, "having" ),
3179 jsonObjectGetKey( query, "order_by" ),
3180 jsonObjectGetKey( query, "limit" ),
3181 jsonObjectGetKey( query, "offset" ),
3190 /* method context */ osrfMethodContext* ctx,
3192 /* SELECT */ jsonObject* selhash,
3193 /* FROM */ jsonObject* join_hash,
3194 /* WHERE */ jsonObject* search_hash,
3195 /* HAVING */ jsonObject* having_hash,
3196 /* ORDER BY */ jsonObject* order_hash,
3197 /* LIMIT */ jsonObject* limit,
3198 /* OFFSET */ jsonObject* offset,
3199 /* flags */ int flags
3201 const char* locale = osrf_message_get_last_locale();
3203 // general tmp objects
3204 const jsonObject* tmp_const;
3205 jsonObject* selclass = NULL;
3206 jsonObject* snode = NULL;
3207 jsonObject* onode = NULL;
3209 char* string = NULL;
3210 int from_function = 0;
3215 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3217 // punt if there's no FROM clause
3218 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3221 "%s: FROM clause is missing or empty",
3225 osrfAppSessionStatus(
3227 OSRF_STATUS_INTERNALSERVERERROR,
3228 "osrfMethodException",
3230 "FROM clause is missing or empty in JSON query"
3235 // the core search class
3236 const char* core_class = NULL;
3238 // get the core class -- the only key of the top level FROM clause, or a string
3239 if (join_hash->type == JSON_HASH) {
3240 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3241 snode = jsonIteratorNext( tmp_itr );
3243 // Populate the current QueryFrame with information
3244 // about the core class
3245 if( add_query_core( NULL, tmp_itr->key ) ) {
3247 osrfAppSessionStatus(
3249 OSRF_STATUS_INTERNALSERVERERROR,
3250 "osrfMethodException",
3252 "Unable to look up core class"
3256 core_class = curr_query->core.class_name;
3259 jsonObject* extra = jsonIteratorNext( tmp_itr );
3261 jsonIteratorFree( tmp_itr );
3264 // There shouldn't be more than one entry in join_hash
3268 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3272 osrfAppSessionStatus(
3274 OSRF_STATUS_INTERNALSERVERERROR,
3275 "osrfMethodException",
3277 "Malformed FROM clause in JSON query"
3279 return NULL; // Malformed join_hash; extra entry
3281 } else if (join_hash->type == JSON_ARRAY) {
3282 // We're selecting from a function, not from a table
3284 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
3287 } else if (join_hash->type == JSON_STRING) {
3288 // Populate the current QueryFrame with information
3289 // about the core class
3290 core_class = jsonObjectGetString( join_hash );
3292 if( add_query_core( NULL, core_class ) ) {
3294 osrfAppSessionStatus(
3296 OSRF_STATUS_INTERNALSERVERERROR,
3297 "osrfMethodException",
3299 "Unable to look up core class"
3307 "%s: FROM clause is unexpected JSON type: %s",
3309 json_type( join_hash->type )
3312 osrfAppSessionStatus(
3314 OSRF_STATUS_INTERNALSERVERERROR,
3315 "osrfMethodException",
3317 "Ill-formed FROM clause in JSON query"
3322 // Build the join clause, if any, while filling out the list
3323 // of joined classes in the current QueryFrame.
3324 char* join_clause = NULL;
3325 if( join_hash && ! from_function ) {
3327 join_clause = searchJOIN( join_hash, &curr_query->core );
3328 if( ! join_clause ) {
3330 osrfAppSessionStatus(
3332 OSRF_STATUS_INTERNALSERVERERROR,
3333 "osrfMethodException",
3335 "Unable to construct JOIN clause(s)"
3341 // For in case we don't get a select list
3342 jsonObject* defaultselhash = NULL;
3344 // if there is no select list, build a default select list ...
3345 if (!selhash && !from_function) {
3346 jsonObject* default_list = defaultSelectList( core_class );
3347 if( ! default_list ) {
3349 osrfAppSessionStatus(
3351 OSRF_STATUS_INTERNALSERVERERROR,
3352 "osrfMethodException",
3354 "Unable to build default SELECT clause in JSON query"
3356 free( join_clause );
3361 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
3362 jsonObjectSetKey( selhash, core_class, default_list );
3365 // The SELECT clause can be encoded only by a hash
3366 if( !from_function && selhash->type != JSON_HASH ) {
3369 "%s: Expected JSON_HASH for SELECT clause; found %s",
3371 json_type( selhash->type )
3375 osrfAppSessionStatus(
3377 OSRF_STATUS_INTERNALSERVERERROR,
3378 "osrfMethodException",
3380 "Malformed SELECT clause in JSON query"
3382 free( join_clause );
3386 // If you see a null or wild card specifier for the core class, or an
3387 // empty array, replace it with a default SELECT list
3388 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3390 int default_needed = 0; // boolean
3391 if( JSON_STRING == tmp_const->type
3392 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3394 else if( JSON_NULL == tmp_const->type )
3397 if( default_needed ) {
3398 // Build a default SELECT list
3399 jsonObject* default_list = defaultSelectList( core_class );
3400 if( ! default_list ) {
3402 osrfAppSessionStatus(
3404 OSRF_STATUS_INTERNALSERVERERROR,
3405 "osrfMethodException",
3407 "Can't build default SELECT clause in JSON query"
3409 free( join_clause );
3414 jsonObjectSetKey( selhash, core_class, default_list );
3418 // temp buffers for the SELECT list and GROUP BY clause
3419 growing_buffer* select_buf = buffer_init(128);
3420 growing_buffer* group_buf = buffer_init(128);
3422 int aggregate_found = 0; // boolean
3424 // Build a select list
3425 if(from_function) // From a function we select everything
3426 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3429 // Build the SELECT list as SQL
3433 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3434 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3436 const char* cname = selclass_itr->key;
3438 // Make sure the target relation is in the FROM clause.
3440 // At this point join_hash is a step down from the join_hash we
3441 // received as a parameter. If the original was a JSON_STRING,
3442 // then json_hash is now NULL. If the original was a JSON_HASH,
3443 // then json_hash is now the first (and only) entry in it,
3444 // denoting the core class. We've already excluded the
3445 // possibility that the original was a JSON_ARRAY, because in
3446 // that case from_function would be non-NULL, and we wouldn't
3449 // If the current table alias isn't in scope, bail out
3450 ClassInfo* class_info = search_alias( cname );
3451 if( ! class_info ) {
3454 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3459 osrfAppSessionStatus(
3461 OSRF_STATUS_INTERNALSERVERERROR,
3462 "osrfMethodException",
3464 "Selected class not in FROM clause in JSON query"
3466 jsonIteratorFree( selclass_itr );
3467 buffer_free( select_buf );
3468 buffer_free( group_buf );
3469 if( defaultselhash ) jsonObjectFree( defaultselhash );
3470 free( join_clause );
3474 if( selclass->type != JSON_ARRAY ) {
3477 "%s: Malformed SELECT list for class \"%s\"; not an array",
3482 osrfAppSessionStatus(
3484 OSRF_STATUS_INTERNALSERVERERROR,
3485 "osrfMethodException",
3487 "Selected class not in FROM clause in JSON query"
3490 jsonIteratorFree( selclass_itr );
3491 buffer_free( select_buf );
3492 buffer_free( group_buf );
3493 if( defaultselhash ) jsonObjectFree( defaultselhash );
3494 free( join_clause );
3498 // Look up some attributes of the current class
3499 osrfHash* idlClass = class_info->class_def;
3500 osrfHash* class_field_set = class_info->fields;
3501 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3502 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3504 if( 0 == selclass->size ) {
3507 "%s: No columns selected from \"%s\"",
3513 // stitch together the column list for the current table alias...
3514 unsigned long field_idx = 0;
3515 jsonObject* selfield = NULL;
3516 while((selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3518 // If we need a separator comma, add one
3522 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3525 // if the field specification is a string, add it to the list
3526 if (selfield->type == JSON_STRING) {
3528 // Look up the field in the IDL
3529 const char* col_name = jsonObjectGetString( selfield );
3530 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3532 // No such field in current class
3535 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3541 osrfAppSessionStatus(
3543 OSRF_STATUS_INTERNALSERVERERROR,
3544 "osrfMethodException",
3546 "Selected column not defined in JSON query"
3548 jsonIteratorFree( selclass_itr );
3549 buffer_free( select_buf );
3550 buffer_free( group_buf );
3551 if( defaultselhash ) jsonObjectFree( defaultselhash );
3552 free( join_clause );
3554 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3555 // Virtual field not allowed
3558 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3564 osrfAppSessionStatus(
3566 OSRF_STATUS_INTERNALSERVERERROR,
3567 "osrfMethodException",
3569 "Selected column may not be virtual in JSON query"
3571 jsonIteratorFree( selclass_itr );
3572 buffer_free( select_buf );
3573 buffer_free( group_buf );
3574 if( defaultselhash ) jsonObjectFree( defaultselhash );
3575 free( join_clause );
3581 if (flags & DISABLE_I18N)
3584 i18n = osrfHashGet(field_def, "i18n");
3586 if( str_is_true( i18n ) ) {
3587 buffer_fadd( select_buf,
3588 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3589 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3591 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3594 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3597 // ... but it could be an object, in which case we check for a Field Transform
3598 } else if (selfield->type == JSON_HASH) {
3600 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3602 // Get the field definition from the IDL
3603 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3605 // No such field in current class
3608 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3614 osrfAppSessionStatus(
3616 OSRF_STATUS_INTERNALSERVERERROR,
3617 "osrfMethodException",
3619 "Selected column is not defined in JSON query"
3621 jsonIteratorFree( selclass_itr );
3622 buffer_free( select_buf );
3623 buffer_free( group_buf );
3624 if( defaultselhash ) jsonObjectFree( defaultselhash );
3625 free( join_clause );
3627 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3628 // No such field in current class
3631 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3637 osrfAppSessionStatus(
3639 OSRF_STATUS_INTERNALSERVERERROR,
3640 "osrfMethodException",
3642 "Selected column is virtual in JSON query"
3644 jsonIteratorFree( selclass_itr );
3645 buffer_free( select_buf );
3646 buffer_free( group_buf );
3647 if( defaultselhash ) jsonObjectFree( defaultselhash );
3648 free( join_clause );
3652 // Decide what to use as a column alias
3654 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3655 _alias = jsonObjectGetString( tmp_const );
3656 } else { // Use field name as the alias
3660 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3661 char* transform_str = searchFieldTransform(class_info->alias, field_def, selfield);
3662 if( transform_str ) {
3663 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3664 free(transform_str);
3667 osrfAppSessionStatus(
3669 OSRF_STATUS_INTERNALSERVERERROR,
3670 "osrfMethodException",
3672 "Unable to generate transform function in JSON query"
3674 jsonIteratorFree( selclass_itr );
3675 buffer_free( select_buf );
3676 buffer_free( group_buf );
3677 if( defaultselhash ) jsonObjectFree( defaultselhash );
3678 free( join_clause );
3685 if (flags & DISABLE_I18N)
3688 i18n = osrfHashGet(field_def, "i18n");
3690 if( str_is_true( i18n ) ) {
3691 buffer_fadd( select_buf,
3692 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3693 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3695 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3698 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3705 "%s: Selected item is unexpected JSON type: %s",
3707 json_type( selfield->type )
3710 osrfAppSessionStatus(
3712 OSRF_STATUS_INTERNALSERVERERROR,
3713 "osrfMethodException",
3715 "Ill-formed SELECT item in JSON query"
3717 jsonIteratorFree( selclass_itr );
3718 buffer_free( select_buf );
3719 buffer_free( group_buf );
3720 if( defaultselhash ) jsonObjectFree( defaultselhash );
3721 free( join_clause );
3725 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3726 if( obj_is_true( agg_obj ) )
3727 aggregate_found = 1;
3729 // Append a comma (except for the first one)
3730 // and add the column to a GROUP BY clause
3734 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3736 buffer_fadd(group_buf, " %d", sel_pos);
3740 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3742 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3743 if ( ! obj_is_true( aggregate_obj ) ) {
3747 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3750 buffer_fadd(group_buf, " %d", sel_pos);
3753 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3757 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3760 _column = searchFieldTransform(class_info->alias, field, selfield);
3761 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3762 OSRF_BUFFER_ADD(group_buf, _column);
3763 _column = searchFieldTransform(class_info->alias, field, selfield);
3770 } // end while -- iterating across SELECT columns
3772 } // end while -- iterating across classes
3774 jsonIteratorFree(selclass_itr);
3778 char* col_list = buffer_release(select_buf);
3780 // Make sure the SELECT list isn't empty. This can happen, for example,
3781 // if we try to build a default SELECT clause from a non-core table.
3784 osrfLogError(OSRF_LOG_MARK, "%s: SELECT clause is empty", MODULENAME );
3786 osrfAppSessionStatus(
3788 OSRF_STATUS_INTERNALSERVERERROR,
3789 "osrfMethodException",
3791 "SELECT list is empty"
3794 buffer_free( group_buf );
3795 if( defaultselhash ) jsonObjectFree( defaultselhash );
3796 free( join_clause );
3801 if (from_function) table = searchValueTransform(join_hash);
3802 else table = strdup( curr_query->core.source_def );
3806 osrfAppSessionStatus(
3808 OSRF_STATUS_INTERNALSERVERERROR,
3809 "osrfMethodException",
3811 "Unable to identify table for core class"
3814 buffer_free( group_buf );
3815 if( defaultselhash ) jsonObjectFree( defaultselhash );
3816 free( join_clause );
3820 // Put it all together
3821 growing_buffer* sql_buf = buffer_init(128);
3822 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3826 // Append the join clause, if any
3828 buffer_add(sql_buf, join_clause);
3832 char* order_by_list = NULL;
3833 char* having_buf = NULL;
3835 if (!from_function) {
3837 // Build a WHERE clause, if there is one
3838 if ( search_hash ) {
3839 buffer_add(sql_buf, " WHERE ");
3841 // and it's on the WHERE clause
3842 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
3845 osrfAppSessionStatus(
3847 OSRF_STATUS_INTERNALSERVERERROR,
3848 "osrfMethodException",
3850 "Severe query error in WHERE predicate -- see error log for more details"
3853 buffer_free(group_buf);
3854 buffer_free(sql_buf);
3855 if (defaultselhash) jsonObjectFree(defaultselhash);
3859 buffer_add(sql_buf, pred);
3863 // Build a HAVING clause, if there is one
3864 if ( having_hash ) {
3866 // and it's on the the WHERE clause
3867 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
3869 if( ! having_buf ) {
3871 osrfAppSessionStatus(
3873 OSRF_STATUS_INTERNALSERVERERROR,
3874 "osrfMethodException",
3876 "Severe query error in HAVING predicate -- see error log for more details"
3879 buffer_free(group_buf);
3880 buffer_free(sql_buf);
3881 if (defaultselhash) jsonObjectFree(defaultselhash);
3886 growing_buffer* order_buf = NULL; // to collect ORDER BY list
3888 // Build an ORDER BY clause, if there is one
3889 if( NULL == order_hash )
3890 ; // No ORDER BY? do nothing
3891 else if( JSON_ARRAY == order_hash->type ) {
3892 // Array of field specifications, each specification being a
3893 // hash to define the class, field, and other details
3895 jsonObject* order_spec;
3896 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
3898 if( JSON_HASH != order_spec->type ) {
3899 osrfLogError(OSRF_LOG_MARK,
3900 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
3901 MODULENAME, json_type( order_spec->type ) );
3903 osrfAppSessionStatus(
3905 OSRF_STATUS_INTERNALSERVERERROR,
3906 "osrfMethodException",
3908 "Malformed ORDER BY clause -- see error log for more details"
3910 buffer_free( order_buf );
3912 buffer_free(group_buf);
3913 buffer_free(sql_buf);
3914 if (defaultselhash) jsonObjectFree(defaultselhash);
3918 const char* class_alias =
3919 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
3921 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
3924 OSRF_BUFFER_ADD(order_buf, ", ");
3926 order_buf = buffer_init(128);
3928 if( !field || !class_alias ) {
3929 osrfLogError(OSRF_LOG_MARK,
3930 "%s: Missing class or field name in field specification of ORDER BY clause",
3933 osrfAppSessionStatus(
3935 OSRF_STATUS_INTERNALSERVERERROR,
3936 "osrfMethodException",
3938 "Malformed ORDER BY clause -- see error log for more details"
3940 buffer_free( order_buf );
3942 buffer_free(group_buf);
3943 buffer_free(sql_buf);
3944 if (defaultselhash) jsonObjectFree(defaultselhash);
3948 ClassInfo* order_class_info = search_alias( class_alias );
3949 if( ! order_class_info ) {
3950 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
3951 "not in FROM clause", MODULENAME, class_alias );
3953 osrfAppSessionStatus(
3955 OSRF_STATUS_INTERNALSERVERERROR,
3956 "osrfMethodException",
3958 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3961 buffer_free(group_buf);
3962 buffer_free(sql_buf);
3963 if (defaultselhash) jsonObjectFree(defaultselhash);
3967 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
3969 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
3970 MODULENAME, class_alias, field );
3972 osrfAppSessionStatus(
3974 OSRF_STATUS_INTERNALSERVERERROR,
3975 "osrfMethodException",
3977 "Invalid field referenced in ORDER BY clause -- see error log for more details"
3980 buffer_free(group_buf);
3981 buffer_free(sql_buf);
3982 if (defaultselhash) jsonObjectFree(defaultselhash);
3984 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3985 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3986 MODULENAME, field );
3988 osrfAppSessionStatus(
3990 OSRF_STATUS_INTERNALSERVERERROR,
3991 "osrfMethodException",
3993 "Virtual field in ORDER BY clause -- see error log for more details"
3995 buffer_free( order_buf );
3997 buffer_free(group_buf);
3998 buffer_free(sql_buf);
3999 if (defaultselhash) jsonObjectFree(defaultselhash);
4003 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4004 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
4005 if( ! transform_str ) {
4007 osrfAppSessionStatus(
4009 OSRF_STATUS_INTERNALSERVERERROR,
4010 "osrfMethodException",
4012 "Severe query error in ORDER BY clause -- see error log for more details"
4014 buffer_free( order_buf );
4016 buffer_free(group_buf);
4017 buffer_free(sql_buf);
4018 if (defaultselhash) jsonObjectFree(defaultselhash);
4022 OSRF_BUFFER_ADD( order_buf, transform_str );
4023 free( transform_str );
4026 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4028 const char* direction =
4029 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4031 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4032 OSRF_BUFFER_ADD( order_buf, " DESC" );
4034 OSRF_BUFFER_ADD( order_buf, " ASC" );
4037 } else if( JSON_HASH == order_hash->type ) {
4038 // This hash is keyed on class alias. Each class has either
4039 // an array of field names or a hash keyed on field name.
4040 jsonIterator* class_itr = jsonNewIterator( order_hash );
4041 while ( (snode = jsonIteratorNext( class_itr )) ) {
4043 ClassInfo* order_class_info = search_alias( class_itr->key );
4044 if( ! order_class_info ) {
4045 osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4046 MODULENAME, class_itr->key );
4048 osrfAppSessionStatus(
4050 OSRF_STATUS_INTERNALSERVERERROR,
4051 "osrfMethodException",
4053 "Invalid class referenced in ORDER BY clause -- see error log for more details"
4055 jsonIteratorFree( class_itr );
4056 buffer_free( order_buf );
4058 buffer_free(group_buf);
4059 buffer_free(sql_buf);
4060 if (defaultselhash) jsonObjectFree(defaultselhash);
4064 osrfHash* field_list_def = order_class_info->fields;
4066 if ( snode->type == JSON_HASH ) {
4068 // Hash is keyed on field names from the current class. For each field
4069 // there is another layer of hash to define the sorting details, if any,
4070 // or a string to indicate direction of sorting.
4071 jsonIterator* order_itr = jsonNewIterator( snode );
4072 while ( (onode = jsonIteratorNext( order_itr )) ) {
4074 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4076 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
4077 MODULENAME, order_itr->key );
4079 osrfAppSessionStatus(
4081 OSRF_STATUS_INTERNALSERVERERROR,
4082 "osrfMethodException",
4084 "Invalid field in ORDER BY clause -- see error log for more details"
4086 jsonIteratorFree( order_itr );
4087 jsonIteratorFree( class_itr );
4088 buffer_free( order_buf );
4090 buffer_free(group_buf);
4091 buffer_free(sql_buf);
4092 if (defaultselhash) jsonObjectFree(defaultselhash);
4094 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4095 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4096 MODULENAME, order_itr->key );
4098 osrfAppSessionStatus(
4100 OSRF_STATUS_INTERNALSERVERERROR,
4101 "osrfMethodException",
4103 "Virtual field in ORDER BY clause -- see error log for more details"
4105 jsonIteratorFree( order_itr );
4106 jsonIteratorFree( class_itr );
4107 buffer_free( order_buf );
4109 buffer_free(group_buf);
4110 buffer_free(sql_buf);
4111 if (defaultselhash) jsonObjectFree(defaultselhash);
4115 const char* direction = NULL;
4116 if ( onode->type == JSON_HASH ) {
4117 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4118 string = searchFieldTransform(
4120 osrfHashGet( field_list_def, order_itr->key ),
4124 if( ctx ) osrfAppSessionStatus(
4126 OSRF_STATUS_INTERNALSERVERERROR,
4127 "osrfMethodException",
4129 "Severe query error in ORDER BY clause -- see error log for more details"
4131 jsonIteratorFree( order_itr );
4132 jsonIteratorFree( class_itr );
4134 buffer_free(group_buf);
4135 buffer_free(order_buf);
4136 buffer_free(sql_buf);
4137 if (defaultselhash) jsonObjectFree(defaultselhash);
4141 growing_buffer* field_buf = buffer_init(16);
4142 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4143 string = buffer_release(field_buf);
4146 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4147 const char* dir = jsonObjectGetString(tmp_const);
4148 if (!strncasecmp(dir, "d", 1)) {
4149 direction = " DESC";
4155 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4156 osrfLogError( OSRF_LOG_MARK,
4157 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4158 MODULENAME, json_type( onode->type ) );
4160 osrfAppSessionStatus(
4162 OSRF_STATUS_INTERNALSERVERERROR,
4163 "osrfMethodException",
4165 "Malformed ORDER BY clause -- see error log for more details"
4167 jsonIteratorFree( order_itr );
4168 jsonIteratorFree( class_itr );
4170 buffer_free(group_buf);
4171 buffer_free(order_buf);
4172 buffer_free(sql_buf);
4173 if (defaultselhash) jsonObjectFree(defaultselhash);
4177 string = strdup(order_itr->key);
4178 const char* dir = jsonObjectGetString(onode);
4179 if (!strncasecmp(dir, "d", 1)) {
4180 direction = " DESC";
4187 OSRF_BUFFER_ADD(order_buf, ", ");
4189 order_buf = buffer_init(128);
4191 OSRF_BUFFER_ADD(order_buf, string);
4195 OSRF_BUFFER_ADD(order_buf, direction);
4199 jsonIteratorFree(order_itr);
4201 } else if ( snode->type == JSON_ARRAY ) {
4203 // Array is a list of fields from the current class
4204 unsigned long order_idx = 0;
4205 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4207 const char* _f = jsonObjectGetString( onode );
4209 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4211 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
4214 osrfAppSessionStatus(
4216 OSRF_STATUS_INTERNALSERVERERROR,
4217 "osrfMethodException",
4219 "Invalid field in ORDER BY clause -- see error log for more details"
4221 jsonIteratorFree( class_itr );
4222 buffer_free( order_buf );
4224 buffer_free(group_buf);
4225 buffer_free(sql_buf);
4226 if (defaultselhash) jsonObjectFree(defaultselhash);
4228 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4229 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4232 osrfAppSessionStatus(
4234 OSRF_STATUS_INTERNALSERVERERROR,
4235 "osrfMethodException",
4237 "Virtual field in ORDER BY clause -- see error log for more details"
4239 jsonIteratorFree( class_itr );
4240 buffer_free( order_buf );
4242 buffer_free(group_buf);
4243 buffer_free(sql_buf);
4244 if (defaultselhash) jsonObjectFree(defaultselhash);
4249 OSRF_BUFFER_ADD(order_buf, ", ");
4251 order_buf = buffer_init(128);
4253 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
4257 // IT'S THE OOOOOOOOOOOLD STYLE!
4259 osrfLogError(OSRF_LOG_MARK,
4260 "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
4262 osrfAppSessionStatus(
4264 OSRF_STATUS_INTERNALSERVERERROR,
4265 "osrfMethodException",
4267 "Severe query error -- see error log for more details"
4272 buffer_free(group_buf);
4273 buffer_free(order_buf);
4274 buffer_free(sql_buf);
4275 if (defaultselhash) jsonObjectFree(defaultselhash);
4276 jsonIteratorFree(class_itr);
4280 jsonIteratorFree( class_itr );
4282 osrfLogError(OSRF_LOG_MARK,
4283 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4284 MODULENAME, json_type( order_hash->type ) );
4286 osrfAppSessionStatus(
4288 OSRF_STATUS_INTERNALSERVERERROR,
4289 "osrfMethodException",
4291 "Malformed ORDER BY clause -- see error log for more details"
4293 buffer_free( order_buf );
4295 buffer_free(group_buf);
4296 buffer_free(sql_buf);
4297 if (defaultselhash) jsonObjectFree(defaultselhash);
4302 order_by_list = buffer_release( order_buf );
4306 string = buffer_release(group_buf);
4308 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4309 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4310 OSRF_BUFFER_ADD( sql_buf, string );
4315 if( having_buf && *having_buf ) {
4316 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4317 OSRF_BUFFER_ADD( sql_buf, having_buf );
4321 if( order_by_list ) {
4323 if ( *order_by_list ) {
4324 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4325 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4328 free( order_by_list );
4332 const char* str = jsonObjectGetString(limit);
4333 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
4337 const char* str = jsonObjectGetString(offset);
4338 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
4341 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4343 if (defaultselhash) jsonObjectFree(defaultselhash);
4345 return buffer_release(sql_buf);
4347 } // end of SELECT()
4349 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4351 const char* locale = osrf_message_get_last_locale();
4353 osrfHash* fields = osrfHashGet(meta, "fields");
4354 char* core_class = osrfHashGet(meta, "classname");
4356 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4358 jsonObject* node = NULL;
4359 jsonObject* snode = NULL;
4360 jsonObject* onode = NULL;
4361 const jsonObject* _tmp = NULL;
4362 jsonObject* selhash = NULL;
4363 jsonObject* defaultselhash = NULL;
4365 growing_buffer* sql_buf = buffer_init(128);
4366 growing_buffer* select_buf = buffer_init(128);
4368 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4369 defaultselhash = jsonNewObjectType(JSON_HASH);
4370 selhash = defaultselhash;
4373 // If there's no SELECT list for the core class, build one
4374 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
4375 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4377 // Add every non-virtual field to the field list
4378 osrfHash* field_def = NULL;
4379 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4380 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4381 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4382 const char* field = osrfHashIteratorKey( field_itr );
4383 jsonObjectPush( field_list, jsonNewObject( field ) );
4386 osrfHashIteratorFree( field_itr );
4387 jsonObjectSetKey( selhash, core_class, field_list );
4391 jsonIterator* class_itr = jsonNewIterator( selhash );
4392 while ( (snode = jsonIteratorNext( class_itr )) ) {
4394 const char* cname = class_itr->key;
4395 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4396 if (!idlClass) continue;
4398 if (strcmp(core_class,class_itr->key)) {
4399 if (!join_hash) continue;
4401 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
4403 jsonObjectFree(found);
4407 jsonObjectFree(found);
4410 jsonIterator* select_itr = jsonNewIterator( snode );
4411 while ( (node = jsonIteratorNext( select_itr )) ) {
4412 const char* item_str = jsonObjectGetString( node );
4413 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4414 char* fname = osrfHashGet(field, "name");
4416 if (!field) continue;
4421 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
4426 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4427 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4430 i18n = osrfHashGet(field, "i18n");
4432 if( str_is_true( i18n ) ) {
4433 char* pkey = osrfHashGet(idlClass, "primarykey");
4434 char* tname = osrfHashGet(idlClass, "tablename");
4436 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);
4438 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4441 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4445 jsonIteratorFree(select_itr);
4448 jsonIteratorFree(class_itr);
4450 char* col_list = buffer_release(select_buf);
4451 char* table = getSourceDefinition(meta);
4453 table = strdup( "(null)" );
4455 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4459 // Clear the query stack (as a fail-safe precaution against possible
4460 // leftover garbage); then push the first query frame onto the stack.
4461 clear_query_stack();
4463 if( add_query_core( NULL, core_class ) ) {
4465 osrfAppSessionStatus(
4467 OSRF_STATUS_INTERNALSERVERERROR,
4468 "osrfMethodException",
4470 "Unable to build query frame for core class"
4476 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4477 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
4478 OSRF_BUFFER_ADD(sql_buf, join_clause);
4482 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4483 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
4485 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
4487 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4489 osrfAppSessionStatus(
4491 OSRF_STATUS_INTERNALSERVERERROR,
4492 "osrfMethodException",
4494 "Severe query error -- see error log for more details"
4496 buffer_free(sql_buf);
4497 if(defaultselhash) jsonObjectFree(defaultselhash);
4498 clear_query_stack();
4501 buffer_add(sql_buf, pred);
4506 char* string = NULL;
4507 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4509 growing_buffer* order_buf = buffer_init(128);
4512 jsonIterator* class_itr = jsonNewIterator( _tmp );
4513 while ( (snode = jsonIteratorNext( class_itr )) ) {
4515 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4518 if ( snode->type == JSON_HASH ) {
4520 jsonIterator* order_itr = jsonNewIterator( snode );
4521 while ( (onode = jsonIteratorNext( order_itr )) ) {
4523 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4524 class_itr->key, order_itr->key );
4528 char* direction = NULL;
4529 if ( onode->type == JSON_HASH ) {
4530 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4531 string = searchFieldTransform( class_itr->key, field_def, onode );
4533 osrfAppSessionStatus(
4535 OSRF_STATUS_INTERNALSERVERERROR,
4536 "osrfMethodException",
4538 "Severe query error in ORDER BY clause -- see error log for more details"
4540 jsonIteratorFree( order_itr );
4541 jsonIteratorFree( class_itr );
4542 buffer_free( order_buf );
4543 buffer_free( sql_buf );
4544 if( defaultselhash ) jsonObjectFree( defaultselhash );
4545 clear_query_stack();
4549 growing_buffer* field_buf = buffer_init(16);
4550 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4551 string = buffer_release(field_buf);
4554 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4555 const char* dir = jsonObjectGetString(_tmp);
4556 if (!strncasecmp(dir, "d", 1)) {
4557 direction = " DESC";
4564 string = strdup(order_itr->key);
4565 const char* dir = jsonObjectGetString(onode);
4566 if (!strncasecmp(dir, "d", 1)) {
4567 direction = " DESC";
4576 buffer_add(order_buf, ", ");
4579 buffer_add(order_buf, string);
4583 buffer_add(order_buf, direction);
4588 jsonIteratorFree(order_itr);
4591 const char* str = jsonObjectGetString(snode);
4592 buffer_add(order_buf, str);
4598 jsonIteratorFree(class_itr);
4600 string = buffer_release(order_buf);
4603 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4604 OSRF_BUFFER_ADD( sql_buf, string );
4610 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4611 const char* str = jsonObjectGetString(_tmp);
4619 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4621 const char* str = jsonObjectGetString(_tmp);
4630 if (defaultselhash) jsonObjectFree(defaultselhash);
4631 clear_query_stack();
4633 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4634 return buffer_release(sql_buf);
4637 int doJSONSearch ( osrfMethodContext* ctx ) {
4638 if(osrfMethodVerifyContext( ctx )) {
4639 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4643 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
4648 dbhandle = writehandle;
4650 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4654 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4655 flags |= SELECT_DISTINCT;
4657 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4658 flags |= DISABLE_I18N;
4660 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4661 clear_query_stack(); // a possibly needless precaution
4662 char* sql = buildQuery( ctx, hash, flags );
4663 clear_query_stack();
4670 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4671 dbi_result result = dbi_conn_query(dbhandle, sql);
4674 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4676 if (dbi_result_first_row(result)) {
4677 /* JSONify the result */
4678 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4681 jsonObject* return_val = oilsMakeJSONFromResult( result );
4682 osrfAppRespond( ctx, return_val );
4683 jsonObjectFree( return_val );
4684 } while (dbi_result_next_row(result));
4687 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4690 osrfAppRespondComplete( ctx, NULL );
4692 /* clean up the query */
4693 dbi_result_free(result);
4697 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4698 osrfAppSessionStatus(
4700 OSRF_STATUS_INTERNALSERVERERROR,
4701 "osrfMethodException",
4703 "Severe query error -- see error log for more details"
4711 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4712 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4715 dbhandle = writehandle;
4717 osrfHash* links = osrfHashGet(meta, "links");
4718 osrfHash* fields = osrfHashGet(meta, "fields");
4719 char* core_class = osrfHashGet(meta, "classname");
4720 char* pkey = osrfHashGet(meta, "primarykey");
4722 const jsonObject* _tmp;
4725 char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4727 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4732 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4734 dbi_result result = dbi_conn_query(dbhandle, sql);
4735 if( NULL == result ) {
4736 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4737 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4738 osrfAppSessionStatus(
4740 OSRF_STATUS_INTERNALSERVERERROR,
4741 "osrfMethodException",
4743 "Severe query error -- see error log for more details"
4750 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4753 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
4754 osrfHash* dedup = osrfNewHash();
4756 if (dbi_result_first_row(result)) {
4757 /* JSONify the result */
4758 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4760 obj = oilsMakeFieldmapperFromResult( result, meta );
4761 char* pkey_val = oilsFMGetString( obj, pkey );
4762 if ( osrfHashGet( dedup, pkey_val ) ) {
4763 jsonObjectFree(obj);
4766 osrfHashSet( dedup, pkey_val, pkey_val );
4767 jsonObjectPush(res_list, obj);
4769 } while (dbi_result_next_row(result));
4771 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
4775 osrfHashFree(dedup);
4776 /* clean up the query */
4777 dbi_result_free(result);
4780 if (res_list->size && query_hash) {
4781 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
4783 int x = (int)jsonObjectGetNumber(_tmp);
4784 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
4786 const jsonObject* temp_blob;
4787 if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
4789 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
4790 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
4792 osrfStringArray* link_fields = NULL;
4795 if (flesh_fields->size == 1) {
4796 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
4797 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
4802 link_fields = osrfNewStringArray(1);
4803 jsonIterator* _i = jsonNewIterator( flesh_fields );
4804 while ((_f = jsonIteratorNext( _i ))) {
4805 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4807 jsonIteratorFree(_i);
4812 unsigned long res_idx = 0;
4813 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
4816 const char* link_field;
4818 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4820 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4822 osrfHash* kid_link = osrfHashGet(links, link_field);
4823 if (!kid_link) continue;
4825 osrfHash* field = osrfHashGet(fields, link_field);
4826 if (!field) continue;
4828 osrfHash* value_field = field;
4830 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4831 if (!kid_idl) continue;
4833 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4834 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4837 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4838 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4841 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4843 if (link_map->size > 0) {
4844 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4847 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4852 osrfHashGet(kid_link, "class"),
4859 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4860 osrfHashGet(kid_link, "field"),
4861 osrfHashGet(kid_link, "class"),
4862 osrfHashGet(kid_link, "key"),
4863 osrfHashGet(kid_link, "reltype")
4866 const char* search_key = jsonObjectGetString(
4869 atoi( osrfHashGet(value_field, "array_position") )
4874 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4878 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
4880 // construct WHERE clause
4881 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
4884 osrfHashGet(kid_link, "key"),
4885 jsonNewObject( search_key )
4888 // construct the rest of the query
4889 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
4890 jsonObjectSetKey( rest_of_query, "flesh",
4891 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4895 jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
4897 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
4898 jsonObjectSetKey( rest_of_query, "order_by",
4899 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
4903 if (jsonObjectGetKeyConst(query_hash, "select")) {
4904 jsonObjectSetKey( rest_of_query, "select",
4905 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
4909 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
4910 where_clause, rest_of_query, err);
4912 jsonObjectFree( where_clause );
4913 jsonObjectFree( rest_of_query );
4916 osrfStringArrayFree(link_fields);
4917 jsonObjectFree(res_list);
4918 jsonObjectFree(flesh_blob);
4922 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4924 jsonObject* X = NULL;
4925 if ( link_map->size > 0 && kids->size > 0 ) {
4927 kids = jsonNewObjectType(JSON_ARRAY);
4929 jsonObject* _k_node;
4930 unsigned long res_idx = 0;
4931 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
4937 (unsigned long)atoi(
4943 osrfHashGet(kid_link, "class")
4947 osrfStringArrayGetString( link_map, 0 )
4955 } // end while loop traversing X
4958 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4959 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4962 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4963 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4967 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4968 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4971 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4972 jsonObjectClone( kids )
4977 jsonObjectFree(kids);
4981 jsonObjectFree( kids );
4983 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4984 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4987 } // end while loop traversing res_list
4988 jsonObjectFree( flesh_blob );
4989 osrfStringArrayFree(link_fields);
4998 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
5000 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5002 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
5004 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
5007 if (!verifyObjectClass(ctx, target)) {
5012 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
5013 osrfAppSessionStatus(
5015 OSRF_STATUS_BADREQUEST,
5016 "osrfMethodException",
5018 "No active transaction -- required for UPDATE"
5024 // The following test is harmless but redundant. If a class is
5025 // readonly, we don't register an update method for it.
5026 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5027 osrfAppSessionStatus(
5029 OSRF_STATUS_BADREQUEST,
5030 "osrfMethodException",
5032 "Cannot UPDATE readonly class"
5038 dbhandle = writehandle;
5040 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
5042 // Set the last_xact_id
5043 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5045 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5046 trans_id, target->classname, index);
5047 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
5050 char* pkey = osrfHashGet(meta, "primarykey");
5051 osrfHash* fields = osrfHashGet(meta, "fields");
5053 char* id = oilsFMGetString( target, pkey );
5057 "%s updating %s object with %s = %s",
5059 osrfHashGet(meta, "fieldmapper"),
5064 growing_buffer* sql = buffer_init(128);
5065 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
5068 osrfHash* field_def = NULL;
5069 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5070 while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
5072 // Skip virtual fields, and the primary key
5073 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5076 const char* field_name = osrfHashIteratorKey( field_itr );
5077 if( ! strcmp( field_name, pkey ) )
5080 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5082 int value_is_numeric = 0; // boolean
5084 if (field_object && field_object->classname) {
5085 value = oilsFMGetString(
5087 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
5089 } else if( field_object && JSON_BOOL == field_object->type ) {
5090 if( jsonBoolIsTrue( field_object ) )
5091 value = strdup( "t" );
5093 value = strdup( "f" );
5095 value = jsonObjectToSimpleString( field_object );
5096 if( field_object && JSON_NUMBER == field_object->type )
5097 value_is_numeric = 1;
5100 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5101 osrfHashGet(meta, "fieldmapper"), field_name, value);
5103 if (!field_object || field_object->type == JSON_NULL) {
5104 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
5105 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5106 if (first) first = 0;
5107 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5108 buffer_fadd( sql, " %s = NULL", field_name );
5111 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5112 if (first) first = 0;
5113 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5115 const char* numtype = get_datatype( field_def );
5116 if ( !strncmp( numtype, "INT", 3 ) ) {
5117 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
5118 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
5119 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
5121 // Must really be intended as a string, so quote it
5122 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5123 buffer_fadd( sql, " %s = %s", field_name, value );
5125 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5126 osrfAppSessionStatus(
5128 OSRF_STATUS_INTERNALSERVERERROR,
5129 "osrfMethodException",
5131 "Error quoting string -- please see the error log for more details"
5135 osrfHashIteratorFree( field_itr );
5142 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5145 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5146 if (first) first = 0;
5147 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5148 buffer_fadd( sql, " %s = %s", field_name, value );
5151 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5152 osrfAppSessionStatus(
5154 OSRF_STATUS_INTERNALSERVERERROR,
5155 "osrfMethodException",
5157 "Error quoting string -- please see the error log for more details"
5161 osrfHashIteratorFree( field_itr );
5172 osrfHashIteratorFree( field_itr );
5174 jsonObject* obj = jsonNewObject(id);
5176 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5177 dbi_conn_quote_string(dbhandle, &id);
5179 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5181 char* query = buffer_release(sql);
5182 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
5184 dbi_result result = dbi_conn_query(dbhandle, query);
5188 jsonObjectFree(obj);
5189 obj = jsonNewObject(NULL);
5192 "%s ERROR updating %s object with %s = %s",
5194 osrfHashGet(meta, "fieldmapper"),
5205 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
5207 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5209 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
5210 osrfAppSessionStatus(
5212 OSRF_STATUS_BADREQUEST,
5213 "osrfMethodException",
5215 "No active transaction -- required for DELETE"
5221 // The following test is harmless but redundant. If a class is
5222 // readonly, we don't register a delete method for it.
5223 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5224 osrfAppSessionStatus(
5226 OSRF_STATUS_BADREQUEST,
5227 "osrfMethodException",
5229 "Cannot DELETE readonly class"
5235 dbhandle = writehandle;
5239 char* pkey = osrfHashGet(meta, "primarykey");
5247 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
5248 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5253 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5256 if (!verifyObjectPCRUD( ctx, NULL )) {
5261 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
5266 "%s deleting %s object with %s = %s",
5268 osrfHashGet(meta, "fieldmapper"),
5273 obj = jsonNewObject(id);
5275 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5276 dbi_conn_quote_string(writehandle, &id);
5278 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
5281 jsonObjectFree(obj);
5282 obj = jsonNewObject(NULL);
5285 "%s ERROR deleting %s object with %s = %s",
5287 osrfHashGet(meta, "fieldmapper"),
5300 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5301 if(!(result && meta)) return jsonNULL;
5303 jsonObject* object = jsonNewObject(NULL);
5304 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
5306 osrfHash* fields = osrfHashGet(meta, "fields");
5308 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
5312 char dt_string[256];
5316 int columnIndex = 1;
5318 unsigned short type;
5319 const char* columnName;
5321 /* cycle through the column list */
5322 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5324 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5326 fmIndex = -1; // reset the position
5328 /* determine the field type and storage attributes */
5329 type = dbi_result_get_field_type_idx(result, columnIndex);
5330 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5332 /* fetch the fieldmapper index */
5333 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
5335 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
5338 const char* pos = (char*)osrfHashGet(_f, "array_position");
5339 if ( !pos ) continue;
5341 fmIndex = atoi( pos );
5342 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
5347 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5348 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
5353 case DBI_TYPE_INTEGER :
5355 if( attr & DBI_INTEGER_SIZE8 )
5356 jsonObjectSetIndex( object, fmIndex,
5357 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
5359 jsonObjectSetIndex( object, fmIndex,
5360 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
5364 case DBI_TYPE_DECIMAL :
5365 jsonObjectSetIndex( object, fmIndex,
5366 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
5369 case DBI_TYPE_STRING :
5375 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
5380 case DBI_TYPE_DATETIME :
5382 memset(dt_string, '\0', sizeof(dt_string));
5383 memset(&gmdt, '\0', sizeof(gmdt));
5385 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5388 if (!(attr & DBI_DATETIME_DATE)) {
5389 gmtime_r( &_tmp_dt, &gmdt );
5390 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5391 } else if (!(attr & DBI_DATETIME_TIME)) {
5392 localtime_r( &_tmp_dt, &gmdt );
5393 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5395 localtime_r( &_tmp_dt, &gmdt );
5396 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5399 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
5403 case DBI_TYPE_BINARY :
5404 osrfLogError( OSRF_LOG_MARK,
5405 "Can't do binary at column %s : index %d", columnName, columnIndex);
5414 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5415 if(!result) return jsonNULL;
5417 jsonObject* object = jsonNewObject(NULL);
5420 char dt_string[256];
5424 int columnIndex = 1;
5426 unsigned short type;
5427 const char* columnName;
5429 /* cycle through the column list */
5430 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5432 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5434 fmIndex = -1; // reset the position
5436 /* determine the field type and storage attributes */
5437 type = dbi_result_get_field_type_idx(result, columnIndex);
5438 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5440 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5441 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
5446 case DBI_TYPE_INTEGER :
5448 if( attr & DBI_INTEGER_SIZE8 )
5449 jsonObjectSetKey( object, columnName,
5450 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)) );
5452 jsonObjectSetKey( object, columnName,
5453 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
5456 case DBI_TYPE_DECIMAL :
5457 jsonObjectSetKey( object, columnName,
5458 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
5461 case DBI_TYPE_STRING :
5462 jsonObjectSetKey( object, columnName,
5463 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
5466 case DBI_TYPE_DATETIME :
5468 memset(dt_string, '\0', sizeof(dt_string));
5469 memset(&gmdt, '\0', sizeof(gmdt));
5471 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5474 if (!(attr & DBI_DATETIME_DATE)) {
5475 gmtime_r( &_tmp_dt, &gmdt );
5476 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5477 } else if (!(attr & DBI_DATETIME_TIME)) {
5478 localtime_r( &_tmp_dt, &gmdt );
5479 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5481 localtime_r( &_tmp_dt, &gmdt );
5482 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5485 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
5488 case DBI_TYPE_BINARY :
5489 osrfLogError( OSRF_LOG_MARK,
5490 "Can't do binary at column %s : index %d", columnName, columnIndex );
5494 } // end while loop traversing result
5499 // Interpret a string as true or false
5500 static int str_is_true( const char* str ) {
5501 if( NULL == str || strcasecmp( str, "true" ) )
5507 // Interpret a jsonObject as true or false
5508 static int obj_is_true( const jsonObject* obj ) {
5511 else switch( obj->type )
5519 if( strcasecmp( obj->value.s, "true" ) )
5523 case JSON_NUMBER : // Support 1/0 for perl's sake
5524 if( jsonObjectGetNumber( obj ) == 1.0 )
5533 // Translate a numeric code into a text string identifying a type of
5534 // jsonObject. To be used for building error messages.
5535 static const char* json_type( int code ) {
5541 return "JSON_ARRAY";
5543 return "JSON_STRING";
5545 return "JSON_NUMBER";
5551 return "(unrecognized)";
5555 // Extract the "primitive" attribute from an IDL field definition.
5556 // If we haven't initialized the app, then we must be running in
5557 // some kind of testbed. In that case, default to "string".
5558 static const char* get_primitive( osrfHash* field ) {
5559 const char* s = osrfHashGet( field, "primitive" );
5561 if( child_initialized )
5564 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5566 osrfHashGet( field, "name" )
5574 // Extract the "datatype" attribute from an IDL field definition.
5575 // If we haven't initialized the app, then we must be running in
5576 // some kind of testbed. In that case, default to to NUMERIC,
5577 // since we look at the datatype only for numbers.
5578 static const char* get_datatype( osrfHash* field ) {
5579 const char* s = osrfHashGet( field, "datatype" );
5581 if( child_initialized )
5584 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5586 osrfHashGet( field, "name" )
5595 If the input string is potentially a valid SQL identifier, return 1.
5598 Purpose: to prevent certain kinds of SQL injection. To that end we
5599 don't necessarily need to follow all the rules exactly, such as requiring
5600 that the first character not be a digit.
5602 We allow leading and trailing white space. In between, we do not allow
5603 punctuation (except for underscores and dollar signs), control
5604 characters, or embedded white space.
5606 More pedantically we should allow quoted identifiers containing arbitrary
5607 characters, but for the foreseeable future such quoted identifiers are not
5608 likely to be an issue.
5610 static int is_identifier( const char* s) {
5614 // Skip leading white space
5615 while( isspace( (unsigned char) *s ) )
5619 return 0; // Nothing but white space? Not okay.
5621 // Check each character until we reach white space or
5622 // end-of-string. Letters, digits, underscores, and
5623 // dollar signs are okay. With the exception of periods
5624 // (as in schema.identifier), control characters and other
5625 // punctuation characters are not okay. Anything else
5626 // is okay -- it could for example be part of a multibyte
5627 // UTF8 character such as a letter with diacritical marks,
5628 // and those are allowed.
5630 if( isalnum( (unsigned char) *s )
5634 ; // Fine; keep going
5635 else if( ispunct( (unsigned char) *s )
5636 || iscntrl( (unsigned char) *s ) )
5639 } while( *s && ! isspace( (unsigned char) *s ) );
5641 // If we found any white space in the above loop,
5642 // the rest had better be all white space.
5644 while( isspace( (unsigned char) *s ) )
5648 return 0; // White space was embedded within non-white space
5654 Determine whether to accept a character string as a comparison operator.
5655 Return 1 if it's good, or 0 if it's bad.
5657 We don't validate it for real. We just make sure that it doesn't contain
5658 any semicolons or white space (with special exceptions for a few specific
5659 operators). The idea is to block certain kinds of SQL injection. If it
5660 has no semicolons or white space but it's still not a valid operator, then
5661 the database will complain.
5663 Another approach would be to compare the string against a short list of
5664 approved operators. We don't do that because we want to allow custom
5665 operators like ">100*", which would be difficult or impossible to
5666 express otherwise in a JSON query.
5668 static int is_good_operator( const char* op ) {
5669 if( !op ) return 0; // Sanity check
5673 if( isspace( (unsigned char) *s ) ) {
5674 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
5675 // and IS NOT DISTINCT FROM.
5676 if( !strcasecmp( op, "similar to" ) )
5678 else if( !strcasecmp( op, "is distinct from" ) )
5680 else if( !strcasecmp( op, "is not distinct from" ) )
5685 else if( ';' == *s )
5692 /* ----------------------------------------------------------------------------------
5693 The following machinery supports a stack of query frames for use by SELECT().
5695 A query frame caches information about one level of a SELECT query. When we enter
5696 a subquery, we push another query frame onto the stack, and pop it off when we leave.
5698 The query frame stores information about the core class, and about any joined classes
5701 The main purpose is to map table aliases to classes and tables, so that a query can
5702 join to the same table more than once. A secondary goal is to reduce the number of
5703 lookups in the IDL by caching the results.
5704 ----------------------------------------------------------------------------------*/
5706 #define STATIC_CLASS_INFO_COUNT 3
5708 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
5710 /* ---------------------------------------------------------------------------
5711 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5713 ---------------------------------------------------------------------------*/
5714 static ClassInfo* allocate_class_info( void ) {
5715 // In order to reduce the number of mallocs and frees, we return a static
5716 // instance of ClassInfo, if we can find one that we're not already using.
5717 // We rely on the fact that the compiler will implicitly initialize the
5718 // static instances so that in_use == 0.
5721 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5722 if( ! static_class_info[ i ].in_use ) {
5723 static_class_info[ i ].in_use = 1;
5724 return static_class_info + i;
5728 // The static ones are all in use. Malloc one.
5730 return safe_malloc( sizeof( ClassInfo ) );
5733 /* --------------------------------------------------------------------------
5734 Free any malloc'd memory owned by a ClassInfo; return it to a pristine state
5735 ---------------------------------------------------------------------------*/
5736 static void clear_class_info( ClassInfo* info ) {
5741 // Free any malloc'd strings
5743 if( info->alias != info->alias_store )
5744 free( info->alias );
5746 if( info->class_name != info->class_name_store )
5747 free( info->class_name );
5749 free( info->source_def );
5751 info->alias = info->class_name = info->source_def = NULL;
5755 /* --------------------------------------------------------------------------
5756 Deallocate a ClassInfo and everything it owns
5757 ---------------------------------------------------------------------------*/
5758 static void free_class_info( ClassInfo* info ) {
5763 clear_class_info( info );
5765 // If it's one of the static instances, just mark it as not in use
5768 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5769 if( info == static_class_info + i ) {
5770 static_class_info[ i ].in_use = 0;
5775 // Otherwise it must have been malloc'd, so free it
5780 /* --------------------------------------------------------------------------
5781 Populate an already-allocated ClassInfo. Return 0 if successful, 1 if not.
5782 ---------------------------------------------------------------------------*/
5783 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
5786 osrfLogError( OSRF_LOG_MARK,
5787 "%s ERROR: No ClassInfo available to populate", MODULENAME );
5788 info->alias = info->class_name = info->source_def = NULL;
5789 info->class_def = info->fields = info->links = NULL;
5794 osrfLogError( OSRF_LOG_MARK,
5795 "%s ERROR: No class name provided for lookup", MODULENAME );
5796 info->alias = info->class_name = info->source_def = NULL;
5797 info->class_def = info->fields = info->links = NULL;
5801 // Alias defaults to class name if not supplied
5802 if( ! alias || ! alias[ 0 ] )
5805 // Look up class info in the IDL
5806 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
5808 osrfLogError( OSRF_LOG_MARK,
5809 "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
5810 info->alias = info->class_name = info->source_def = NULL;
5811 info->class_def = info->fields = info->links = NULL;
5813 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
5814 osrfLogError( OSRF_LOG_MARK,
5815 "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
5816 info->alias = info->class_name = info->source_def = NULL;
5817 info->class_def = info->fields = info->links = NULL;
5821 osrfHash* links = osrfHashGet( class_def, "links" );
5823 osrfLogError( OSRF_LOG_MARK,
5824 "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
5825 info->alias = info->class_name = info->source_def = NULL;
5826 info->class_def = info->fields = info->links = NULL;
5830 osrfHash* fields = osrfHashGet( class_def, "fields" );
5832 osrfLogError( OSRF_LOG_MARK,
5833 "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
5834 info->alias = info->class_name = info->source_def = NULL;
5835 info->class_def = info->fields = info->links = NULL;
5839 char* source_def = getSourceDefinition( class_def );
5843 // We got everything we need, so populate the ClassInfo
5844 if( strlen( alias ) > ALIAS_STORE_SIZE )
5845 info->alias = strdup( alias );
5847 strcpy( info->alias_store, alias );
5848 info->alias = info->alias_store;
5851 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
5852 info->class_name = strdup( class );
5854 strcpy( info->class_name_store, class );
5855 info->class_name = info->class_name_store;
5858 info->source_def = source_def;
5860 info->class_def = class_def;
5861 info->links = links;
5862 info->fields = fields;
5867 #define STATIC_FRAME_COUNT 3
5869 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
5871 /* ---------------------------------------------------------------------------
5872 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5874 ---------------------------------------------------------------------------*/
5875 static QueryFrame* allocate_frame( void ) {
5876 // In order to reduce the number of mallocs and frees, we return a static
5877 // instance of QueryFrame, if we can find one that we're not already using.
5878 // We rely on the fact that the compiler will implicitly initialize the
5879 // static instances so that in_use == 0.
5882 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5883 if( ! static_frame[ i ].in_use ) {
5884 static_frame[ i ].in_use = 1;
5885 return static_frame + i;
5889 // The static ones are all in use. Malloc one.
5891 return safe_malloc( sizeof( QueryFrame ) );
5894 /* --------------------------------------------------------------------------
5895 Free a QueryFrame, and all the memory it owns.
5896 ---------------------------------------------------------------------------*/
5897 static void free_query_frame( QueryFrame* frame ) {
5902 clear_class_info( &frame->core );
5904 // Free the join list
5906 ClassInfo* info = frame->join_list;
5909 free_class_info( info );
5913 frame->join_list = NULL;
5916 // If the frame is a static instance, just mark it as unused
5918 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5919 if( frame == static_frame + i ) {
5920 static_frame[ i ].in_use = 0;
5925 // Otherwise it must have been malloc'd, so free it
5930 /* --------------------------------------------------------------------------
5931 Search a given QueryFrame for a specified alias. If you find it, return
5932 a pointer to the corresponding ClassInfo. Otherwise return NULL.
5933 ---------------------------------------------------------------------------*/
5934 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
5935 if( ! frame || ! target ) {
5939 ClassInfo* found_class = NULL;
5941 if( !strcmp( target, frame->core.alias ) )
5942 return &(frame->core);
5944 ClassInfo* curr_class = frame->join_list;
5945 while( curr_class ) {
5946 if( strcmp( target, curr_class->alias ) )
5947 curr_class = curr_class->next;
5949 found_class = curr_class;
5958 /* --------------------------------------------------------------------------
5959 Push a new (blank) QueryFrame onto the stack.
5960 ---------------------------------------------------------------------------*/
5961 static void push_query_frame( void ) {
5962 QueryFrame* frame = allocate_frame();
5963 frame->join_list = NULL;
5964 frame->next = curr_query;
5966 // Initialize the ClassInfo for the core class
5967 ClassInfo* core = &frame->core;
5968 core->alias = core->class_name = core->source_def = NULL;
5969 core->class_def = core->fields = core->links = NULL;
5974 /* --------------------------------------------------------------------------
5975 Pop a QueryFrame off the stack and destroy it
5976 ---------------------------------------------------------------------------*/
5977 static void pop_query_frame( void ) {
5982 QueryFrame* popped = curr_query;
5983 curr_query = popped->next;
5985 free_query_frame( popped );
5988 /* --------------------------------------------------------------------------
5989 Populate the ClassInfo for the core class. Return 0 if successful, 1 if not.
5990 ---------------------------------------------------------------------------*/
5991 static int add_query_core( const char* alias, const char* class_name ) {
5994 if( ! curr_query ) {
5995 osrfLogError( OSRF_LOG_MARK,
5996 "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
5998 } else if( curr_query->core.alias ) {
5999 osrfLogError( OSRF_LOG_MARK,
6000 "%s ERROR: Core class %s already populated as %s",
6001 MODULENAME, curr_query->core.class_name, curr_query->core.alias );
6005 build_class_info( &curr_query->core, alias, class_name );
6006 if( curr_query->core.alias )
6009 osrfLogError( OSRF_LOG_MARK,
6010 "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
6015 /* --------------------------------------------------------------------------
6016 Search the current QueryFrame for a specified alias. If you find it,
6017 return a pointer to the corresponding ClassInfo. Otherwise return NULL.
6018 ---------------------------------------------------------------------------*/
6019 static ClassInfo* search_alias( const char* target ) {
6020 return search_alias_in_frame( curr_query, target );
6023 /* --------------------------------------------------------------------------
6024 Search all levels of query for a specified alias, starting with the
6025 current query. If you find it, return a pointer to the corresponding
6026 ClassInfo. Otherwise return NULL.
6027 ---------------------------------------------------------------------------*/
6028 static ClassInfo* search_all_alias( const char* target ) {
6029 ClassInfo* found_class = NULL;
6030 QueryFrame* curr_frame = curr_query;
6032 while( curr_frame ) {
6033 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6036 curr_frame = curr_frame->next;
6042 /* --------------------------------------------------------------------------
6043 Add a class to the list of classes joined to the current query.
6044 ---------------------------------------------------------------------------*/
6045 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6047 if( ! classname || ! *classname ) { // sanity check
6048 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6055 const ClassInfo* conflict = search_alias( alias );
6057 osrfLogError( OSRF_LOG_MARK,
6058 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6059 MODULENAME, alias, conflict->class_name );
6063 ClassInfo* info = allocate_class_info();
6065 if( build_class_info( info, alias, classname ) ) {
6066 free_class_info( info );
6070 // Add the new ClassInfo to the join list of the current QueryFrame
6071 info->next = curr_query->join_list;
6072 curr_query->join_list = info;
6077 /* --------------------------------------------------------------------------
6078 Destroy all nodes on the query stack.
6079 ---------------------------------------------------------------------------*/
6080 static void clear_query_stack( void ) {