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, or to a subquery defined
191 in the IDL. Ignore all virtual 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 );
421 @brief Get a table name, view name, or subquery for use in a FROM clause.
422 @param class Pointer to the IDL class entry.
423 @return A table name, a view name, or a subquery in parentheses.
425 In some cases the IDL defines a class, not with a table name or a view name, but with
426 a SELECT statement, which may be used as a subquery.
428 static char* getSourceDefinition( osrfHash* class ) {
430 char* tabledef = osrfHashGet(class, "tablename");
433 tabledef = strdup(tabledef);
435 tabledef = osrfHashGet(class, "source_definition");
437 growing_buffer* tablebuf = buffer_init(128);
438 buffer_fadd( tablebuf, "(%s)", tabledef );
439 tabledef = buffer_release(tablebuf);
441 const char* classname = osrfHashGet( class, "classname" );
446 "%s ERROR No tablename or source_definition for class \"%s\"",
457 @brief Initialize a server drone.
458 @return Zero if successful, -1 if not.
460 Connect to the database. For each non-virtual class in the IDL, execute a dummy "SELECT * "
461 query to get the datatype of each column. Record the datatypes in the loaded IDL.
463 This function is called by a server drone shortly after it is spawned by the listener.
465 int osrfAppChildInit() {
467 osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
468 dbi_initialize(NULL);
469 osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
471 char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
472 char* user = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
473 char* host = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
474 char* port = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
475 char* db = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
476 char* pw = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
477 char* md = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion",
480 osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
481 writehandle = dbi_conn_new(driver);
484 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
487 osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
489 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
490 "port=%s, user=%s, pw=%s, db=%s", MODULENAME, host, port, user, pw, db );
492 if(host) dbi_conn_set_option(writehandle, "host", host );
493 if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
494 if(user) dbi_conn_set_option(writehandle, "username", user);
495 if(pw) dbi_conn_set_option(writehandle, "password", pw );
496 if(db) dbi_conn_set_option(writehandle, "dbname", db );
498 if(md) max_flesh_depth = atoi(md);
499 if(max_flesh_depth < 0) max_flesh_depth = 1;
500 if(max_flesh_depth > 1000) max_flesh_depth = 1000;
509 if (dbi_conn_connect(writehandle) < 0) {
511 if (dbi_conn_connect(writehandle) < 0) {
512 dbi_conn_error(writehandle, &err);
513 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
518 osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
520 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
521 osrfHash* class = NULL;
523 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
524 const char* classname = osrfHashIteratorKey( class_itr );
525 osrfHash* fields = osrfHashGet( class, "fields" );
527 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
528 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
532 char* tabledef = getSourceDefinition(class);
534 tabledef = strdup( "(null)" );
536 growing_buffer* sql_buf = buffer_init(32);
537 buffer_fadd( sql_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
541 char* sql = buffer_release(sql_buf);
542 osrfLogDebug(OSRF_LOG_MARK, "%s Investigatory SQL = %s", MODULENAME, sql);
544 dbi_result result = dbi_conn_query(writehandle, sql);
550 const char* columnName;
552 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
554 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
555 (char*) columnName );
557 /* fetch the fieldmapper index */
558 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
560 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", (char*)columnName);
562 /* determine the field type and storage attributes */
564 switch( dbi_result_get_field_type_idx(result, columnIndex) ) {
566 case DBI_TYPE_INTEGER : {
568 if ( !osrfHashGet(_f, "primitive") )
569 osrfHashSet(_f, "number", "primitive");
571 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
572 if( attr & DBI_INTEGER_SIZE8 )
573 osrfHashSet(_f, "INT8", "datatype");
575 osrfHashSet(_f, "INT", "datatype");
578 case DBI_TYPE_DECIMAL :
579 if ( !osrfHashGet(_f, "primitive") )
580 osrfHashSet(_f, "number", "primitive");
582 osrfHashSet(_f,"NUMERIC", "datatype");
585 case DBI_TYPE_STRING :
586 if ( !osrfHashGet(_f, "primitive") )
587 osrfHashSet(_f,"string", "primitive");
589 osrfHashSet(_f,"TEXT", "datatype");
592 case DBI_TYPE_DATETIME :
593 if ( !osrfHashGet(_f, "primitive") )
594 osrfHashSet(_f,"string", "primitive");
596 osrfHashSet(_f,"TIMESTAMP", "datatype");
599 case DBI_TYPE_BINARY :
600 if ( !osrfHashGet(_f, "primitive") )
601 osrfHashSet(_f,"string", "primitive");
603 osrfHashSet(_f,"BYTEA", "datatype");
608 "Setting [%s] to primitive [%s] and datatype [%s]...",
610 osrfHashGet(_f, "primitive"),
611 osrfHashGet(_f, "datatype")
615 } // end while loop for traversing result
616 dbi_result_free(result);
618 osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", (char*)classname);
620 } // end for each class in IDL
622 osrfHashIteratorFree( class_itr );
623 child_initialized = 1;
628 This function is a sleazy hack intended *only* for testing and
629 debugging. Any real server process should initialize the
630 database connection by calling osrfAppChildInit().
632 void set_cstore_dbi_conn( dbi_conn conn ) {
633 dbhandle = writehandle = conn;
636 void userDataFree( void* blob ) {
637 osrfHashFree( (osrfHash*)blob );
641 static void sessionDataFree( char* key, void* item ) {
642 if (!(strcmp(key,"xact_id"))) {
644 dbi_conn_query(writehandle, "ROLLBACK;");
651 int beginTransaction ( osrfMethodContext* ctx ) {
652 if(osrfMethodVerifyContext( ctx )) {
653 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
658 jsonObject* user = verifyUserPCRUD( ctx );
661 jsonObjectFree(user);
664 dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
666 osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
667 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error starting transaction" );
670 jsonObject* ret = jsonNewObject(ctx->session->session_id);
671 osrfAppRespondComplete( ctx, ret );
674 if (!ctx->session->userData) {
675 ctx->session->userData = osrfNewHash();
676 osrfHashSetCallback((osrfHash*)ctx->session->userData, &sessionDataFree);
679 osrfHashSet( (osrfHash*)ctx->session->userData, strdup( ctx->session->session_id ), "xact_id" );
680 ctx->session->userDataFree = &userDataFree;
686 int setSavepoint ( osrfMethodContext* ctx ) {
687 if(osrfMethodVerifyContext( ctx )) {
688 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
695 jsonObject* user = verifyUserPCRUD( ctx );
698 jsonObjectFree(user);
701 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
702 osrfAppSessionStatus(
704 OSRF_STATUS_INTERNALSERVERERROR,
705 "osrfMethodException",
707 "No active transaction -- required for savepoints"
712 const char* spName = jsonObjectGetString(jsonObjectGetIndex(ctx->params, spNamePos));
714 dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
718 "%s: Error creating savepoint %s in transaction %s",
721 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
723 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
724 "osrfMethodException", ctx->request, "Error creating savepoint" );
727 jsonObject* ret = jsonNewObject(spName);
728 osrfAppRespondComplete( ctx, ret );
734 int releaseSavepoint ( osrfMethodContext* ctx ) {
735 if(osrfMethodVerifyContext( ctx )) {
736 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
743 jsonObject* user = verifyUserPCRUD( ctx );
746 jsonObjectFree(user);
749 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
750 osrfAppSessionStatus(
752 OSRF_STATUS_INTERNALSERVERERROR,
753 "osrfMethodException",
755 "No active transaction -- required for savepoints"
760 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
762 dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
766 "%s: Error releasing savepoint %s in transaction %s",
769 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
771 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
772 "osrfMethodException", ctx->request, "Error releasing savepoint" );
775 jsonObject* ret = jsonNewObject(spName);
776 osrfAppRespondComplete( ctx, ret );
782 int rollbackSavepoint ( osrfMethodContext* ctx ) {
783 if(osrfMethodVerifyContext( ctx )) {
784 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
791 jsonObject* user = verifyUserPCRUD( ctx );
794 jsonObjectFree(user);
797 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
798 osrfAppSessionStatus(
800 OSRF_STATUS_INTERNALSERVERERROR,
801 "osrfMethodException",
803 "No active transaction -- required for savepoints"
808 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
810 dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
814 "%s: Error rolling back savepoint %s in transaction %s",
817 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
819 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
820 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
823 jsonObject* ret = jsonNewObject(spName);
824 osrfAppRespondComplete( ctx, ret );
830 int commitTransaction ( osrfMethodContext* ctx ) {
831 if(osrfMethodVerifyContext( ctx )) {
832 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
837 jsonObject* user = verifyUserPCRUD( ctx );
840 jsonObjectFree(user);
843 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
844 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
845 "osrfMethodException", ctx->request, "No active transaction to commit" );
849 dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
851 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
852 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
853 "osrfMethodException", ctx->request, "Error committing transaction" );
856 osrfHashRemove(ctx->session->userData, "xact_id");
857 jsonObject* ret = jsonNewObject(ctx->session->session_id);
858 osrfAppRespondComplete( ctx, ret );
864 int rollbackTransaction ( osrfMethodContext* ctx ) {
865 if(osrfMethodVerifyContext( ctx )) {
866 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
871 jsonObject* user = verifyUserPCRUD( ctx );
874 jsonObjectFree(user);
877 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
878 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
879 "osrfMethodException", ctx->request, "No active transaction to roll back" );
883 dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
885 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
886 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
887 "osrfMethodException", ctx->request, "Error rolling back transaction" );
890 osrfHashRemove(ctx->session->userData, "xact_id");
891 jsonObject* ret = jsonNewObject(ctx->session->session_id);
892 osrfAppRespondComplete( ctx, ret );
898 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
899 if(osrfMethodVerifyContext( ctx )) {
900 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
904 osrfHash* meta = (osrfHash*) ctx->method->userData;
905 osrfHash* class_obj = osrfHashGet( meta, "class" );
909 const char* methodtype = osrfHashGet(meta, "methodtype");
910 jsonObject * obj = NULL;
912 if (!strcmp(methodtype, "create")) {
913 obj = doCreate(ctx, &err);
914 osrfAppRespondComplete( ctx, obj );
916 else if (!strcmp(methodtype, "retrieve")) {
917 obj = doRetrieve(ctx, &err);
918 osrfAppRespondComplete( ctx, obj );
920 else if (!strcmp(methodtype, "update")) {
921 obj = doUpdate(ctx, &err);
922 osrfAppRespondComplete( ctx, obj );
924 else if (!strcmp(methodtype, "delete")) {
925 obj = doDelete(ctx, &err);
926 osrfAppRespondComplete( ctx, obj );
928 else if (!strcmp(methodtype, "search")) {
930 jsonObject* where_clause;
931 jsonObject* rest_of_query;
934 where_clause = jsonObjectGetIndex( ctx->params, 1 );
935 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
937 where_clause = jsonObjectGetIndex( ctx->params, 0 );
938 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
941 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
946 unsigned long res_idx = 0;
947 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
949 if(!verifyObjectPCRUD(ctx, cur)) continue;
951 osrfAppRespond( ctx, cur );
953 osrfAppRespondComplete( ctx, NULL );
955 } else if (!strcmp(methodtype, "id_list")) {
957 jsonObject* where_clause;
958 jsonObject* rest_of_query;
960 // We use the where clause without change. But we need
961 // to massage the rest of the query, so we work with a copy
962 // of it instead of modifying the original.
964 where_clause = jsonObjectGetIndex( ctx->params, 1 );
965 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
967 where_clause = jsonObjectGetIndex( ctx->params, 0 );
968 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
971 if ( rest_of_query ) {
972 jsonObjectRemoveKey( rest_of_query, "select" );
973 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
974 jsonObjectRemoveKey( rest_of_query, "flesh" );
975 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
977 rest_of_query = jsonNewObjectType( JSON_HASH );
980 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
982 // Build a SELECT list containing just the primary key,
983 // i.e. like { "classname":["keyname"] }
984 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
985 jsonObjectPush( col_list_obj, // Load array with name of primary key
986 jsonNewObject( osrfHashGet( class_obj, "primarykey" ) ) );
987 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
988 jsonObjectSetKey( select_clause, osrfHashGet( class_obj, "classname" ), col_list_obj );
990 jsonObjectSetKey( rest_of_query, "select", select_clause );
992 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
994 jsonObjectFree( rest_of_query );
998 unsigned long res_idx = 0;
999 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1001 if(!verifyObjectPCRUD(ctx, cur)) continue;
1005 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
1008 osrfAppRespondComplete( ctx, NULL );
1011 osrfAppRespondComplete( ctx, obj );
1014 jsonObjectFree(obj);
1019 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1022 osrfHash* meta = (osrfHash*) ctx->method->userData;
1023 osrfHash* class = osrfHashGet( meta, "class" );
1025 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1027 const char* temp_classname = param->classname;
1028 if( ! temp_classname )
1029 temp_classname = "(null)";
1031 growing_buffer* msg = buffer_init(128);
1034 "%s: %s method for type %s was passed a %s",
1036 osrfHashGet(meta, "methodtype"),
1037 osrfHashGet(class, "classname"),
1041 char* m = buffer_release(msg);
1042 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1051 ret = verifyObjectPCRUD( ctx, param );
1059 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1060 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1061 jsonObject* auth_object = jsonNewObject(auth);
1062 jsonObject* user = oilsUtilsQuickReq("open-ils.auth","open-ils.auth.session.retrieve",
1064 jsonObjectFree(auth_object);
1066 if (!user->classname || strcmp(user->classname, "au")) {
1068 growing_buffer* msg = buffer_init(128);
1071 "%s: permacrud received a bad auth token: %s",
1076 char* m = buffer_release(msg);
1077 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1081 jsonObjectFree(user);
1089 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1091 dbhandle = writehandle;
1093 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1094 osrfHash* class = osrfHashGet( method_metadata, "class" );
1095 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1098 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
1099 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1100 } else if ( *method_type == 'u' || *method_type == 'd' ) {
1101 fetch = 1; // MUST go to the db for the object for update and delete
1104 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
1107 // No permacrud for this method type on this class
1109 growing_buffer* msg = buffer_init(128);
1112 "%s: %s on class %s has no permacrud IDL entry",
1114 osrfHashGet(method_metadata, "methodtype"),
1115 osrfHashGet(class, "classname")
1118 char* m = buffer_release(msg);
1119 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN, "osrfMethodException", ctx->request, m );
1126 jsonObject* user = verifyUserPCRUD( ctx );
1130 int userid = atoi( oilsFMGetString( user, "id" ) );
1131 jsonObjectFree(user);
1133 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
1134 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
1135 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
1137 osrfStringArray* context_org_array = osrfNewStringArray(1);
1140 char* pkey_value = NULL;
1141 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1142 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions required, fetching top of the org tree" );
1144 // check for perm at top of org tree
1145 char* org_tree_root_id = org_tree_root( ctx );
1146 if( org_tree_root_id ) {
1147 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1148 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1150 osrfStringArrayFree( context_org_array );
1155 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, fetching context org ids" );
1156 const char* pkey = osrfHashGet(class, "primarykey");
1157 jsonObject *param = NULL;
1159 if (obj->classname) {
1160 pkey_value = oilsFMGetString( obj, pkey );
1161 if (!fetch) param = jsonObjectClone(obj);
1162 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s", pkey_value );
1164 pkey_value = jsonObjectToSimpleString( obj );
1166 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value of %s and retrieving from the database", pkey_value );
1170 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1171 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1172 jsonObjectFree(_tmp_params);
1174 param = jsonObjectExtractIndex(_list, 0);
1175 jsonObjectFree(_list);
1179 osrfLogDebug( OSRF_LOG_MARK, "Object not found in the database with primary key %s of %s", pkey, pkey_value );
1181 growing_buffer* msg = buffer_init(128);
1184 "%s: no object found with primary key %s of %s",
1190 char* m = buffer_release(msg);
1191 osrfAppSessionStatus(
1193 OSRF_STATUS_INTERNALSERVERERROR,
1194 "osrfMethodException",
1200 if (pkey_value) free(pkey_value);
1205 if (local_context->size > 0) {
1206 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified", local_context->size);
1208 const char* lcontext = NULL;
1209 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1210 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1213 "adding class-local field %s (value: %s) to the context org list",
1215 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1221 if (foreign_context) {
1222 unsigned long class_count = osrfHashGetCount( foreign_context );
1223 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count);
1225 if (class_count > 0) {
1227 osrfHash* fcontext = NULL;
1228 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1229 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1230 const char* class_name = osrfHashIteratorKey( class_itr );
1231 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1235 "%d foreign context fields(s) specified for class %s",
1236 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1240 char* foreign_pkey = osrfHashGet(fcontext, "field");
1241 char* foreign_pkey_value = oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1243 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1245 jsonObject* _list = doFieldmapperSearch(
1246 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1248 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1249 jsonObjectFree(_tmp_params);
1250 jsonObjectFree(_list);
1252 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1254 if (_fparam && jump_list) {
1255 const char* flink = NULL;
1257 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1258 free(foreign_pkey_value);
1260 osrfHash* foreign_link_hash = oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1262 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1263 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1265 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1267 _list = doFieldmapperSearch(
1269 osrfHashGet( oilsIDL(), osrfHashGet( foreign_link_hash, "class" ) ),
1275 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1276 jsonObjectFree(_tmp_params);
1277 jsonObjectFree(_list);
1283 growing_buffer* msg = buffer_init(128);
1286 "%s: no object found with primary key %s of %s",
1292 char* m = buffer_release(msg);
1293 osrfAppSessionStatus(
1295 OSRF_STATUS_INTERNALSERVERERROR,
1296 "osrfMethodException",
1302 osrfHashIteratorFree(class_itr);
1303 free(foreign_pkey_value);
1304 jsonObjectFree(param);
1309 free(foreign_pkey_value);
1312 const char* foreign_field = NULL;
1313 while ( (foreign_field = osrfStringArrayGetString(osrfHashGet(fcontext,"context"), j++)) ) {
1314 osrfStringArrayAdd( context_org_array, oilsFMGetString( _fparam, foreign_field ) );
1317 "adding foreign class %s field %s (value: %s) to the context org list",
1320 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1324 jsonObjectFree(_fparam);
1327 osrfHashIteratorFree( class_itr );
1331 jsonObjectFree(param);
1334 const char* context_org = NULL;
1335 const char* perm = NULL;
1338 if (permission->size == 0) {
1339 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1344 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1346 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1352 "Checking object permission [%s] for user %d on object %s (class %s) at org %d",
1356 osrfHashGet(class, "classname"),
1360 result = dbi_conn_queryf(
1362 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1365 osrfHashGet(class, "classname"),
1373 "Received a result for object permission [%s] for user %d on object %s (class %s) at org %d",
1377 osrfHashGet(class, "classname"),
1381 if (dbi_result_first_row(result)) {
1382 jsonObject* return_val = oilsMakeJSONFromResult( result );
1383 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1387 "Status of object permission [%s] for user %d on object %s (class %s) at org %d is %s",
1391 osrfHashGet(class, "classname"),
1396 if ( *has_perm == 't' ) OK = 1;
1397 jsonObjectFree(return_val);
1400 dbi_result_free(result);
1405 osrfLogDebug( OSRF_LOG_MARK, "Checking non-object permission [%s] for user %d at org %d", perm, userid, atoi(context_org) );
1406 result = dbi_conn_queryf(
1408 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1415 osrfLogDebug( OSRF_LOG_MARK, "Received a result for permission [%s] for user %d at org %d",
1416 perm, userid, atoi(context_org) );
1417 if ( dbi_result_first_row(result) ) {
1418 jsonObject* return_val = oilsMakeJSONFromResult( result );
1419 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1420 osrfLogDebug( OSRF_LOG_MARK, "Status of permission [%s] for user %d at org %d is [%s]",
1421 perm, userid, atoi(context_org), has_perm );
1422 if ( *has_perm == 't' ) OK = 1;
1423 jsonObjectFree(return_val);
1426 dbi_result_free(result);
1434 if (pkey_value) free(pkey_value);
1435 osrfStringArrayFree(context_org_array);
1441 * Look up the root of the org_unit tree. If you find it, return
1442 * a string containing the id, which the caller is responsible for freeing.
1443 * Otherwise return NULL.
1445 static char* org_tree_root( osrfMethodContext* ctx ) {
1447 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1448 static time_t last_lookup_time = 0;
1449 time_t current_time = time( NULL );
1451 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1452 // We successfully looked this up less than an hour ago.
1453 // It's not likely to have changed since then.
1454 return strdup( cached_root_id );
1456 last_lookup_time = current_time;
1459 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1460 jsonObject* result = doFieldmapperSearch(
1461 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1462 jsonObjectFree( where_clause );
1464 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1467 jsonObjectFree( result );
1469 growing_buffer* msg = buffer_init(128);
1470 OSRF_BUFFER_ADD( msg, MODULENAME );
1471 OSRF_BUFFER_ADD( msg,
1472 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1474 char* m = buffer_release(msg);
1475 osrfAppSessionStatus( ctx->session,
1476 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1479 cached_root_id[ 0 ] = '\0';
1483 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1484 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1486 jsonObjectFree( result );
1488 strcpy( cached_root_id, root_org_unit_id );
1489 return root_org_unit_id;
1493 Utility function: create a JSON_HASH with a single key/value pair.
1494 This function is equivalent to:
1496 jsonParseFmt( "{\"%s\":\"%s\"}", key, value )
1498 or, if value is NULL:
1500 jsonParseFmt( "{\"%s\":null}", key )
1502 ...but faster because it doesn't create and parse a JSON string.
1504 static jsonObject* single_hash( const char* key, const char* value ) {
1506 if( ! key ) key = "";
1508 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1509 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1515 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1517 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1519 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1520 jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1522 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1523 jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1526 if (!verifyObjectClass(ctx, target)) {
1531 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1533 char* trans_id = NULL;
1534 if( ctx->session && ctx->session->userData )
1535 trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
1538 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1540 osrfAppSessionStatus(
1542 OSRF_STATUS_BADREQUEST,
1543 "osrfMethodException",
1545 "No active transaction -- required for CREATE"
1551 // The following test is harmless but redundant. If a class is
1552 // readonly, we don't register a create method for it.
1553 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1554 osrfAppSessionStatus(
1556 OSRF_STATUS_BADREQUEST,
1557 "osrfMethodException",
1559 "Cannot INSERT readonly class"
1565 // Set the last_xact_id
1566 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1568 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
1569 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1572 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1574 dbhandle = writehandle;
1576 osrfHash* fields = osrfHashGet(meta, "fields");
1577 char* pkey = osrfHashGet(meta, "primarykey");
1578 char* seq = osrfHashGet(meta, "sequence");
1580 growing_buffer* table_buf = buffer_init(128);
1581 growing_buffer* col_buf = buffer_init(128);
1582 growing_buffer* val_buf = buffer_init(128);
1584 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1585 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1586 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1587 buffer_add(val_buf,"VALUES (");
1591 osrfHash* field = NULL;
1592 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1593 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1595 const char* field_name = osrfHashIteratorKey( field_itr );
1597 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1600 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1603 if (field_object && field_object->classname) {
1604 value = oilsFMGetString(
1606 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1608 } else if( field_object && JSON_BOOL == field_object->type ) {
1609 if( jsonBoolIsTrue( field_object ) )
1610 value = strdup( "t" );
1612 value = strdup( "f" );
1614 value = jsonObjectToSimpleString( field_object );
1620 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1621 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1624 buffer_add(col_buf, field_name);
1626 if (!field_object || field_object->type == JSON_NULL) {
1627 buffer_add( val_buf, "DEFAULT" );
1629 } else if ( !strcmp(get_primitive( field ), "number") ) {
1630 const char* numtype = get_datatype( field );
1631 if ( !strcmp( numtype, "INT8") ) {
1632 buffer_fadd( val_buf, "%lld", atoll(value) );
1634 } else if ( !strcmp( numtype, "INT") ) {
1635 buffer_fadd( val_buf, "%d", atoi(value) );
1637 } else if ( !strcmp( numtype, "NUMERIC") ) {
1638 buffer_fadd( val_buf, "%f", atof(value) );
1641 if ( dbi_conn_quote_string(writehandle, &value) ) {
1642 OSRF_BUFFER_ADD( val_buf, value );
1645 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1646 osrfAppSessionStatus(
1648 OSRF_STATUS_INTERNALSERVERERROR,
1649 "osrfMethodException",
1651 "Error quoting string -- please see the error log for more details"
1654 buffer_free(table_buf);
1655 buffer_free(col_buf);
1656 buffer_free(val_buf);
1666 osrfHashIteratorFree( field_itr );
1668 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1669 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1671 char* table_str = buffer_release(table_buf);
1672 char* col_str = buffer_release(col_buf);
1673 char* val_str = buffer_release(val_buf);
1674 growing_buffer* sql = buffer_init(128);
1675 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1680 char* query = buffer_release(sql);
1682 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1685 dbi_result result = dbi_conn_query(writehandle, query);
1687 jsonObject* obj = NULL;
1690 obj = jsonNewObject(NULL);
1693 "%s ERROR inserting %s object using query [%s]",
1695 osrfHashGet(meta, "fieldmapper"),
1698 osrfAppSessionStatus(
1700 OSRF_STATUS_INTERNALSERVERERROR,
1701 "osrfMethodException",
1703 "INSERT error -- please see the error log for more details"
1708 char* id = oilsFMGetString(target, pkey);
1710 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1711 growing_buffer* _id = buffer_init(10);
1712 buffer_fadd(_id, "%lld", new_id);
1713 id = buffer_release(_id);
1716 // Find quietness specification, if present
1717 const char* quiet_str = NULL;
1719 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1721 quiet_str = jsonObjectGetString( quiet_obj );
1724 if( str_is_true( quiet_str ) ) { // if quietness is specified
1725 obj = jsonNewObject(id);
1729 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1730 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1732 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1734 jsonObjectFree( where_clause );
1739 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1742 jsonObjectFree( list );
1755 * Fetch one row from a specified table, using a specified value
1756 * for the primary key
1758 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1768 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1770 const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos); // key value
1774 "%s retrieving %s object with primary key value of %s",
1776 osrfHashGet( class_def, "fieldmapper" ),
1777 jsonObjectGetString( id_obj )
1780 // Build a WHERE clause based on the key value
1781 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1784 osrfHashGet( class_def, "primarykey" ),
1785 jsonObjectClone( id_obj )
1788 jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
1790 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
1792 jsonObjectFree( where_clause );
1796 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
1797 jsonObjectFree( list );
1800 if(!verifyObjectPCRUD(ctx, obj)) {
1801 jsonObjectFree(obj);
1804 growing_buffer* msg = buffer_init(128);
1805 OSRF_BUFFER_ADD( msg, MODULENAME );
1806 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1808 char* m = buffer_release(msg);
1809 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1820 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1821 growing_buffer* val_buf = buffer_init(32);
1822 const char* numtype = get_datatype( field );
1824 if ( !strncmp( numtype, "INT", 3 ) ) {
1825 if (value->type == JSON_NUMBER)
1826 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1827 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1829 //const char* val_str = jsonObjectGetString( value );
1830 //buffer_fadd( val_buf, "%ld", atol(val_str) );
1831 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1834 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
1835 if (value->type == JSON_NUMBER)
1836 //buffer_fadd( val_buf, "%f", jsonObjectGetNumber(value) );
1837 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1839 //const char* val_str = jsonObjectGetString( value );
1840 //buffer_fadd( val_buf, "%f", atof(val_str) );
1841 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1845 // Presumably this was really intended ot be a string, so quote it
1846 char* str = jsonObjectToSimpleString( value );
1847 if ( dbi_conn_quote_string(dbhandle, &str) ) {
1848 OSRF_BUFFER_ADD( val_buf, str );
1851 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
1853 buffer_free(val_buf);
1858 return buffer_release(val_buf);
1861 static char* searchINPredicate (const char* class_alias, osrfHash* field,
1862 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1863 growing_buffer* sql_buf = buffer_init(32);
1869 osrfHashGet(field, "name")
1873 buffer_add(sql_buf, "IN (");
1874 } else if (!(strcasecmp(op,"not in"))) {
1875 buffer_add(sql_buf, "NOT IN (");
1877 buffer_add(sql_buf, "IN (");
1880 if (node->type == JSON_HASH) {
1881 // subquery predicate
1882 char* subpred = buildQuery( ctx, node, SUBSELECT );
1884 buffer_free( sql_buf );
1888 buffer_add(sql_buf, subpred);
1891 } else if (node->type == JSON_ARRAY) {
1892 // literal value list
1893 int in_item_index = 0;
1894 int in_item_first = 1;
1895 const jsonObject* in_item;
1896 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1901 buffer_add(sql_buf, ", ");
1904 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1905 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1906 MODULENAME, json_type( in_item->type ) );
1907 buffer_free(sql_buf);
1911 // Append the literal value -- quoted if not a number
1912 if ( JSON_NUMBER == in_item->type ) {
1913 char* val = jsonNumberToDBString( field, in_item );
1914 OSRF_BUFFER_ADD( sql_buf, val );
1917 } else if ( !strcmp( get_primitive( field ), "number") ) {
1918 char* val = jsonNumberToDBString( field, in_item );
1919 OSRF_BUFFER_ADD( sql_buf, val );
1923 char* key_string = jsonObjectToSimpleString(in_item);
1924 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1925 OSRF_BUFFER_ADD( sql_buf, key_string );
1928 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1930 buffer_free(sql_buf);
1936 if( in_item_first ) {
1937 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1938 buffer_free( sql_buf );
1942 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1943 MODULENAME, json_type( node->type ) );
1944 buffer_free(sql_buf);
1948 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1950 return buffer_release(sql_buf);
1953 // Receive a JSON_ARRAY representing a function call. The first
1954 // entry in the array is the function name. The rest are parameters.
1955 static char* searchValueTransform( const jsonObject* array ) {
1957 if( array->size < 1 ) {
1958 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
1962 // Get the function name
1963 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
1964 if( func_item->type != JSON_STRING ) {
1965 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
1966 MODULENAME, json_type( func_item->type ) );
1970 growing_buffer* sql_buf = buffer_init(32);
1972 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1973 OSRF_BUFFER_ADD( sql_buf, "( " );
1975 // Get the parameters
1976 int func_item_index = 1; // We already grabbed the zeroth entry
1977 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1979 // Add a separator comma, if we need one
1980 if( func_item_index > 2 )
1981 buffer_add( sql_buf, ", " );
1983 // Add the current parameter
1984 if (func_item->type == JSON_NULL) {
1985 buffer_add( sql_buf, "NULL" );
1987 char* val = jsonObjectToSimpleString(func_item);
1988 if ( dbi_conn_quote_string(dbhandle, &val) ) {
1989 OSRF_BUFFER_ADD( sql_buf, val );
1992 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1993 buffer_free(sql_buf);
2000 buffer_add( sql_buf, " )" );
2002 return buffer_release(sql_buf);
2005 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
2006 const jsonObject* node, const char* op) {
2008 if( ! is_good_operator( op ) ) {
2009 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2013 char* val = searchValueTransform(node);
2017 growing_buffer* sql_buf = buffer_init(32);
2022 osrfHashGet(field, "name"),
2029 return buffer_release(sql_buf);
2032 // class_alias is a class name or other table alias
2033 // field is a field definition as stored in the IDL
2034 // node comes from the method parameter, and may represent an entry in the SELECT list
2035 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
2036 growing_buffer* sql_buf = buffer_init(32);
2038 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
2039 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
2041 if(transform_subcolumn) {
2042 if( ! is_identifier( transform_subcolumn ) ) {
2043 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2044 MODULENAME, transform_subcolumn );
2045 buffer_free( sql_buf );
2048 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2051 if (field_transform) {
2053 if( ! is_identifier( field_transform ) ) {
2054 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2055 MODULENAME, field_transform );
2056 buffer_free( sql_buf );
2060 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class_alias, osrfHashGet(field, "name"));
2061 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2064 if( array->type != JSON_ARRAY ) {
2065 osrfLogError( OSRF_LOG_MARK,
2066 "%s: Expected JSON_ARRAY for function params; found %s",
2067 MODULENAME, json_type( array->type ) );
2068 buffer_free( sql_buf );
2071 int func_item_index = 0;
2072 jsonObject* func_item;
2073 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2075 char* val = jsonObjectToSimpleString(func_item);
2078 buffer_add( sql_buf, ",NULL" );
2079 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
2080 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2081 OSRF_BUFFER_ADD( sql_buf, val );
2083 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
2085 buffer_free(sql_buf);
2092 buffer_add( sql_buf, " )" );
2095 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
2098 if (transform_subcolumn)
2099 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2101 return buffer_release(sql_buf);
2104 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2105 const jsonObject* node, const char* op ) {
2107 if( ! is_good_operator( op ) ) {
2108 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2112 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2113 if( ! field_transform )
2116 int extra_parens = 0; // boolean
2118 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2119 if ( ! value_obj ) {
2120 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2122 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
2123 free(field_transform);
2127 } else if ( value_obj->type == JSON_ARRAY ) {
2128 value = searchValueTransform( value_obj );
2130 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
2131 free( field_transform );
2134 } else if ( value_obj->type == JSON_HASH ) {
2135 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2137 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
2138 free(field_transform);
2142 } else if ( value_obj->type == JSON_NUMBER ) {
2143 value = jsonNumberToDBString( field, value_obj );
2144 } else if ( value_obj->type == JSON_NULL ) {
2145 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
2146 free(field_transform);
2148 } else if ( value_obj->type == JSON_BOOL ) {
2149 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
2150 free(field_transform);
2153 if ( !strcmp( get_primitive( field ), "number") ) {
2154 value = jsonNumberToDBString( field, value_obj );
2156 value = jsonObjectToSimpleString( value_obj );
2157 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2158 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
2160 free(field_transform);
2166 const char* left_parens = "";
2167 const char* right_parens = "";
2169 if( extra_parens ) {
2174 growing_buffer* sql_buf = buffer_init(32);
2178 "%s%s %s %s %s %s%s",
2189 free(field_transform);
2191 return buffer_release(sql_buf);
2194 static char* searchSimplePredicate (const char* op, const char* class_alias,
2195 osrfHash* field, const jsonObject* node) {
2197 if( ! is_good_operator( op ) ) {
2198 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2204 // Get the value to which we are comparing the specified column
2205 if (node->type != JSON_NULL) {
2206 if ( node->type == JSON_NUMBER ) {
2207 val = jsonNumberToDBString( field, node );
2208 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2209 val = jsonNumberToDBString( field, node );
2211 val = jsonObjectToSimpleString(node);
2216 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2217 // Value is not numeric; enclose it in quotes
2218 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2219 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2225 // Compare to a null value
2226 val = strdup( "NULL" );
2227 if (strcmp( op, "=" ))
2233 growing_buffer* sql_buf = buffer_init(32);
2234 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2235 char* pred = buffer_release( sql_buf );
2242 static char* searchBETWEENPredicate (const char* class_alias,
2243 osrfHash* field, const jsonObject* node) {
2245 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2246 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2248 if( NULL == y_node ) {
2249 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2252 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2253 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2260 if ( !strcmp( get_primitive( field ), "number") ) {
2261 x_string = jsonNumberToDBString(field, x_node);
2262 y_string = jsonNumberToDBString(field, y_node);
2265 x_string = jsonObjectToSimpleString(x_node);
2266 y_string = jsonObjectToSimpleString(y_node);
2267 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2268 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2269 MODULENAME, x_string, y_string);
2276 growing_buffer* sql_buf = buffer_init(32);
2277 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2278 class_alias, osrfHashGet(field, "name"), x_string, y_string );
2282 return buffer_release(sql_buf);
2285 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2286 jsonObject* node, osrfMethodContext* ctx ) {
2289 if (node->type == JSON_ARRAY) { // equality IN search
2290 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2291 } else if (node->type == JSON_HASH) { // other search
2292 jsonIterator* pred_itr = jsonNewIterator( node );
2293 if( !jsonIteratorHasNext( pred_itr ) ) {
2294 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2295 MODULENAME, osrfHashGet(field, "name") );
2297 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2299 // Verify that there are no additional predicates
2300 if( jsonIteratorHasNext( pred_itr ) ) {
2301 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2302 MODULENAME, osrfHashGet(field, "name") );
2303 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2304 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2305 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2306 pred = searchINPredicate( class_info->alias, field, pred_node, pred_itr->key, ctx );
2307 else if ( pred_node->type == JSON_ARRAY )
2308 pred = searchFunctionPredicate( class_info->alias, field, pred_node, pred_itr->key );
2309 else if ( pred_node->type == JSON_HASH )
2310 pred = searchFieldTransformPredicate( class_info, field, pred_node, pred_itr->key );
2312 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2314 jsonIteratorFree(pred_itr);
2316 } else if (node->type == JSON_NULL) { // IS NULL search
2317 growing_buffer* _p = buffer_init(64);
2320 "\"%s\".%s IS NULL",
2321 class_info->class_name,
2322 osrfHashGet(field, "name")
2324 pred = buffer_release(_p);
2325 } else { // equality search
2326 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2345 field : call_number,
2361 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2363 const jsonObject* working_hash;
2364 jsonObject* freeable_hash = NULL;
2366 if (join_hash->type == JSON_HASH) {
2367 working_hash = join_hash;
2368 } else if (join_hash->type == JSON_STRING) {
2369 // turn it into a JSON_HASH by creating a wrapper
2370 // around a copy of the original
2371 const char* _tmp = jsonObjectGetString( join_hash );
2372 freeable_hash = jsonNewObjectType(JSON_HASH);
2373 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2374 working_hash = freeable_hash;
2378 "%s: JOIN failed; expected JSON object type not found",
2384 growing_buffer* join_buf = buffer_init(128);
2385 const char* leftclass = left_info->class_name;
2387 jsonObject* snode = NULL;
2388 jsonIterator* search_itr = jsonNewIterator( working_hash );
2390 while ( (snode = jsonIteratorNext( search_itr )) ) {
2391 const char* right_alias = search_itr->key;
2393 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2395 class = right_alias;
2397 const ClassInfo* right_info = add_joined_class( right_alias, class );
2401 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2405 jsonIteratorFree( search_itr );
2406 buffer_free( join_buf );
2408 jsonObjectFree( freeable_hash );
2411 osrfHash* links = right_info->links;
2412 const char* table = right_info->source_def;
2414 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2415 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2417 if (field && !fkey) {
2418 // Look up the corresponding join column in the IDL.
2419 // The link must be defined in the child table,
2420 // and point to the right parent table.
2421 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2422 const char* reltype = NULL;
2423 const char* other_class = NULL;
2424 reltype = osrfHashGet( idl_link, "reltype" );
2425 if( reltype && strcmp( reltype, "has_many" ) )
2426 other_class = osrfHashGet( idl_link, "class" );
2427 if( other_class && !strcmp( other_class, leftclass ) )
2428 fkey = osrfHashGet( idl_link, "key" );
2432 "%s: JOIN failed. No link defined from %s.%s to %s",
2438 buffer_free(join_buf);
2440 jsonObjectFree(freeable_hash);
2441 jsonIteratorFree(search_itr);
2445 } else if (!field && fkey) {
2446 // Look up the corresponding join column in the IDL.
2447 // The link must be defined in the child table,
2448 // and point to the right parent table.
2449 osrfHash* left_links = left_info->links;
2450 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2451 const char* reltype = NULL;
2452 const char* other_class = NULL;
2453 reltype = osrfHashGet( idl_link, "reltype" );
2454 if( reltype && strcmp( reltype, "has_many" ) )
2455 other_class = osrfHashGet( idl_link, "class" );
2456 if( other_class && !strcmp( other_class, class ) )
2457 field = osrfHashGet( idl_link, "key" );
2461 "%s: JOIN failed. No link defined from %s.%s to %s",
2467 buffer_free(join_buf);
2469 jsonObjectFree(freeable_hash);
2470 jsonIteratorFree(search_itr);
2474 } else if (!field && !fkey) {
2475 osrfHash* left_links = left_info->links;
2477 // For each link defined for the left class:
2478 // see if the link references the joined class
2479 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2480 osrfHash* curr_link = NULL;
2481 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2482 const char* other_class = osrfHashGet( curr_link, "class" );
2483 if( other_class && !strcmp( other_class, class ) ) {
2485 // In the IDL, the parent class doesn't know then names of the child
2486 // columns that are pointing to it, so don't use that end of the link
2487 const char* reltype = osrfHashGet( curr_link, "reltype" );
2488 if( reltype && strcmp( reltype, "has_many" ) ) {
2489 // Found a link between the classes
2490 fkey = osrfHashIteratorKey( itr );
2491 field = osrfHashGet( curr_link, "key" );
2496 osrfHashIteratorFree( itr );
2498 if (!field || !fkey) {
2499 // Do another such search, with the classes reversed
2501 // For each link defined for the joined class:
2502 // see if the link references the left class
2503 osrfHashIterator* itr = osrfNewHashIterator( links );
2504 osrfHash* curr_link = NULL;
2505 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2506 const char* other_class = osrfHashGet( curr_link, "class" );
2507 if( other_class && !strcmp( other_class, leftclass ) ) {
2509 // In the IDL, the parent class doesn't know then names of the child
2510 // columns that are pointing to it, so don't use that end of the link
2511 const char* reltype = osrfHashGet( curr_link, "reltype" );
2512 if( reltype && strcmp( reltype, "has_many" ) ) {
2513 // Found a link between the classes
2514 field = osrfHashIteratorKey( itr );
2515 fkey = osrfHashGet( curr_link, "key" );
2520 osrfHashIteratorFree( itr );
2523 if (!field || !fkey) {
2526 "%s: JOIN failed. No link defined between %s and %s",
2531 buffer_free(join_buf);
2533 jsonObjectFree(freeable_hash);
2534 jsonIteratorFree(search_itr);
2540 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2542 if ( !strcasecmp(type,"left") ) {
2543 buffer_add(join_buf, " LEFT JOIN");
2544 } else if ( !strcasecmp(type,"right") ) {
2545 buffer_add(join_buf, " RIGHT JOIN");
2546 } else if ( !strcasecmp(type,"full") ) {
2547 buffer_add(join_buf, " FULL JOIN");
2549 buffer_add(join_buf, " INNER JOIN");
2552 buffer_add(join_buf, " INNER JOIN");
2555 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2556 table, right_alias, right_alias, field, left_info->alias, fkey);
2558 // Add any other join conditions as specified by "filter"
2559 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2561 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2562 if ( filter_op && !strcasecmp("or",filter_op) ) {
2563 buffer_add( join_buf, " OR " );
2565 buffer_add( join_buf, " AND " );
2568 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2570 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2571 OSRF_BUFFER_ADD( join_buf, jpred );
2576 "%s: JOIN failed. Invalid conditional expression.",
2579 jsonIteratorFree( search_itr );
2580 buffer_free( join_buf );
2582 jsonObjectFree( freeable_hash );
2587 buffer_add(join_buf, " ) ");
2589 // Recursively add a nested join, if one is present
2590 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2592 char* jpred = searchJOIN( join_filter, right_info );
2594 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2595 OSRF_BUFFER_ADD( join_buf, jpred );
2598 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2599 jsonIteratorFree( search_itr );
2600 buffer_free( join_buf );
2602 jsonObjectFree( freeable_hash );
2609 jsonObjectFree(freeable_hash);
2610 jsonIteratorFree(search_itr);
2612 return buffer_release(join_buf);
2617 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2618 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2619 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2621 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2623 search_hash is the JSON expression of the conditions.
2624 meta is the class definition from the IDL, for the relevant table.
2625 opjoin_type indicates whether multiple conditions, if present, should be
2626 connected by AND or OR.
2627 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2628 to pass it to other functions -- and all they do with it is to use the session
2629 and request members to send error messages back to the client.
2633 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
2634 int opjoin_type, osrfMethodContext* ctx ) {
2638 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2641 class_info->class_def,
2646 growing_buffer* sql_buf = buffer_init(128);
2648 jsonObject* node = NULL;
2651 if ( search_hash->type == JSON_ARRAY ) {
2652 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2653 if( 0 == search_hash->size ) {
2656 "%s: Invalid predicate structure: empty JSON array",
2659 buffer_free( sql_buf );
2663 unsigned long i = 0;
2664 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
2668 if (opjoin_type == OR_OP_JOIN)
2669 buffer_add(sql_buf, " OR ");
2671 buffer_add(sql_buf, " AND ");
2674 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2676 buffer_free( sql_buf );
2680 buffer_fadd(sql_buf, "( %s )", subpred);
2684 } else if ( search_hash->type == JSON_HASH ) {
2685 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2686 jsonIterator* search_itr = jsonNewIterator( search_hash );
2687 if( !jsonIteratorHasNext( search_itr ) ) {
2690 "%s: Invalid predicate structure: empty JSON object",
2693 jsonIteratorFree( search_itr );
2694 buffer_free( sql_buf );
2698 while ( (node = jsonIteratorNext( search_itr )) ) {
2703 if (opjoin_type == OR_OP_JOIN)
2704 buffer_add(sql_buf, " OR ");
2706 buffer_add(sql_buf, " AND ");
2709 if ( '+' == search_itr->key[ 0 ] ) {
2711 // This plus sign prefixes a class name or other table alias;
2712 // make sure the table alias is in scope
2713 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
2714 if( ! alias_info ) {
2717 "%s: Invalid table alias \"%s\" in WHERE clause",
2721 jsonIteratorFree( search_itr );
2722 buffer_free( sql_buf );
2726 if ( node->type == JSON_STRING ) {
2727 // It's the name of a column; make sure it belongs to the class
2728 const char* fieldname = jsonObjectGetString( node );
2729 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
2732 "%s: Invalid column name \"%s\" in WHERE clause for table alias \"%s\"",
2737 jsonIteratorFree( search_itr );
2738 buffer_free( sql_buf );
2742 buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
2744 // It's something more complicated
2745 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
2747 jsonIteratorFree( search_itr );
2748 buffer_free( sql_buf );
2752 buffer_fadd(sql_buf, "( %s )", subpred);
2755 } else if ( '-' == search_itr->key[ 0 ] ) {
2756 if ( !strcasecmp("-or",search_itr->key) ) {
2757 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
2759 jsonIteratorFree( search_itr );
2760 buffer_free( sql_buf );
2764 buffer_fadd(sql_buf, "( %s )", subpred);
2766 } else if ( !strcasecmp("-and",search_itr->key) ) {
2767 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2769 jsonIteratorFree( search_itr );
2770 buffer_free( sql_buf );
2774 buffer_fadd(sql_buf, "( %s )", subpred);
2776 } else if ( !strcasecmp("-not",search_itr->key) ) {
2777 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2779 jsonIteratorFree( search_itr );
2780 buffer_free( sql_buf );
2784 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2786 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2787 char* subpred = buildQuery( ctx, node, SUBSELECT );
2789 jsonIteratorFree( search_itr );
2790 buffer_free( sql_buf );
2794 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2796 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2797 char* subpred = buildQuery( ctx, node, SUBSELECT );
2799 jsonIteratorFree( search_itr );
2800 buffer_free( sql_buf );
2804 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2806 } else { // Invalid "minus" operator
2809 "%s: Invalid operator \"%s\" in WHERE clause",
2813 jsonIteratorFree( search_itr );
2814 buffer_free( sql_buf );
2820 const char* class = class_info->class_name;
2821 osrfHash* fields = class_info->fields;
2822 osrfHash* field = osrfHashGet( fields, search_itr->key );
2825 const char* table = class_info->source_def;
2828 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2831 table ? table : "?",
2834 jsonIteratorFree(search_itr);
2835 buffer_free(sql_buf);
2839 char* subpred = searchPredicate( class_info, field, node, ctx );
2841 buffer_free(sql_buf);
2842 jsonIteratorFree(search_itr);
2846 buffer_add( sql_buf, subpred );
2850 jsonIteratorFree(search_itr);
2853 // ERROR ... only hash and array allowed at this level
2854 char* predicate_string = jsonObjectToJSON( search_hash );
2857 "%s: Invalid predicate structure: %s",
2861 buffer_free(sql_buf);
2862 free(predicate_string);
2866 return buffer_release(sql_buf);
2869 /* Build a JSON_ARRAY of field names for a given table alias
2871 static jsonObject* defaultSelectList( const char* table_alias ) {
2876 ClassInfo* class_info = search_all_alias( table_alias );
2877 if( ! class_info ) {
2880 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
2887 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
2888 osrfHash* field_def = NULL;
2889 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
2890 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2891 const char* field_name = osrfHashIteratorKey( field_itr );
2892 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2893 jsonObjectPush( array, jsonNewObject( field_name ) );
2896 osrfHashIteratorFree( field_itr );
2901 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
2902 // The jsonObject must be a JSON_HASH with an single entry for "union",
2903 // "intersect", or "except". The data associated with this key must be an
2904 // array of hashes, each hash being a query.
2905 // Also allowed but currently ignored: entries for "order_by" and "alias".
2906 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
2908 if( ! combo || combo->type != JSON_HASH )
2909 return NULL; // should be impossible; validated by caller
2911 const jsonObject* query_array = NULL; // array of subordinate queries
2912 const char* op = NULL; // name of operator, e.g. UNION
2913 const char* alias = NULL; // alias for the query (needed for ORDER BY)
2914 int op_count = 0; // for detecting conflicting operators
2915 int excepting = 0; // boolean
2916 int all = 0; // boolean
2917 jsonObject* order_obj = NULL;
2919 // Identify the elements in the hash
2920 jsonIterator* query_itr = jsonNewIterator( combo );
2921 jsonObject* curr_obj = NULL;
2922 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
2923 if( ! strcmp( "union", query_itr->key ) ) {
2926 query_array = curr_obj;
2927 } else if( ! strcmp( "intersect", query_itr->key ) ) {
2930 query_array = curr_obj;
2931 } else if( ! strcmp( "except", query_itr->key ) ) {
2935 query_array = curr_obj;
2936 } else if( ! strcmp( "order_by", query_itr->key ) ) {
2939 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
2942 order_obj = curr_obj;
2943 } else if( ! strcmp( "alias", query_itr->key ) ) {
2944 if( curr_obj->type != JSON_STRING ) {
2945 jsonIteratorFree( query_itr );
2948 alias = jsonObjectGetString( curr_obj );
2949 } else if( ! strcmp( "all", query_itr->key ) ) {
2950 if( obj_is_true( curr_obj ) )
2954 osrfAppSessionStatus(
2956 OSRF_STATUS_INTERNALSERVERERROR,
2957 "osrfMethodException",
2959 "Malformed query; unexpected entry in query object"
2963 "%s: Unexpected entry for \"%s\" in%squery",
2968 jsonIteratorFree( query_itr );
2972 jsonIteratorFree( query_itr );
2974 // More sanity checks
2975 if( ! query_array ) {
2977 osrfAppSessionStatus(
2979 OSRF_STATUS_INTERNALSERVERERROR,
2980 "osrfMethodException",
2982 "Expected UNION, INTERSECT, or EXCEPT operator not found"
2986 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
2989 return NULL; // should be impossible...
2990 } else if( op_count > 1 ) {
2992 osrfAppSessionStatus(
2994 OSRF_STATUS_INTERNALSERVERERROR,
2995 "osrfMethodException",
2997 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3001 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3005 } if( query_array->type != JSON_ARRAY ) {
3007 osrfAppSessionStatus(
3009 OSRF_STATUS_INTERNALSERVERERROR,
3010 "osrfMethodException",
3012 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3016 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3019 json_type( query_array->type )
3022 } if( query_array->size < 2 ) {
3024 osrfAppSessionStatus(
3026 OSRF_STATUS_INTERNALSERVERERROR,
3027 "osrfMethodException",
3029 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3033 "%s:%srequires multiple queries as operands",
3038 } else if( excepting && query_array->size > 2 ) {
3040 osrfAppSessionStatus(
3042 OSRF_STATUS_INTERNALSERVERERROR,
3043 "osrfMethodException",
3045 "EXCEPT operator has too many queries as operands"
3049 "%s:EXCEPT operator has too many queries as operands",
3053 } else if( order_obj && ! alias ) {
3055 osrfAppSessionStatus(
3057 OSRF_STATUS_INTERNALSERVERERROR,
3058 "osrfMethodException",
3060 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3064 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3070 // So far so good. Now build the SQL.
3071 growing_buffer* sql = buffer_init( 256 );
3073 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3074 // Add a layer of parentheses
3075 if( flags & SUBCOMBO )
3076 OSRF_BUFFER_ADD( sql, "( " );
3078 // Traverse the query array. Each entry should be a hash.
3079 int first = 1; // boolean
3081 jsonObject* query = NULL;
3082 while((query = jsonObjectGetIndex( query_array, i++ ) )) {
3083 if( query->type != JSON_HASH ) {
3085 osrfAppSessionStatus(
3087 OSRF_STATUS_INTERNALSERVERERROR,
3088 "osrfMethodException",
3090 "Malformed query under UNION, INTERSECT or EXCEPT"
3094 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3097 json_type( query->type )
3106 OSRF_BUFFER_ADD( sql, op );
3108 OSRF_BUFFER_ADD( sql, "ALL " );
3111 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3115 "%s: Error building query under%s",
3123 OSRF_BUFFER_ADD( sql, query_str );
3126 if( flags & SUBCOMBO )
3127 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3129 if ( !(flags & SUBSELECT) )
3130 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3132 return buffer_release( sql );
3135 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3136 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3137 // or "except" to indicate the type of query.
3138 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3142 osrfAppSessionStatus(
3144 OSRF_STATUS_INTERNALSERVERERROR,
3145 "osrfMethodException",
3147 "Malformed query; no query object"
3149 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", MODULENAME );
3151 } else if( query->type != JSON_HASH ) {
3153 osrfAppSessionStatus(
3155 OSRF_STATUS_INTERNALSERVERERROR,
3156 "osrfMethodException",
3158 "Malformed query object"
3162 "%s: Query object is %s instead of JSON_HASH",
3164 json_type( query->type )
3169 // Determine what kind of query it purports to be, and dispatch accordingly.
3170 if( jsonObjectGetKey( query, "union" ) ||
3171 jsonObjectGetKey( query, "intersect" ) ||
3172 jsonObjectGetKey( query, "except" ) ) {
3173 return doCombo( ctx, query, flags );
3175 // It is presumably a SELECT query
3177 // Push a node onto the stack for the current query. Every level of
3178 // subquery gets its own QueryFrame on the Stack.
3181 // Build an SQL SELECT statement
3184 jsonObjectGetKey( query, "select" ),
3185 jsonObjectGetKey( query, "from" ),
3186 jsonObjectGetKey( query, "where" ),
3187 jsonObjectGetKey( query, "having" ),
3188 jsonObjectGetKey( query, "order_by" ),
3189 jsonObjectGetKey( query, "limit" ),
3190 jsonObjectGetKey( query, "offset" ),
3199 /* method context */ osrfMethodContext* ctx,
3201 /* SELECT */ jsonObject* selhash,
3202 /* FROM */ jsonObject* join_hash,
3203 /* WHERE */ jsonObject* search_hash,
3204 /* HAVING */ jsonObject* having_hash,
3205 /* ORDER BY */ jsonObject* order_hash,
3206 /* LIMIT */ jsonObject* limit,
3207 /* OFFSET */ jsonObject* offset,
3208 /* flags */ int flags
3210 const char* locale = osrf_message_get_last_locale();
3212 // general tmp objects
3213 const jsonObject* tmp_const;
3214 jsonObject* selclass = NULL;
3215 jsonObject* snode = NULL;
3216 jsonObject* onode = NULL;
3218 char* string = NULL;
3219 int from_function = 0;
3224 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3226 // punt if there's no FROM clause
3227 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3230 "%s: FROM clause is missing or empty",
3234 osrfAppSessionStatus(
3236 OSRF_STATUS_INTERNALSERVERERROR,
3237 "osrfMethodException",
3239 "FROM clause is missing or empty in JSON query"
3244 // the core search class
3245 const char* core_class = NULL;
3247 // get the core class -- the only key of the top level FROM clause, or a string
3248 if (join_hash->type == JSON_HASH) {
3249 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3250 snode = jsonIteratorNext( tmp_itr );
3252 // Populate the current QueryFrame with information
3253 // about the core class
3254 if( add_query_core( NULL, tmp_itr->key ) ) {
3256 osrfAppSessionStatus(
3258 OSRF_STATUS_INTERNALSERVERERROR,
3259 "osrfMethodException",
3261 "Unable to look up core class"
3265 core_class = curr_query->core.class_name;
3268 jsonObject* extra = jsonIteratorNext( tmp_itr );
3270 jsonIteratorFree( tmp_itr );
3273 // There shouldn't be more than one entry in join_hash
3277 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3281 osrfAppSessionStatus(
3283 OSRF_STATUS_INTERNALSERVERERROR,
3284 "osrfMethodException",
3286 "Malformed FROM clause in JSON query"
3288 return NULL; // Malformed join_hash; extra entry
3290 } else if (join_hash->type == JSON_ARRAY) {
3291 // We're selecting from a function, not from a table
3293 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
3296 } else if (join_hash->type == JSON_STRING) {
3297 // Populate the current QueryFrame with information
3298 // about the core class
3299 core_class = jsonObjectGetString( join_hash );
3301 if( add_query_core( NULL, core_class ) ) {
3303 osrfAppSessionStatus(
3305 OSRF_STATUS_INTERNALSERVERERROR,
3306 "osrfMethodException",
3308 "Unable to look up core class"
3316 "%s: FROM clause is unexpected JSON type: %s",
3318 json_type( join_hash->type )
3321 osrfAppSessionStatus(
3323 OSRF_STATUS_INTERNALSERVERERROR,
3324 "osrfMethodException",
3326 "Ill-formed FROM clause in JSON query"
3331 // Build the join clause, if any, while filling out the list
3332 // of joined classes in the current QueryFrame.
3333 char* join_clause = NULL;
3334 if( join_hash && ! from_function ) {
3336 join_clause = searchJOIN( join_hash, &curr_query->core );
3337 if( ! join_clause ) {
3339 osrfAppSessionStatus(
3341 OSRF_STATUS_INTERNALSERVERERROR,
3342 "osrfMethodException",
3344 "Unable to construct JOIN clause(s)"
3350 // For in case we don't get a select list
3351 jsonObject* defaultselhash = NULL;
3353 // if there is no select list, build a default select list ...
3354 if (!selhash && !from_function) {
3355 jsonObject* default_list = defaultSelectList( core_class );
3356 if( ! default_list ) {
3358 osrfAppSessionStatus(
3360 OSRF_STATUS_INTERNALSERVERERROR,
3361 "osrfMethodException",
3363 "Unable to build default SELECT clause in JSON query"
3365 free( join_clause );
3370 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
3371 jsonObjectSetKey( selhash, core_class, default_list );
3374 // The SELECT clause can be encoded only by a hash
3375 if( !from_function && selhash->type != JSON_HASH ) {
3378 "%s: Expected JSON_HASH for SELECT clause; found %s",
3380 json_type( selhash->type )
3384 osrfAppSessionStatus(
3386 OSRF_STATUS_INTERNALSERVERERROR,
3387 "osrfMethodException",
3389 "Malformed SELECT clause in JSON query"
3391 free( join_clause );
3395 // If you see a null or wild card specifier for the core class, or an
3396 // empty array, replace it with a default SELECT list
3397 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3399 int default_needed = 0; // boolean
3400 if( JSON_STRING == tmp_const->type
3401 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3403 else if( JSON_NULL == tmp_const->type )
3406 if( default_needed ) {
3407 // Build a default SELECT list
3408 jsonObject* default_list = defaultSelectList( core_class );
3409 if( ! default_list ) {
3411 osrfAppSessionStatus(
3413 OSRF_STATUS_INTERNALSERVERERROR,
3414 "osrfMethodException",
3416 "Can't build default SELECT clause in JSON query"
3418 free( join_clause );
3423 jsonObjectSetKey( selhash, core_class, default_list );
3427 // temp buffers for the SELECT list and GROUP BY clause
3428 growing_buffer* select_buf = buffer_init(128);
3429 growing_buffer* group_buf = buffer_init(128);
3431 int aggregate_found = 0; // boolean
3433 // Build a select list
3434 if(from_function) // From a function we select everything
3435 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3438 // Build the SELECT list as SQL
3442 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3443 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3445 const char* cname = selclass_itr->key;
3447 // Make sure the target relation is in the FROM clause.
3449 // At this point join_hash is a step down from the join_hash we
3450 // received as a parameter. If the original was a JSON_STRING,
3451 // then json_hash is now NULL. If the original was a JSON_HASH,
3452 // then json_hash is now the first (and only) entry in it,
3453 // denoting the core class. We've already excluded the
3454 // possibility that the original was a JSON_ARRAY, because in
3455 // that case from_function would be non-NULL, and we wouldn't
3458 // If the current table alias isn't in scope, bail out
3459 ClassInfo* class_info = search_alias( cname );
3460 if( ! class_info ) {
3463 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3468 osrfAppSessionStatus(
3470 OSRF_STATUS_INTERNALSERVERERROR,
3471 "osrfMethodException",
3473 "Selected class not in FROM clause in JSON query"
3475 jsonIteratorFree( selclass_itr );
3476 buffer_free( select_buf );
3477 buffer_free( group_buf );
3478 if( defaultselhash ) jsonObjectFree( defaultselhash );
3479 free( join_clause );
3483 if( selclass->type != JSON_ARRAY ) {
3486 "%s: Malformed SELECT list for class \"%s\"; not an array",
3491 osrfAppSessionStatus(
3493 OSRF_STATUS_INTERNALSERVERERROR,
3494 "osrfMethodException",
3496 "Selected class not in FROM clause in JSON query"
3499 jsonIteratorFree( selclass_itr );
3500 buffer_free( select_buf );
3501 buffer_free( group_buf );
3502 if( defaultselhash ) jsonObjectFree( defaultselhash );
3503 free( join_clause );
3507 // Look up some attributes of the current class
3508 osrfHash* idlClass = class_info->class_def;
3509 osrfHash* class_field_set = class_info->fields;
3510 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3511 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3513 if( 0 == selclass->size ) {
3516 "%s: No columns selected from \"%s\"",
3522 // stitch together the column list for the current table alias...
3523 unsigned long field_idx = 0;
3524 jsonObject* selfield = NULL;
3525 while((selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3527 // If we need a separator comma, add one
3531 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3534 // if the field specification is a string, add it to the list
3535 if (selfield->type == JSON_STRING) {
3537 // Look up the field in the IDL
3538 const char* col_name = jsonObjectGetString( selfield );
3539 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3541 // No such field in current class
3544 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3550 osrfAppSessionStatus(
3552 OSRF_STATUS_INTERNALSERVERERROR,
3553 "osrfMethodException",
3555 "Selected column not defined in JSON query"
3557 jsonIteratorFree( selclass_itr );
3558 buffer_free( select_buf );
3559 buffer_free( group_buf );
3560 if( defaultselhash ) jsonObjectFree( defaultselhash );
3561 free( join_clause );
3563 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3564 // Virtual field not allowed
3567 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3573 osrfAppSessionStatus(
3575 OSRF_STATUS_INTERNALSERVERERROR,
3576 "osrfMethodException",
3578 "Selected column may not be virtual in JSON query"
3580 jsonIteratorFree( selclass_itr );
3581 buffer_free( select_buf );
3582 buffer_free( group_buf );
3583 if( defaultselhash ) jsonObjectFree( defaultselhash );
3584 free( join_clause );
3590 if (flags & DISABLE_I18N)
3593 i18n = osrfHashGet(field_def, "i18n");
3595 if( str_is_true( i18n ) ) {
3596 buffer_fadd( select_buf,
3597 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3598 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3600 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3603 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3606 // ... but it could be an object, in which case we check for a Field Transform
3607 } else if (selfield->type == JSON_HASH) {
3609 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3611 // Get the field definition from the IDL
3612 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3614 // No such field in current class
3617 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3623 osrfAppSessionStatus(
3625 OSRF_STATUS_INTERNALSERVERERROR,
3626 "osrfMethodException",
3628 "Selected column is not defined in JSON query"
3630 jsonIteratorFree( selclass_itr );
3631 buffer_free( select_buf );
3632 buffer_free( group_buf );
3633 if( defaultselhash ) jsonObjectFree( defaultselhash );
3634 free( join_clause );
3636 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3637 // No such field in current class
3640 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3646 osrfAppSessionStatus(
3648 OSRF_STATUS_INTERNALSERVERERROR,
3649 "osrfMethodException",
3651 "Selected column is virtual in JSON query"
3653 jsonIteratorFree( selclass_itr );
3654 buffer_free( select_buf );
3655 buffer_free( group_buf );
3656 if( defaultselhash ) jsonObjectFree( defaultselhash );
3657 free( join_clause );
3661 // Decide what to use as a column alias
3663 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3664 _alias = jsonObjectGetString( tmp_const );
3665 } else { // Use field name as the alias
3669 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3670 char* transform_str = searchFieldTransform(class_info->alias, field_def, selfield);
3671 if( transform_str ) {
3672 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3673 free(transform_str);
3676 osrfAppSessionStatus(
3678 OSRF_STATUS_INTERNALSERVERERROR,
3679 "osrfMethodException",
3681 "Unable to generate transform function in JSON query"
3683 jsonIteratorFree( selclass_itr );
3684 buffer_free( select_buf );
3685 buffer_free( group_buf );
3686 if( defaultselhash ) jsonObjectFree( defaultselhash );
3687 free( join_clause );
3694 if (flags & DISABLE_I18N)
3697 i18n = osrfHashGet(field_def, "i18n");
3699 if( str_is_true( i18n ) ) {
3700 buffer_fadd( select_buf,
3701 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3702 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3704 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3707 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3714 "%s: Selected item is unexpected JSON type: %s",
3716 json_type( selfield->type )
3719 osrfAppSessionStatus(
3721 OSRF_STATUS_INTERNALSERVERERROR,
3722 "osrfMethodException",
3724 "Ill-formed SELECT item in JSON query"
3726 jsonIteratorFree( selclass_itr );
3727 buffer_free( select_buf );
3728 buffer_free( group_buf );
3729 if( defaultselhash ) jsonObjectFree( defaultselhash );
3730 free( join_clause );
3734 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3735 if( obj_is_true( agg_obj ) )
3736 aggregate_found = 1;
3738 // Append a comma (except for the first one)
3739 // and add the column to a GROUP BY clause
3743 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3745 buffer_fadd(group_buf, " %d", sel_pos);
3749 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3751 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3752 if ( ! obj_is_true( aggregate_obj ) ) {
3756 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3759 buffer_fadd(group_buf, " %d", sel_pos);
3762 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3766 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3769 _column = searchFieldTransform(class_info->alias, field, selfield);
3770 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3771 OSRF_BUFFER_ADD(group_buf, _column);
3772 _column = searchFieldTransform(class_info->alias, field, selfield);
3779 } // end while -- iterating across SELECT columns
3781 } // end while -- iterating across classes
3783 jsonIteratorFree(selclass_itr);
3787 char* col_list = buffer_release(select_buf);
3789 // Make sure the SELECT list isn't empty. This can happen, for example,
3790 // if we try to build a default SELECT clause from a non-core table.
3793 osrfLogError(OSRF_LOG_MARK, "%s: SELECT clause is empty", MODULENAME );
3795 osrfAppSessionStatus(
3797 OSRF_STATUS_INTERNALSERVERERROR,
3798 "osrfMethodException",
3800 "SELECT list is empty"
3803 buffer_free( group_buf );
3804 if( defaultselhash ) jsonObjectFree( defaultselhash );
3805 free( join_clause );
3810 if (from_function) table = searchValueTransform(join_hash);
3811 else table = strdup( curr_query->core.source_def );
3815 osrfAppSessionStatus(
3817 OSRF_STATUS_INTERNALSERVERERROR,
3818 "osrfMethodException",
3820 "Unable to identify table for core class"
3823 buffer_free( group_buf );
3824 if( defaultselhash ) jsonObjectFree( defaultselhash );
3825 free( join_clause );
3829 // Put it all together
3830 growing_buffer* sql_buf = buffer_init(128);
3831 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3835 // Append the join clause, if any
3837 buffer_add(sql_buf, join_clause);
3841 char* order_by_list = NULL;
3842 char* having_buf = NULL;
3844 if (!from_function) {
3846 // Build a WHERE clause, if there is one
3847 if ( search_hash ) {
3848 buffer_add(sql_buf, " WHERE ");
3850 // and it's on the WHERE clause
3851 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
3854 osrfAppSessionStatus(
3856 OSRF_STATUS_INTERNALSERVERERROR,
3857 "osrfMethodException",
3859 "Severe query error in WHERE predicate -- see error log for more details"
3862 buffer_free(group_buf);
3863 buffer_free(sql_buf);
3864 if (defaultselhash) jsonObjectFree(defaultselhash);
3868 buffer_add(sql_buf, pred);
3872 // Build a HAVING clause, if there is one
3873 if ( having_hash ) {
3875 // and it's on the the WHERE clause
3876 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
3878 if( ! having_buf ) {
3880 osrfAppSessionStatus(
3882 OSRF_STATUS_INTERNALSERVERERROR,
3883 "osrfMethodException",
3885 "Severe query error in HAVING predicate -- see error log for more details"
3888 buffer_free(group_buf);
3889 buffer_free(sql_buf);
3890 if (defaultselhash) jsonObjectFree(defaultselhash);
3895 growing_buffer* order_buf = NULL; // to collect ORDER BY list
3897 // Build an ORDER BY clause, if there is one
3898 if( NULL == order_hash )
3899 ; // No ORDER BY? do nothing
3900 else if( JSON_ARRAY == order_hash->type ) {
3901 // Array of field specifications, each specification being a
3902 // hash to define the class, field, and other details
3904 jsonObject* order_spec;
3905 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
3907 if( JSON_HASH != order_spec->type ) {
3908 osrfLogError(OSRF_LOG_MARK,
3909 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
3910 MODULENAME, json_type( order_spec->type ) );
3912 osrfAppSessionStatus(
3914 OSRF_STATUS_INTERNALSERVERERROR,
3915 "osrfMethodException",
3917 "Malformed ORDER BY clause -- see error log for more details"
3919 buffer_free( order_buf );
3921 buffer_free(group_buf);
3922 buffer_free(sql_buf);
3923 if (defaultselhash) jsonObjectFree(defaultselhash);
3927 const char* class_alias =
3928 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
3930 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
3933 OSRF_BUFFER_ADD(order_buf, ", ");
3935 order_buf = buffer_init(128);
3937 if( !field || !class_alias ) {
3938 osrfLogError(OSRF_LOG_MARK,
3939 "%s: Missing class or field name in field specification of ORDER BY clause",
3942 osrfAppSessionStatus(
3944 OSRF_STATUS_INTERNALSERVERERROR,
3945 "osrfMethodException",
3947 "Malformed ORDER BY clause -- see error log for more details"
3949 buffer_free( order_buf );
3951 buffer_free(group_buf);
3952 buffer_free(sql_buf);
3953 if (defaultselhash) jsonObjectFree(defaultselhash);
3957 ClassInfo* order_class_info = search_alias( class_alias );
3958 if( ! order_class_info ) {
3959 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
3960 "not in FROM clause", MODULENAME, class_alias );
3962 osrfAppSessionStatus(
3964 OSRF_STATUS_INTERNALSERVERERROR,
3965 "osrfMethodException",
3967 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3970 buffer_free(group_buf);
3971 buffer_free(sql_buf);
3972 if (defaultselhash) jsonObjectFree(defaultselhash);
3976 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
3978 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
3979 MODULENAME, class_alias, field );
3981 osrfAppSessionStatus(
3983 OSRF_STATUS_INTERNALSERVERERROR,
3984 "osrfMethodException",
3986 "Invalid field referenced in ORDER BY clause -- see error log for more details"
3989 buffer_free(group_buf);
3990 buffer_free(sql_buf);
3991 if (defaultselhash) jsonObjectFree(defaultselhash);
3993 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3994 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3995 MODULENAME, field );
3997 osrfAppSessionStatus(
3999 OSRF_STATUS_INTERNALSERVERERROR,
4000 "osrfMethodException",
4002 "Virtual field in ORDER BY clause -- see error log for more details"
4004 buffer_free( order_buf );
4006 buffer_free(group_buf);
4007 buffer_free(sql_buf);
4008 if (defaultselhash) jsonObjectFree(defaultselhash);
4012 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4013 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
4014 if( ! transform_str ) {
4016 osrfAppSessionStatus(
4018 OSRF_STATUS_INTERNALSERVERERROR,
4019 "osrfMethodException",
4021 "Severe query error in ORDER BY clause -- see error log for more details"
4023 buffer_free( order_buf );
4025 buffer_free(group_buf);
4026 buffer_free(sql_buf);
4027 if (defaultselhash) jsonObjectFree(defaultselhash);
4031 OSRF_BUFFER_ADD( order_buf, transform_str );
4032 free( transform_str );
4035 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4037 const char* direction =
4038 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4040 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4041 OSRF_BUFFER_ADD( order_buf, " DESC" );
4043 OSRF_BUFFER_ADD( order_buf, " ASC" );
4046 } else if( JSON_HASH == order_hash->type ) {
4047 // This hash is keyed on class alias. Each class has either
4048 // an array of field names or a hash keyed on field name.
4049 jsonIterator* class_itr = jsonNewIterator( order_hash );
4050 while ( (snode = jsonIteratorNext( class_itr )) ) {
4052 ClassInfo* order_class_info = search_alias( class_itr->key );
4053 if( ! order_class_info ) {
4054 osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4055 MODULENAME, class_itr->key );
4057 osrfAppSessionStatus(
4059 OSRF_STATUS_INTERNALSERVERERROR,
4060 "osrfMethodException",
4062 "Invalid class referenced in ORDER BY clause -- see error log for more details"
4064 jsonIteratorFree( class_itr );
4065 buffer_free( order_buf );
4067 buffer_free(group_buf);
4068 buffer_free(sql_buf);
4069 if (defaultselhash) jsonObjectFree(defaultselhash);
4073 osrfHash* field_list_def = order_class_info->fields;
4075 if ( snode->type == JSON_HASH ) {
4077 // Hash is keyed on field names from the current class. For each field
4078 // there is another layer of hash to define the sorting details, if any,
4079 // or a string to indicate direction of sorting.
4080 jsonIterator* order_itr = jsonNewIterator( snode );
4081 while ( (onode = jsonIteratorNext( order_itr )) ) {
4083 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4085 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
4086 MODULENAME, order_itr->key );
4088 osrfAppSessionStatus(
4090 OSRF_STATUS_INTERNALSERVERERROR,
4091 "osrfMethodException",
4093 "Invalid field in ORDER BY clause -- see error log for more details"
4095 jsonIteratorFree( order_itr );
4096 jsonIteratorFree( class_itr );
4097 buffer_free( order_buf );
4099 buffer_free(group_buf);
4100 buffer_free(sql_buf);
4101 if (defaultselhash) jsonObjectFree(defaultselhash);
4103 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4104 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4105 MODULENAME, order_itr->key );
4107 osrfAppSessionStatus(
4109 OSRF_STATUS_INTERNALSERVERERROR,
4110 "osrfMethodException",
4112 "Virtual field in ORDER BY clause -- see error log for more details"
4114 jsonIteratorFree( order_itr );
4115 jsonIteratorFree( class_itr );
4116 buffer_free( order_buf );
4118 buffer_free(group_buf);
4119 buffer_free(sql_buf);
4120 if (defaultselhash) jsonObjectFree(defaultselhash);
4124 const char* direction = NULL;
4125 if ( onode->type == JSON_HASH ) {
4126 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4127 string = searchFieldTransform(
4129 osrfHashGet( field_list_def, order_itr->key ),
4133 if( ctx ) osrfAppSessionStatus(
4135 OSRF_STATUS_INTERNALSERVERERROR,
4136 "osrfMethodException",
4138 "Severe query error in ORDER BY clause -- see error log for more details"
4140 jsonIteratorFree( order_itr );
4141 jsonIteratorFree( class_itr );
4143 buffer_free(group_buf);
4144 buffer_free(order_buf);
4145 buffer_free(sql_buf);
4146 if (defaultselhash) jsonObjectFree(defaultselhash);
4150 growing_buffer* field_buf = buffer_init(16);
4151 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4152 string = buffer_release(field_buf);
4155 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4156 const char* dir = jsonObjectGetString(tmp_const);
4157 if (!strncasecmp(dir, "d", 1)) {
4158 direction = " DESC";
4164 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4165 osrfLogError( OSRF_LOG_MARK,
4166 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4167 MODULENAME, json_type( onode->type ) );
4169 osrfAppSessionStatus(
4171 OSRF_STATUS_INTERNALSERVERERROR,
4172 "osrfMethodException",
4174 "Malformed ORDER BY clause -- see error log for more details"
4176 jsonIteratorFree( order_itr );
4177 jsonIteratorFree( class_itr );
4179 buffer_free(group_buf);
4180 buffer_free(order_buf);
4181 buffer_free(sql_buf);
4182 if (defaultselhash) jsonObjectFree(defaultselhash);
4186 string = strdup(order_itr->key);
4187 const char* dir = jsonObjectGetString(onode);
4188 if (!strncasecmp(dir, "d", 1)) {
4189 direction = " DESC";
4196 OSRF_BUFFER_ADD(order_buf, ", ");
4198 order_buf = buffer_init(128);
4200 OSRF_BUFFER_ADD(order_buf, string);
4204 OSRF_BUFFER_ADD(order_buf, direction);
4208 jsonIteratorFree(order_itr);
4210 } else if ( snode->type == JSON_ARRAY ) {
4212 // Array is a list of fields from the current class
4213 unsigned long order_idx = 0;
4214 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4216 const char* _f = jsonObjectGetString( onode );
4218 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4220 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
4223 osrfAppSessionStatus(
4225 OSRF_STATUS_INTERNALSERVERERROR,
4226 "osrfMethodException",
4228 "Invalid field in ORDER BY clause -- see error log for more details"
4230 jsonIteratorFree( class_itr );
4231 buffer_free( order_buf );
4233 buffer_free(group_buf);
4234 buffer_free(sql_buf);
4235 if (defaultselhash) jsonObjectFree(defaultselhash);
4237 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4238 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4241 osrfAppSessionStatus(
4243 OSRF_STATUS_INTERNALSERVERERROR,
4244 "osrfMethodException",
4246 "Virtual field in ORDER BY clause -- see error log for more details"
4248 jsonIteratorFree( class_itr );
4249 buffer_free( order_buf );
4251 buffer_free(group_buf);
4252 buffer_free(sql_buf);
4253 if (defaultselhash) jsonObjectFree(defaultselhash);
4258 OSRF_BUFFER_ADD(order_buf, ", ");
4260 order_buf = buffer_init(128);
4262 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
4266 // IT'S THE OOOOOOOOOOOLD STYLE!
4268 osrfLogError(OSRF_LOG_MARK,
4269 "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
4271 osrfAppSessionStatus(
4273 OSRF_STATUS_INTERNALSERVERERROR,
4274 "osrfMethodException",
4276 "Severe query error -- see error log for more details"
4281 buffer_free(group_buf);
4282 buffer_free(order_buf);
4283 buffer_free(sql_buf);
4284 if (defaultselhash) jsonObjectFree(defaultselhash);
4285 jsonIteratorFree(class_itr);
4289 jsonIteratorFree( class_itr );
4291 osrfLogError(OSRF_LOG_MARK,
4292 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4293 MODULENAME, json_type( order_hash->type ) );
4295 osrfAppSessionStatus(
4297 OSRF_STATUS_INTERNALSERVERERROR,
4298 "osrfMethodException",
4300 "Malformed ORDER BY clause -- see error log for more details"
4302 buffer_free( order_buf );
4304 buffer_free(group_buf);
4305 buffer_free(sql_buf);
4306 if (defaultselhash) jsonObjectFree(defaultselhash);
4311 order_by_list = buffer_release( order_buf );
4315 string = buffer_release(group_buf);
4317 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4318 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4319 OSRF_BUFFER_ADD( sql_buf, string );
4324 if( having_buf && *having_buf ) {
4325 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4326 OSRF_BUFFER_ADD( sql_buf, having_buf );
4330 if( order_by_list ) {
4332 if ( *order_by_list ) {
4333 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4334 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4337 free( order_by_list );
4341 const char* str = jsonObjectGetString(limit);
4342 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
4346 const char* str = jsonObjectGetString(offset);
4347 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
4350 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4352 if (defaultselhash) jsonObjectFree(defaultselhash);
4354 return buffer_release(sql_buf);
4356 } // end of SELECT()
4358 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4360 const char* locale = osrf_message_get_last_locale();
4362 osrfHash* fields = osrfHashGet(meta, "fields");
4363 char* core_class = osrfHashGet(meta, "classname");
4365 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4367 jsonObject* node = NULL;
4368 jsonObject* snode = NULL;
4369 jsonObject* onode = NULL;
4370 const jsonObject* _tmp = NULL;
4371 jsonObject* selhash = NULL;
4372 jsonObject* defaultselhash = NULL;
4374 growing_buffer* sql_buf = buffer_init(128);
4375 growing_buffer* select_buf = buffer_init(128);
4377 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4378 defaultselhash = jsonNewObjectType(JSON_HASH);
4379 selhash = defaultselhash;
4382 // If there's no SELECT list for the core class, build one
4383 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
4384 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4386 // Add every non-virtual field to the field list
4387 osrfHash* field_def = NULL;
4388 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4389 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4390 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4391 const char* field = osrfHashIteratorKey( field_itr );
4392 jsonObjectPush( field_list, jsonNewObject( field ) );
4395 osrfHashIteratorFree( field_itr );
4396 jsonObjectSetKey( selhash, core_class, field_list );
4400 jsonIterator* class_itr = jsonNewIterator( selhash );
4401 while ( (snode = jsonIteratorNext( class_itr )) ) {
4403 const char* cname = class_itr->key;
4404 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4405 if (!idlClass) continue;
4407 if (strcmp(core_class,class_itr->key)) {
4408 if (!join_hash) continue;
4410 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
4412 jsonObjectFree(found);
4416 jsonObjectFree(found);
4419 jsonIterator* select_itr = jsonNewIterator( snode );
4420 while ( (node = jsonIteratorNext( select_itr )) ) {
4421 const char* item_str = jsonObjectGetString( node );
4422 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4423 char* fname = osrfHashGet(field, "name");
4425 if (!field) continue;
4430 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
4435 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4436 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4439 i18n = osrfHashGet(field, "i18n");
4441 if( str_is_true( i18n ) ) {
4442 char* pkey = osrfHashGet(idlClass, "primarykey");
4443 char* tname = osrfHashGet(idlClass, "tablename");
4445 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);
4447 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4450 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4454 jsonIteratorFree(select_itr);
4457 jsonIteratorFree(class_itr);
4459 char* col_list = buffer_release(select_buf);
4460 char* table = getSourceDefinition(meta);
4462 table = strdup( "(null)" );
4464 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4468 // Clear the query stack (as a fail-safe precaution against possible
4469 // leftover garbage); then push the first query frame onto the stack.
4470 clear_query_stack();
4472 if( add_query_core( NULL, core_class ) ) {
4474 osrfAppSessionStatus(
4476 OSRF_STATUS_INTERNALSERVERERROR,
4477 "osrfMethodException",
4479 "Unable to build query frame for core class"
4485 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4486 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
4487 OSRF_BUFFER_ADD(sql_buf, join_clause);
4491 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4492 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
4494 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
4496 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4498 osrfAppSessionStatus(
4500 OSRF_STATUS_INTERNALSERVERERROR,
4501 "osrfMethodException",
4503 "Severe query error -- see error log for more details"
4505 buffer_free(sql_buf);
4506 if(defaultselhash) jsonObjectFree(defaultselhash);
4507 clear_query_stack();
4510 buffer_add(sql_buf, pred);
4515 char* string = NULL;
4516 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4518 growing_buffer* order_buf = buffer_init(128);
4521 jsonIterator* class_itr = jsonNewIterator( _tmp );
4522 while ( (snode = jsonIteratorNext( class_itr )) ) {
4524 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4527 if ( snode->type == JSON_HASH ) {
4529 jsonIterator* order_itr = jsonNewIterator( snode );
4530 while ( (onode = jsonIteratorNext( order_itr )) ) {
4532 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4533 class_itr->key, order_itr->key );
4537 char* direction = NULL;
4538 if ( onode->type == JSON_HASH ) {
4539 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4540 string = searchFieldTransform( class_itr->key, field_def, onode );
4542 osrfAppSessionStatus(
4544 OSRF_STATUS_INTERNALSERVERERROR,
4545 "osrfMethodException",
4547 "Severe query error in ORDER BY clause -- see error log for more details"
4549 jsonIteratorFree( order_itr );
4550 jsonIteratorFree( class_itr );
4551 buffer_free( order_buf );
4552 buffer_free( sql_buf );
4553 if( defaultselhash ) jsonObjectFree( defaultselhash );
4554 clear_query_stack();
4558 growing_buffer* field_buf = buffer_init(16);
4559 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4560 string = buffer_release(field_buf);
4563 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4564 const char* dir = jsonObjectGetString(_tmp);
4565 if (!strncasecmp(dir, "d", 1)) {
4566 direction = " DESC";
4573 string = strdup(order_itr->key);
4574 const char* dir = jsonObjectGetString(onode);
4575 if (!strncasecmp(dir, "d", 1)) {
4576 direction = " DESC";
4585 buffer_add(order_buf, ", ");
4588 buffer_add(order_buf, string);
4592 buffer_add(order_buf, direction);
4597 jsonIteratorFree(order_itr);
4600 const char* str = jsonObjectGetString(snode);
4601 buffer_add(order_buf, str);
4607 jsonIteratorFree(class_itr);
4609 string = buffer_release(order_buf);
4612 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4613 OSRF_BUFFER_ADD( sql_buf, string );
4619 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4620 const char* str = jsonObjectGetString(_tmp);
4628 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4630 const char* str = jsonObjectGetString(_tmp);
4639 if (defaultselhash) jsonObjectFree(defaultselhash);
4640 clear_query_stack();
4642 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4643 return buffer_release(sql_buf);
4646 int doJSONSearch ( osrfMethodContext* ctx ) {
4647 if(osrfMethodVerifyContext( ctx )) {
4648 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4652 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
4657 dbhandle = writehandle;
4659 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4663 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4664 flags |= SELECT_DISTINCT;
4666 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4667 flags |= DISABLE_I18N;
4669 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4670 clear_query_stack(); // a possibly needless precaution
4671 char* sql = buildQuery( ctx, hash, flags );
4672 clear_query_stack();
4679 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4680 dbi_result result = dbi_conn_query(dbhandle, sql);
4683 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4685 if (dbi_result_first_row(result)) {
4686 /* JSONify the result */
4687 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4690 jsonObject* return_val = oilsMakeJSONFromResult( result );
4691 osrfAppRespond( ctx, return_val );
4692 jsonObjectFree( return_val );
4693 } while (dbi_result_next_row(result));
4696 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4699 osrfAppRespondComplete( ctx, NULL );
4701 /* clean up the query */
4702 dbi_result_free(result);
4706 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4707 osrfAppSessionStatus(
4709 OSRF_STATUS_INTERNALSERVERERROR,
4710 "osrfMethodException",
4712 "Severe query error -- see error log for more details"
4720 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4721 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4724 dbhandle = writehandle;
4726 osrfHash* links = osrfHashGet(meta, "links");
4727 osrfHash* fields = osrfHashGet(meta, "fields");
4728 char* core_class = osrfHashGet(meta, "classname");
4729 char* pkey = osrfHashGet(meta, "primarykey");
4731 const jsonObject* _tmp;
4734 char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4736 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4741 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4743 dbi_result result = dbi_conn_query(dbhandle, sql);
4744 if( NULL == result ) {
4745 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4746 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4747 osrfAppSessionStatus(
4749 OSRF_STATUS_INTERNALSERVERERROR,
4750 "osrfMethodException",
4752 "Severe query error -- see error log for more details"
4759 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4762 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
4763 osrfHash* dedup = osrfNewHash();
4765 if (dbi_result_first_row(result)) {
4766 /* JSONify the result */
4767 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4769 obj = oilsMakeFieldmapperFromResult( result, meta );
4770 char* pkey_val = oilsFMGetString( obj, pkey );
4771 if ( osrfHashGet( dedup, pkey_val ) ) {
4772 jsonObjectFree(obj);
4775 osrfHashSet( dedup, pkey_val, pkey_val );
4776 jsonObjectPush(res_list, obj);
4778 } while (dbi_result_next_row(result));
4780 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
4784 osrfHashFree(dedup);
4785 /* clean up the query */
4786 dbi_result_free(result);
4789 if (res_list->size && query_hash) {
4790 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
4792 int x = (int)jsonObjectGetNumber(_tmp);
4793 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
4795 const jsonObject* temp_blob;
4796 if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
4798 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
4799 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
4801 osrfStringArray* link_fields = NULL;
4804 if (flesh_fields->size == 1) {
4805 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
4806 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
4811 link_fields = osrfNewStringArray(1);
4812 jsonIterator* _i = jsonNewIterator( flesh_fields );
4813 while ((_f = jsonIteratorNext( _i ))) {
4814 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4816 jsonIteratorFree(_i);
4821 unsigned long res_idx = 0;
4822 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
4825 const char* link_field;
4827 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4829 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4831 osrfHash* kid_link = osrfHashGet(links, link_field);
4832 if (!kid_link) continue;
4834 osrfHash* field = osrfHashGet(fields, link_field);
4835 if (!field) continue;
4837 osrfHash* value_field = field;
4839 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4840 if (!kid_idl) continue;
4842 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4843 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4846 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4847 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4850 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4852 if (link_map->size > 0) {
4853 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4856 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4861 osrfHashGet(kid_link, "class"),
4868 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4869 osrfHashGet(kid_link, "field"),
4870 osrfHashGet(kid_link, "class"),
4871 osrfHashGet(kid_link, "key"),
4872 osrfHashGet(kid_link, "reltype")
4875 const char* search_key = jsonObjectGetString(
4878 atoi( osrfHashGet(value_field, "array_position") )
4883 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4887 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
4889 // construct WHERE clause
4890 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
4893 osrfHashGet(kid_link, "key"),
4894 jsonNewObject( search_key )
4897 // construct the rest of the query
4898 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
4899 jsonObjectSetKey( rest_of_query, "flesh",
4900 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4904 jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
4906 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
4907 jsonObjectSetKey( rest_of_query, "order_by",
4908 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
4912 if (jsonObjectGetKeyConst(query_hash, "select")) {
4913 jsonObjectSetKey( rest_of_query, "select",
4914 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
4918 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
4919 where_clause, rest_of_query, err);
4921 jsonObjectFree( where_clause );
4922 jsonObjectFree( rest_of_query );
4925 osrfStringArrayFree(link_fields);
4926 jsonObjectFree(res_list);
4927 jsonObjectFree(flesh_blob);
4931 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4933 jsonObject* X = NULL;
4934 if ( link_map->size > 0 && kids->size > 0 ) {
4936 kids = jsonNewObjectType(JSON_ARRAY);
4938 jsonObject* _k_node;
4939 unsigned long res_idx = 0;
4940 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
4946 (unsigned long)atoi(
4952 osrfHashGet(kid_link, "class")
4956 osrfStringArrayGetString( link_map, 0 )
4964 } // end while loop traversing X
4967 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4968 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4971 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4972 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4976 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4977 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4980 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4981 jsonObjectClone( kids )
4986 jsonObjectFree(kids);
4990 jsonObjectFree( kids );
4992 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4993 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4996 } // end while loop traversing res_list
4997 jsonObjectFree( flesh_blob );
4998 osrfStringArrayFree(link_fields);
5007 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
5009 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5011 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
5013 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
5016 if (!verifyObjectClass(ctx, target)) {
5021 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
5022 osrfAppSessionStatus(
5024 OSRF_STATUS_BADREQUEST,
5025 "osrfMethodException",
5027 "No active transaction -- required for UPDATE"
5033 // The following test is harmless but redundant. If a class is
5034 // readonly, we don't register an update method for it.
5035 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5036 osrfAppSessionStatus(
5038 OSRF_STATUS_BADREQUEST,
5039 "osrfMethodException",
5041 "Cannot UPDATE readonly class"
5047 dbhandle = writehandle;
5049 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
5051 // Set the last_xact_id
5052 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5054 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5055 trans_id, target->classname, index);
5056 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
5059 char* pkey = osrfHashGet(meta, "primarykey");
5060 osrfHash* fields = osrfHashGet(meta, "fields");
5062 char* id = oilsFMGetString( target, pkey );
5066 "%s updating %s object with %s = %s",
5068 osrfHashGet(meta, "fieldmapper"),
5073 growing_buffer* sql = buffer_init(128);
5074 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
5077 osrfHash* field_def = NULL;
5078 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5079 while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
5081 // Skip virtual fields, and the primary key
5082 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5085 const char* field_name = osrfHashIteratorKey( field_itr );
5086 if( ! strcmp( field_name, pkey ) )
5089 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5091 int value_is_numeric = 0; // boolean
5093 if (field_object && field_object->classname) {
5094 value = oilsFMGetString(
5096 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
5098 } else if( field_object && JSON_BOOL == field_object->type ) {
5099 if( jsonBoolIsTrue( field_object ) )
5100 value = strdup( "t" );
5102 value = strdup( "f" );
5104 value = jsonObjectToSimpleString( field_object );
5105 if( field_object && JSON_NUMBER == field_object->type )
5106 value_is_numeric = 1;
5109 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5110 osrfHashGet(meta, "fieldmapper"), field_name, value);
5112 if (!field_object || field_object->type == JSON_NULL) {
5113 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
5114 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5115 if (first) first = 0;
5116 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5117 buffer_fadd( sql, " %s = NULL", field_name );
5120 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5121 if (first) first = 0;
5122 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5124 const char* numtype = get_datatype( field_def );
5125 if ( !strncmp( numtype, "INT", 3 ) ) {
5126 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
5127 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
5128 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
5130 // Must really be intended as a string, so quote it
5131 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5132 buffer_fadd( sql, " %s = %s", field_name, value );
5134 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5135 osrfAppSessionStatus(
5137 OSRF_STATUS_INTERNALSERVERERROR,
5138 "osrfMethodException",
5140 "Error quoting string -- please see the error log for more details"
5144 osrfHashIteratorFree( field_itr );
5151 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5154 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5155 if (first) first = 0;
5156 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5157 buffer_fadd( sql, " %s = %s", field_name, value );
5160 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5161 osrfAppSessionStatus(
5163 OSRF_STATUS_INTERNALSERVERERROR,
5164 "osrfMethodException",
5166 "Error quoting string -- please see the error log for more details"
5170 osrfHashIteratorFree( field_itr );
5181 osrfHashIteratorFree( field_itr );
5183 jsonObject* obj = jsonNewObject(id);
5185 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5186 dbi_conn_quote_string(dbhandle, &id);
5188 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5190 char* query = buffer_release(sql);
5191 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
5193 dbi_result result = dbi_conn_query(dbhandle, query);
5197 jsonObjectFree(obj);
5198 obj = jsonNewObject(NULL);
5201 "%s ERROR updating %s object with %s = %s",
5203 osrfHashGet(meta, "fieldmapper"),
5214 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
5216 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5218 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
5219 osrfAppSessionStatus(
5221 OSRF_STATUS_BADREQUEST,
5222 "osrfMethodException",
5224 "No active transaction -- required for DELETE"
5230 // The following test is harmless but redundant. If a class is
5231 // readonly, we don't register a delete method for it.
5232 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5233 osrfAppSessionStatus(
5235 OSRF_STATUS_BADREQUEST,
5236 "osrfMethodException",
5238 "Cannot DELETE readonly class"
5244 dbhandle = writehandle;
5248 char* pkey = osrfHashGet(meta, "primarykey");
5256 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
5257 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5262 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5265 if (!verifyObjectPCRUD( ctx, NULL )) {
5270 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
5275 "%s deleting %s object with %s = %s",
5277 osrfHashGet(meta, "fieldmapper"),
5282 obj = jsonNewObject(id);
5284 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5285 dbi_conn_quote_string(writehandle, &id);
5287 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
5290 jsonObjectFree(obj);
5291 obj = jsonNewObject(NULL);
5294 "%s ERROR deleting %s object with %s = %s",
5296 osrfHashGet(meta, "fieldmapper"),
5309 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5310 if(!(result && meta)) return jsonNULL;
5312 jsonObject* object = jsonNewObject(NULL);
5313 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
5315 osrfHash* fields = osrfHashGet(meta, "fields");
5317 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
5321 char dt_string[256];
5325 int columnIndex = 1;
5327 unsigned short type;
5328 const char* columnName;
5330 /* cycle through the column list */
5331 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5333 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5335 fmIndex = -1; // reset the position
5337 /* determine the field type and storage attributes */
5338 type = dbi_result_get_field_type_idx(result, columnIndex);
5339 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5341 /* fetch the fieldmapper index */
5342 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
5344 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
5347 const char* pos = (char*)osrfHashGet(_f, "array_position");
5348 if ( !pos ) continue;
5350 fmIndex = atoi( pos );
5351 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
5356 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5357 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
5362 case DBI_TYPE_INTEGER :
5364 if( attr & DBI_INTEGER_SIZE8 )
5365 jsonObjectSetIndex( object, fmIndex,
5366 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
5368 jsonObjectSetIndex( object, fmIndex,
5369 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
5373 case DBI_TYPE_DECIMAL :
5374 jsonObjectSetIndex( object, fmIndex,
5375 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
5378 case DBI_TYPE_STRING :
5384 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
5389 case DBI_TYPE_DATETIME :
5391 memset(dt_string, '\0', sizeof(dt_string));
5392 memset(&gmdt, '\0', sizeof(gmdt));
5394 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5397 if (!(attr & DBI_DATETIME_DATE)) {
5398 gmtime_r( &_tmp_dt, &gmdt );
5399 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5400 } else if (!(attr & DBI_DATETIME_TIME)) {
5401 localtime_r( &_tmp_dt, &gmdt );
5402 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5404 localtime_r( &_tmp_dt, &gmdt );
5405 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5408 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
5412 case DBI_TYPE_BINARY :
5413 osrfLogError( OSRF_LOG_MARK,
5414 "Can't do binary at column %s : index %d", columnName, columnIndex);
5423 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5424 if(!result) return jsonNULL;
5426 jsonObject* object = jsonNewObject(NULL);
5429 char dt_string[256];
5433 int columnIndex = 1;
5435 unsigned short type;
5436 const char* columnName;
5438 /* cycle through the column list */
5439 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5441 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5443 fmIndex = -1; // reset the position
5445 /* determine the field type and storage attributes */
5446 type = dbi_result_get_field_type_idx(result, columnIndex);
5447 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5449 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5450 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
5455 case DBI_TYPE_INTEGER :
5457 if( attr & DBI_INTEGER_SIZE8 )
5458 jsonObjectSetKey( object, columnName,
5459 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)) );
5461 jsonObjectSetKey( object, columnName,
5462 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
5465 case DBI_TYPE_DECIMAL :
5466 jsonObjectSetKey( object, columnName,
5467 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
5470 case DBI_TYPE_STRING :
5471 jsonObjectSetKey( object, columnName,
5472 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
5475 case DBI_TYPE_DATETIME :
5477 memset(dt_string, '\0', sizeof(dt_string));
5478 memset(&gmdt, '\0', sizeof(gmdt));
5480 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5483 if (!(attr & DBI_DATETIME_DATE)) {
5484 gmtime_r( &_tmp_dt, &gmdt );
5485 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5486 } else if (!(attr & DBI_DATETIME_TIME)) {
5487 localtime_r( &_tmp_dt, &gmdt );
5488 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5490 localtime_r( &_tmp_dt, &gmdt );
5491 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5494 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
5497 case DBI_TYPE_BINARY :
5498 osrfLogError( OSRF_LOG_MARK,
5499 "Can't do binary at column %s : index %d", columnName, columnIndex );
5503 } // end while loop traversing result
5508 // Interpret a string as true or false
5509 static int str_is_true( const char* str ) {
5510 if( NULL == str || strcasecmp( str, "true" ) )
5516 // Interpret a jsonObject as true or false
5517 static int obj_is_true( const jsonObject* obj ) {
5520 else switch( obj->type )
5528 if( strcasecmp( obj->value.s, "true" ) )
5532 case JSON_NUMBER : // Support 1/0 for perl's sake
5533 if( jsonObjectGetNumber( obj ) == 1.0 )
5542 // Translate a numeric code into a text string identifying a type of
5543 // jsonObject. To be used for building error messages.
5544 static const char* json_type( int code ) {
5550 return "JSON_ARRAY";
5552 return "JSON_STRING";
5554 return "JSON_NUMBER";
5560 return "(unrecognized)";
5564 // Extract the "primitive" attribute from an IDL field definition.
5565 // If we haven't initialized the app, then we must be running in
5566 // some kind of testbed. In that case, default to "string".
5567 static const char* get_primitive( osrfHash* field ) {
5568 const char* s = osrfHashGet( field, "primitive" );
5570 if( child_initialized )
5573 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5575 osrfHashGet( field, "name" )
5583 // Extract the "datatype" attribute from an IDL field definition.
5584 // If we haven't initialized the app, then we must be running in
5585 // some kind of testbed. In that case, default to to NUMERIC,
5586 // since we look at the datatype only for numbers.
5587 static const char* get_datatype( osrfHash* field ) {
5588 const char* s = osrfHashGet( field, "datatype" );
5590 if( child_initialized )
5593 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5595 osrfHashGet( field, "name" )
5604 If the input string is potentially a valid SQL identifier, return 1.
5607 Purpose: to prevent certain kinds of SQL injection. To that end we
5608 don't necessarily need to follow all the rules exactly, such as requiring
5609 that the first character not be a digit.
5611 We allow leading and trailing white space. In between, we do not allow
5612 punctuation (except for underscores and dollar signs), control
5613 characters, or embedded white space.
5615 More pedantically we should allow quoted identifiers containing arbitrary
5616 characters, but for the foreseeable future such quoted identifiers are not
5617 likely to be an issue.
5619 static int is_identifier( const char* s) {
5623 // Skip leading white space
5624 while( isspace( (unsigned char) *s ) )
5628 return 0; // Nothing but white space? Not okay.
5630 // Check each character until we reach white space or
5631 // end-of-string. Letters, digits, underscores, and
5632 // dollar signs are okay. With the exception of periods
5633 // (as in schema.identifier), control characters and other
5634 // punctuation characters are not okay. Anything else
5635 // is okay -- it could for example be part of a multibyte
5636 // UTF8 character such as a letter with diacritical marks,
5637 // and those are allowed.
5639 if( isalnum( (unsigned char) *s )
5643 ; // Fine; keep going
5644 else if( ispunct( (unsigned char) *s )
5645 || iscntrl( (unsigned char) *s ) )
5648 } while( *s && ! isspace( (unsigned char) *s ) );
5650 // If we found any white space in the above loop,
5651 // the rest had better be all white space.
5653 while( isspace( (unsigned char) *s ) )
5657 return 0; // White space was embedded within non-white space
5663 Determine whether to accept a character string as a comparison operator.
5664 Return 1 if it's good, or 0 if it's bad.
5666 We don't validate it for real. We just make sure that it doesn't contain
5667 any semicolons or white space (with special exceptions for a few specific
5668 operators). The idea is to block certain kinds of SQL injection. If it
5669 has no semicolons or white space but it's still not a valid operator, then
5670 the database will complain.
5672 Another approach would be to compare the string against a short list of
5673 approved operators. We don't do that because we want to allow custom
5674 operators like ">100*", which would be difficult or impossible to
5675 express otherwise in a JSON query.
5677 static int is_good_operator( const char* op ) {
5678 if( !op ) return 0; // Sanity check
5682 if( isspace( (unsigned char) *s ) ) {
5683 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
5684 // and IS NOT DISTINCT FROM.
5685 if( !strcasecmp( op, "similar to" ) )
5687 else if( !strcasecmp( op, "is distinct from" ) )
5689 else if( !strcasecmp( op, "is not distinct from" ) )
5694 else if( ';' == *s )
5701 /* ----------------------------------------------------------------------------------
5702 The following machinery supports a stack of query frames for use by SELECT().
5704 A query frame caches information about one level of a SELECT query. When we enter
5705 a subquery, we push another query frame onto the stack, and pop it off when we leave.
5707 The query frame stores information about the core class, and about any joined classes
5710 The main purpose is to map table aliases to classes and tables, so that a query can
5711 join to the same table more than once. A secondary goal is to reduce the number of
5712 lookups in the IDL by caching the results.
5713 ----------------------------------------------------------------------------------*/
5715 #define STATIC_CLASS_INFO_COUNT 3
5717 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
5719 /* ---------------------------------------------------------------------------
5720 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5722 ---------------------------------------------------------------------------*/
5723 static ClassInfo* allocate_class_info( void ) {
5724 // In order to reduce the number of mallocs and frees, we return a static
5725 // instance of ClassInfo, if we can find one that we're not already using.
5726 // We rely on the fact that the compiler will implicitly initialize the
5727 // static instances so that in_use == 0.
5730 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5731 if( ! static_class_info[ i ].in_use ) {
5732 static_class_info[ i ].in_use = 1;
5733 return static_class_info + i;
5737 // The static ones are all in use. Malloc one.
5739 return safe_malloc( sizeof( ClassInfo ) );
5742 /* --------------------------------------------------------------------------
5743 Free any malloc'd memory owned by a ClassInfo; return it to a pristine state
5744 ---------------------------------------------------------------------------*/
5745 static void clear_class_info( ClassInfo* info ) {
5750 // Free any malloc'd strings
5752 if( info->alias != info->alias_store )
5753 free( info->alias );
5755 if( info->class_name != info->class_name_store )
5756 free( info->class_name );
5758 free( info->source_def );
5760 info->alias = info->class_name = info->source_def = NULL;
5764 /* --------------------------------------------------------------------------
5765 Deallocate a ClassInfo and everything it owns
5766 ---------------------------------------------------------------------------*/
5767 static void free_class_info( ClassInfo* info ) {
5772 clear_class_info( info );
5774 // If it's one of the static instances, just mark it as not in use
5777 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5778 if( info == static_class_info + i ) {
5779 static_class_info[ i ].in_use = 0;
5784 // Otherwise it must have been malloc'd, so free it
5789 /* --------------------------------------------------------------------------
5790 Populate an already-allocated ClassInfo. Return 0 if successful, 1 if not.
5791 ---------------------------------------------------------------------------*/
5792 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
5795 osrfLogError( OSRF_LOG_MARK,
5796 "%s ERROR: No ClassInfo available to populate", MODULENAME );
5797 info->alias = info->class_name = info->source_def = NULL;
5798 info->class_def = info->fields = info->links = NULL;
5803 osrfLogError( OSRF_LOG_MARK,
5804 "%s ERROR: No class name provided for lookup", MODULENAME );
5805 info->alias = info->class_name = info->source_def = NULL;
5806 info->class_def = info->fields = info->links = NULL;
5810 // Alias defaults to class name if not supplied
5811 if( ! alias || ! alias[ 0 ] )
5814 // Look up class info in the IDL
5815 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
5817 osrfLogError( OSRF_LOG_MARK,
5818 "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
5819 info->alias = info->class_name = info->source_def = NULL;
5820 info->class_def = info->fields = info->links = NULL;
5822 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
5823 osrfLogError( OSRF_LOG_MARK,
5824 "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
5825 info->alias = info->class_name = info->source_def = NULL;
5826 info->class_def = info->fields = info->links = NULL;
5830 osrfHash* links = osrfHashGet( class_def, "links" );
5832 osrfLogError( OSRF_LOG_MARK,
5833 "%s ERROR: No links 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 osrfHash* fields = osrfHashGet( class_def, "fields" );
5841 osrfLogError( OSRF_LOG_MARK,
5842 "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
5843 info->alias = info->class_name = info->source_def = NULL;
5844 info->class_def = info->fields = info->links = NULL;
5848 char* source_def = getSourceDefinition( class_def );
5852 // We got everything we need, so populate the ClassInfo
5853 if( strlen( alias ) > ALIAS_STORE_SIZE )
5854 info->alias = strdup( alias );
5856 strcpy( info->alias_store, alias );
5857 info->alias = info->alias_store;
5860 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
5861 info->class_name = strdup( class );
5863 strcpy( info->class_name_store, class );
5864 info->class_name = info->class_name_store;
5867 info->source_def = source_def;
5869 info->class_def = class_def;
5870 info->links = links;
5871 info->fields = fields;
5876 #define STATIC_FRAME_COUNT 3
5878 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
5880 /* ---------------------------------------------------------------------------
5881 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5883 ---------------------------------------------------------------------------*/
5884 static QueryFrame* allocate_frame( void ) {
5885 // In order to reduce the number of mallocs and frees, we return a static
5886 // instance of QueryFrame, if we can find one that we're not already using.
5887 // We rely on the fact that the compiler will implicitly initialize the
5888 // static instances so that in_use == 0.
5891 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5892 if( ! static_frame[ i ].in_use ) {
5893 static_frame[ i ].in_use = 1;
5894 return static_frame + i;
5898 // The static ones are all in use. Malloc one.
5900 return safe_malloc( sizeof( QueryFrame ) );
5903 /* --------------------------------------------------------------------------
5904 Free a QueryFrame, and all the memory it owns.
5905 ---------------------------------------------------------------------------*/
5906 static void free_query_frame( QueryFrame* frame ) {
5911 clear_class_info( &frame->core );
5913 // Free the join list
5915 ClassInfo* info = frame->join_list;
5918 free_class_info( info );
5922 frame->join_list = NULL;
5925 // If the frame is a static instance, just mark it as unused
5927 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5928 if( frame == static_frame + i ) {
5929 static_frame[ i ].in_use = 0;
5934 // Otherwise it must have been malloc'd, so free it
5939 /* --------------------------------------------------------------------------
5940 Search a given QueryFrame for a specified alias. If you find it, return
5941 a pointer to the corresponding ClassInfo. Otherwise return NULL.
5942 ---------------------------------------------------------------------------*/
5943 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
5944 if( ! frame || ! target ) {
5948 ClassInfo* found_class = NULL;
5950 if( !strcmp( target, frame->core.alias ) )
5951 return &(frame->core);
5953 ClassInfo* curr_class = frame->join_list;
5954 while( curr_class ) {
5955 if( strcmp( target, curr_class->alias ) )
5956 curr_class = curr_class->next;
5958 found_class = curr_class;
5967 /* --------------------------------------------------------------------------
5968 Push a new (blank) QueryFrame onto the stack.
5969 ---------------------------------------------------------------------------*/
5970 static void push_query_frame( void ) {
5971 QueryFrame* frame = allocate_frame();
5972 frame->join_list = NULL;
5973 frame->next = curr_query;
5975 // Initialize the ClassInfo for the core class
5976 ClassInfo* core = &frame->core;
5977 core->alias = core->class_name = core->source_def = NULL;
5978 core->class_def = core->fields = core->links = NULL;
5983 /* --------------------------------------------------------------------------
5984 Pop a QueryFrame off the stack and destroy it
5985 ---------------------------------------------------------------------------*/
5986 static void pop_query_frame( void ) {
5991 QueryFrame* popped = curr_query;
5992 curr_query = popped->next;
5994 free_query_frame( popped );
5997 /* --------------------------------------------------------------------------
5998 Populate the ClassInfo for the core class. Return 0 if successful, 1 if not.
5999 ---------------------------------------------------------------------------*/
6000 static int add_query_core( const char* alias, const char* class_name ) {
6003 if( ! curr_query ) {
6004 osrfLogError( OSRF_LOG_MARK,
6005 "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
6007 } else if( curr_query->core.alias ) {
6008 osrfLogError( OSRF_LOG_MARK,
6009 "%s ERROR: Core class %s already populated as %s",
6010 MODULENAME, curr_query->core.class_name, curr_query->core.alias );
6014 build_class_info( &curr_query->core, alias, class_name );
6015 if( curr_query->core.alias )
6018 osrfLogError( OSRF_LOG_MARK,
6019 "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
6024 /* --------------------------------------------------------------------------
6025 Search the current QueryFrame for a specified alias. If you find it,
6026 return a pointer to the corresponding ClassInfo. Otherwise return NULL.
6027 ---------------------------------------------------------------------------*/
6028 static ClassInfo* search_alias( const char* target ) {
6029 return search_alias_in_frame( curr_query, target );
6032 /* --------------------------------------------------------------------------
6033 Search all levels of query for a specified alias, starting with the
6034 current query. If you find it, return a pointer to the corresponding
6035 ClassInfo. Otherwise return NULL.
6036 ---------------------------------------------------------------------------*/
6037 static ClassInfo* search_all_alias( const char* target ) {
6038 ClassInfo* found_class = NULL;
6039 QueryFrame* curr_frame = curr_query;
6041 while( curr_frame ) {
6042 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6045 curr_frame = curr_frame->next;
6051 /* --------------------------------------------------------------------------
6052 Add a class to the list of classes joined to the current query.
6053 ---------------------------------------------------------------------------*/
6054 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6056 if( ! classname || ! *classname ) { // sanity check
6057 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6064 const ClassInfo* conflict = search_alias( alias );
6066 osrfLogError( OSRF_LOG_MARK,
6067 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6068 MODULENAME, alias, conflict->class_name );
6072 ClassInfo* info = allocate_class_info();
6074 if( build_class_info( info, alias, classname ) ) {
6075 free_class_info( info );
6079 // Add the new ClassInfo to the join list of the current QueryFrame
6080 info->next = curr_query->join_list;
6081 curr_query->join_list = info;
6086 /* --------------------------------------------------------------------------
6087 Destroy all nodes on the query stack.
6088 ---------------------------------------------------------------------------*/
6089 static void clear_query_stack( void ) {