2 #include "opensrf/osrf_application.h"
3 #include "opensrf/osrf_settings.h"
4 #include "opensrf/osrf_message.h"
5 #include "opensrf/utils.h"
6 #include "opensrf/osrf_json.h"
7 #include "opensrf/log.h"
8 #include "openils/oils_utils.h"
17 # define MODULENAME "open-ils.reporter-store"
20 # define MODULENAME "open-ils.pcrud"
22 # define MODULENAME "open-ils.cstore"
26 // The next four macros are OR'd together as needed to form a set
27 // of bitflags. SUBCOMBO enables an extra pair of parentheses when
28 // nesting one UNION, INTERSECT or EXCEPT inside another.
29 // SUBSELECT tells us we're in a subquery, so don't add the
30 // terminal semicolon yet.
33 #define DISABLE_I18N 2
34 #define SELECT_DISTINCT 1
39 struct ClassInfoStruct;
40 typedef struct ClassInfoStruct ClassInfo;
42 #define ALIAS_STORE_SIZE 16
43 #define CLASS_NAME_STORE_SIZE 16
45 struct ClassInfoStruct {
49 osrfHash* class_def; // Points into IDL
50 osrfHash* fields; // Points into IDL
51 osrfHash* links; // Points into IDL
53 // The remaining members are private and internal. Client code should not
54 // access them directly.
56 ClassInfo* next; // Supports linked list of joined classes
57 int in_use; // boolean
59 // We usually store the alias and class name in the following arrays, and
60 // point the corresponding pointers at them. When the string is too big
61 // for the array (which will probably never happen in practice), we strdup it.
63 char alias_store[ ALIAS_STORE_SIZE + 1 ];
64 char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
67 struct QueryFrameStruct;
68 typedef struct QueryFrameStruct QueryFrame;
70 struct QueryFrameStruct {
72 ClassInfo* join_list; // linked list of classes joined to the core class
73 QueryFrame* next; // implements stack as linked list
74 int in_use; // boolean
77 int osrfAppChildInit();
78 int osrfAppInitialize();
79 void osrfAppChildExit();
81 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
83 int beginTransaction ( osrfMethodContext* );
84 int commitTransaction ( osrfMethodContext* );
85 int rollbackTransaction ( osrfMethodContext* );
87 int setSavepoint ( osrfMethodContext* );
88 int releaseSavepoint ( osrfMethodContext* );
89 int rollbackSavepoint ( osrfMethodContext* );
91 int doJSONSearch ( osrfMethodContext* );
93 int dispatchCRUDMethod ( osrfMethodContext* );
94 static jsonObject* doCreate ( osrfMethodContext*, int* );
95 static jsonObject* doRetrieve ( osrfMethodContext*, int* );
96 static jsonObject* doUpdate ( osrfMethodContext*, int* );
97 static jsonObject* doDelete ( osrfMethodContext*, int* );
98 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
99 jsonObject* where_hash, jsonObject* query_hash, int* err );
100 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
101 static jsonObject* oilsMakeJSONFromResult( dbi_result );
103 static char* searchSimplePredicate ( const char* op, const char* class_alias,
104 osrfHash* field, const jsonObject* node );
105 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
106 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject*);
107 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*, const char* );
108 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
109 static char* searchINPredicate ( const char*, osrfHash*,
110 jsonObject*, const char*, osrfMethodContext* );
111 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
112 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
113 static char* searchWHERE ( const jsonObject*, const ClassInfo*, int, osrfMethodContext* );
114 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
115 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
117 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
119 void userDataFree( void* );
120 static void sessionDataFree( char*, void* );
121 static char* getSourceDefinition( osrfHash* );
122 static int str_is_true( const char* str );
123 static int obj_is_true( const jsonObject* obj );
124 static const char* json_type( int code );
125 static const char* get_primitive( osrfHash* field );
126 static const char* get_datatype( osrfHash* field );
127 static int is_identifier( const char* s);
128 static int is_good_operator( const char* op );
129 static void pop_query_frame( void );
130 static void push_query_frame( void );
131 static int add_query_core( const char* alias, const char* class_name );
132 static ClassInfo* search_alias( const char* target );
133 static ClassInfo* search_all_alias( const char* target );
134 static ClassInfo* add_joined_class( const char* alias, const char* classname );
135 static void clear_query_stack( void );
138 static jsonObject* verifyUserPCRUD( osrfMethodContext* );
139 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
140 static char* org_tree_root( osrfMethodContext* ctx );
141 static jsonObject* single_hash( const char* key, const char* value );
144 static int child_initialized = 0; /* boolean */
146 static dbi_conn writehandle; /* our MASTER db connection */
147 static dbi_conn dbhandle; /* our CURRENT db connection */
148 //static osrfHash * readHandles;
149 static jsonObject* const jsonNULL = NULL; //
150 static int max_flesh_depth = 100;
152 // The following points the top of a stack of QueryFrames. It's a little
153 // confusing because the top level of the query is at the bottom of the stack.
154 static QueryFrame* curr_query = NULL;
156 /* called when this process is about to exit */
157 void osrfAppChildExit() {
158 osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
161 if (writehandle == dbhandle) same = 1;
163 dbi_conn_query(writehandle, "ROLLBACK;");
164 dbi_conn_close(writehandle);
167 if (dbhandle && !same)
168 dbi_conn_close(dbhandle);
170 // XXX add cleanup of readHandles whenever that gets used
175 int osrfAppInitialize() {
177 osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
178 osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
180 if (!oilsIDLInit( osrf_settings_host_value("/IDL") )) return 1; /* return non-zero to indicate error */
182 growing_buffer* method_name = buffer_init(64);
184 // Generic search thingy
185 buffer_add(method_name, MODULENAME);
186 buffer_add(method_name, ".json_query");
187 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
188 "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
191 // first we register all the transaction and savepoint methods
192 buffer_reset(method_name);
193 OSRF_BUFFER_ADD(method_name, MODULENAME);
194 OSRF_BUFFER_ADD(method_name, ".transaction.begin");
195 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
196 "beginTransaction", "", 0, 0 );
198 buffer_reset(method_name);
199 OSRF_BUFFER_ADD(method_name, MODULENAME);
200 OSRF_BUFFER_ADD(method_name, ".transaction.commit");
201 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
202 "commitTransaction", "", 0, 0 );
204 buffer_reset(method_name);
205 OSRF_BUFFER_ADD(method_name, MODULENAME);
206 OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
207 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
208 "rollbackTransaction", "", 0, 0 );
210 buffer_reset(method_name);
211 OSRF_BUFFER_ADD(method_name, MODULENAME);
212 OSRF_BUFFER_ADD(method_name, ".savepoint.set");
213 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
214 "setSavepoint", "", 1, 0 );
216 buffer_reset(method_name);
217 OSRF_BUFFER_ADD(method_name, MODULENAME);
218 OSRF_BUFFER_ADD(method_name, ".savepoint.release");
219 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
220 "releaseSavepoint", "", 1, 0 );
222 buffer_reset(method_name);
223 OSRF_BUFFER_ADD(method_name, MODULENAME);
224 OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
225 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
226 "rollbackSavepoint", "", 1, 0 );
228 static const char* global_method[] = {
236 const int global_method_count
237 = sizeof( global_method ) / sizeof ( global_method[0] );
239 unsigned long class_count = osrfHashGetCount( oilsIDL() );
240 osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
241 osrfLogDebug(OSRF_LOG_MARK,
242 "At most %lu methods will be generated",
243 (unsigned long) (class_count * global_method_count) );
245 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
246 osrfHash* idlClass = NULL;
248 // For each class in IDL...
249 while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
251 const char* classname = osrfHashIteratorKey( class_itr );
252 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
254 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), MODULENAME )) {
255 osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on", MODULENAME, classname);
259 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
260 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
264 // Look up some other attributes of the current class
265 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
266 if( !idlClass_fieldmapper ) {
267 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL", classname );
272 osrfHash* idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
273 if (!idlClass_permacrud) {
274 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no permacrud in IDL", classname );
278 const char* readonly = osrfHashGet(idlClass, "readonly");
281 for( i = 0; i < global_method_count; ++i ) { // for each global method
282 const char* method_type = global_method[ i ];
283 osrfLogDebug(OSRF_LOG_MARK,
284 "Using files to build %s class methods for %s", method_type, classname);
287 const char* tmp_method = method_type;
288 if ( *tmp_method == 'i' || *tmp_method == 's') {
289 tmp_method = "retrieve";
291 if (!osrfHashGet( idlClass_permacrud, tmp_method )) continue;
294 if ( str_is_true( readonly ) &&
295 ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd')
298 buffer_reset( method_name );
300 buffer_fadd(method_name, "%s.%s.%s", MODULENAME, method_type, classname);
304 char* _fm = strdup( idlClass_fieldmapper );
305 part = strtok_r(_fm, ":", &st_tmp);
307 buffer_fadd(method_name, "%s.direct.%s", MODULENAME, part);
309 while ((part = strtok_r(NULL, ":", &st_tmp))) {
310 OSRF_BUFFER_ADD_CHAR(method_name, '.');
311 OSRF_BUFFER_ADD(method_name, part);
313 OSRF_BUFFER_ADD_CHAR(method_name, '.');
314 OSRF_BUFFER_ADD(method_name, method_type);
318 char* method = buffer_data(method_name);
321 if (*method_type == 'i' || *method_type == 's') {
322 flags = flags | OSRF_METHOD_STREAMING;
325 osrfHash* method_meta = osrfNewHash();
326 osrfHashSet( method_meta, idlClass, "class");
327 osrfHashSet( method_meta, method, "methodname" );
328 osrfHashSet( method_meta, strdup(method_type), "methodtype" );
330 osrfAppRegisterExtendedMethod(
333 "dispatchCRUDMethod",
341 } // end for each global method
342 } // end for each class in IDL
344 buffer_free( method_name );
345 osrfHashIteratorFree( class_itr );
350 static char* getSourceDefinition( osrfHash* class ) {
352 char* tabledef = osrfHashGet(class, "tablename");
355 tabledef = strdup(tabledef);
357 tabledef = osrfHashGet(class, "source_definition");
359 growing_buffer* tablebuf = buffer_init(128);
360 buffer_fadd( tablebuf, "(%s)", tabledef );
361 tabledef = buffer_release(tablebuf);
363 const char* classname = osrfHashGet( class, "classname" );
368 "%s ERROR No tablename or source_definition for class \"%s\"",
379 * Connects to the database
381 int osrfAppChildInit() {
383 osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
384 dbi_initialize(NULL);
385 osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
387 char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
388 char* user = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
389 char* host = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
390 char* port = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
391 char* db = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
392 char* pw = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
393 char* md = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion", MODULENAME);
395 osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
396 writehandle = dbi_conn_new(driver);
399 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
402 osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
404 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
405 "port=%s, user=%s, pw=%s, db=%s", MODULENAME, host, port, user, pw, db );
407 if(host) dbi_conn_set_option(writehandle, "host", host );
408 if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
409 if(user) dbi_conn_set_option(writehandle, "username", user);
410 if(pw) dbi_conn_set_option(writehandle, "password", pw );
411 if(db) dbi_conn_set_option(writehandle, "dbname", db );
413 if(md) max_flesh_depth = atoi(md);
414 if(max_flesh_depth < 0) max_flesh_depth = 1;
415 if(max_flesh_depth > 1000) max_flesh_depth = 1000;
424 if (dbi_conn_connect(writehandle) < 0) {
426 if (dbi_conn_connect(writehandle) < 0) {
427 dbi_conn_error(writehandle, &err);
428 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
433 osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
435 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
436 osrfHash* class = NULL;
438 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
439 const char* classname = osrfHashIteratorKey( class_itr );
440 osrfHash* fields = osrfHashGet( class, "fields" );
442 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
443 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
447 char* tabledef = getSourceDefinition(class);
449 tabledef = strdup( "(null)" );
451 growing_buffer* sql_buf = buffer_init(32);
452 buffer_fadd( sql_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
456 char* sql = buffer_release(sql_buf);
457 osrfLogDebug(OSRF_LOG_MARK, "%s Investigatory SQL = %s", MODULENAME, sql);
459 dbi_result result = dbi_conn_query(writehandle, sql);
465 const char* columnName;
467 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
469 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
471 /* fetch the fieldmapper index */
472 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
474 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", (char*)columnName);
476 /* determine the field type and storage attributes */
478 switch( dbi_result_get_field_type_idx(result, columnIndex) ) {
480 case DBI_TYPE_INTEGER : {
482 if ( !osrfHashGet(_f, "primitive") )
483 osrfHashSet(_f,"number", "primitive");
485 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
486 if( attr & DBI_INTEGER_SIZE8 )
487 osrfHashSet(_f,"INT8", "datatype");
489 osrfHashSet(_f,"INT", "datatype");
492 case DBI_TYPE_DECIMAL :
493 if ( !osrfHashGet(_f, "primitive") )
494 osrfHashSet(_f,"number", "primitive");
496 osrfHashSet(_f,"NUMERIC", "datatype");
499 case DBI_TYPE_STRING :
500 if ( !osrfHashGet(_f, "primitive") )
501 osrfHashSet(_f,"string", "primitive");
502 osrfHashSet(_f,"TEXT", "datatype");
505 case DBI_TYPE_DATETIME :
506 if ( !osrfHashGet(_f, "primitive") )
507 osrfHashSet(_f,"string", "primitive");
509 osrfHashSet(_f,"TIMESTAMP", "datatype");
512 case DBI_TYPE_BINARY :
513 if ( !osrfHashGet(_f, "primitive") )
514 osrfHashSet(_f,"string", "primitive");
516 osrfHashSet(_f,"BYTEA", "datatype");
521 "Setting [%s] to primitive [%s] and datatype [%s]...",
523 osrfHashGet(_f, "primitive"),
524 osrfHashGet(_f, "datatype")
528 } // end while loop for traversing result
529 dbi_result_free(result);
531 osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", (char*)classname);
533 } // end for each class in IDL
535 osrfHashIteratorFree( class_itr );
536 child_initialized = 1;
541 This function is a sleazy hack intended *only* for testing and
542 debugging. Any real server process should initialize the
543 database connection by calling osrfAppChildInit().
545 void set_cstore_dbi_conn( dbi_conn conn ) {
546 dbhandle = writehandle = conn;
549 void userDataFree( void* blob ) {
550 osrfHashFree( (osrfHash*)blob );
554 static void sessionDataFree( char* key, void* item ) {
555 if (!(strcmp(key,"xact_id"))) {
557 dbi_conn_query(writehandle, "ROLLBACK;");
564 int beginTransaction ( osrfMethodContext* ctx ) {
565 if(osrfMethodVerifyContext( ctx )) {
566 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
571 jsonObject* user = verifyUserPCRUD( ctx );
572 if (!user) return -1;
573 jsonObjectFree(user);
576 dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
578 osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
579 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error starting transaction" );
582 jsonObject* ret = jsonNewObject(ctx->session->session_id);
583 osrfAppRespondComplete( ctx, ret );
586 if (!ctx->session->userData) {
587 ctx->session->userData = osrfNewHash();
588 osrfHashSetCallback((osrfHash*)ctx->session->userData, &sessionDataFree);
591 osrfHashSet( (osrfHash*)ctx->session->userData, strdup( ctx->session->session_id ), "xact_id" );
592 ctx->session->userDataFree = &userDataFree;
598 int setSavepoint ( osrfMethodContext* ctx ) {
599 if(osrfMethodVerifyContext( ctx )) {
600 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
607 jsonObject* user = verifyUserPCRUD( ctx );
608 if (!user) return -1;
609 jsonObjectFree(user);
612 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
613 osrfAppSessionStatus(
615 OSRF_STATUS_INTERNALSERVERERROR,
616 "osrfMethodException",
618 "No active transaction -- required for savepoints"
623 const char* spName = jsonObjectGetString(jsonObjectGetIndex(ctx->params, spNamePos));
625 dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
629 "%s: Error creating savepoint %s in transaction %s",
632 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
634 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
635 "osrfMethodException", ctx->request, "Error creating savepoint" );
638 jsonObject* ret = jsonNewObject(spName);
639 osrfAppRespondComplete( ctx, ret );
645 int releaseSavepoint ( osrfMethodContext* ctx ) {
646 if(osrfMethodVerifyContext( ctx )) {
647 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
654 jsonObject* user = verifyUserPCRUD( ctx );
655 if (!user) return -1;
656 jsonObjectFree(user);
659 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
660 osrfAppSessionStatus(
662 OSRF_STATUS_INTERNALSERVERERROR,
663 "osrfMethodException",
665 "No active transaction -- required for savepoints"
670 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
672 dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
676 "%s: Error releasing savepoint %s in transaction %s",
679 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
681 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
682 "osrfMethodException", ctx->request, "Error releasing savepoint" );
685 jsonObject* ret = jsonNewObject(spName);
686 osrfAppRespondComplete( ctx, ret );
692 int rollbackSavepoint ( osrfMethodContext* ctx ) {
693 if(osrfMethodVerifyContext( ctx )) {
694 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
701 jsonObject* user = verifyUserPCRUD( ctx );
702 if (!user) return -1;
703 jsonObjectFree(user);
706 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
707 osrfAppSessionStatus(
709 OSRF_STATUS_INTERNALSERVERERROR,
710 "osrfMethodException",
712 "No active transaction -- required for savepoints"
717 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
719 dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
723 "%s: Error rolling back savepoint %s in transaction %s",
726 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
728 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
729 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
732 jsonObject* ret = jsonNewObject(spName);
733 osrfAppRespondComplete( ctx, ret );
739 int commitTransaction ( osrfMethodContext* ctx ) {
740 if(osrfMethodVerifyContext( ctx )) {
741 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
746 jsonObject* user = verifyUserPCRUD( ctx );
747 if (!user) return -1;
748 jsonObjectFree(user);
751 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
752 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to commit" );
756 dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
758 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
759 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error committing transaction" );
762 osrfHashRemove(ctx->session->userData, "xact_id");
763 jsonObject* ret = jsonNewObject(ctx->session->session_id);
764 osrfAppRespondComplete( ctx, ret );
770 int rollbackTransaction ( osrfMethodContext* ctx ) {
771 if(osrfMethodVerifyContext( ctx )) {
772 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
777 jsonObject* user = verifyUserPCRUD( ctx );
778 if (!user) return -1;
779 jsonObjectFree(user);
782 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
783 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to roll back" );
787 dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
789 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
790 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error rolling back transaction" );
793 osrfHashRemove(ctx->session->userData, "xact_id");
794 jsonObject* ret = jsonNewObject(ctx->session->session_id);
795 osrfAppRespondComplete( ctx, ret );
801 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
802 if(osrfMethodVerifyContext( ctx )) {
803 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
807 osrfHash* meta = (osrfHash*) ctx->method->userData;
808 osrfHash* class_obj = osrfHashGet( meta, "class" );
812 const char* methodtype = osrfHashGet(meta, "methodtype");
813 jsonObject * obj = NULL;
815 if (!strcmp(methodtype, "create")) {
816 obj = doCreate(ctx, &err);
817 osrfAppRespondComplete( ctx, obj );
819 else if (!strcmp(methodtype, "retrieve")) {
820 obj = doRetrieve(ctx, &err);
821 osrfAppRespondComplete( ctx, obj );
823 else if (!strcmp(methodtype, "update")) {
824 obj = doUpdate(ctx, &err);
825 osrfAppRespondComplete( ctx, obj );
827 else if (!strcmp(methodtype, "delete")) {
828 obj = doDelete(ctx, &err);
829 osrfAppRespondComplete( ctx, obj );
831 else if (!strcmp(methodtype, "search")) {
833 jsonObject* where_clause;
834 jsonObject* rest_of_query;
837 where_clause = jsonObjectGetIndex( ctx->params, 1 );
838 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
840 where_clause = jsonObjectGetIndex( ctx->params, 0 );
841 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
844 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
849 unsigned long res_idx = 0;
850 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
852 if(!verifyObjectPCRUD(ctx, cur)) continue;
854 osrfAppRespond( ctx, cur );
856 osrfAppRespondComplete( ctx, NULL );
858 } else if (!strcmp(methodtype, "id_list")) {
860 jsonObject* where_clause;
861 jsonObject* rest_of_query;
863 // We use the where clause without change. But we need
864 // to massage the rest of the query, so we work with a copy
865 // of it instead of modifying the original.
867 where_clause = jsonObjectGetIndex( ctx->params, 1 );
868 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
870 where_clause = jsonObjectGetIndex( ctx->params, 0 );
871 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
874 if ( rest_of_query ) {
875 jsonObjectRemoveKey( rest_of_query, "select" );
876 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
877 jsonObjectRemoveKey( rest_of_query, "flesh" );
878 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
880 rest_of_query = jsonNewObjectType( JSON_HASH );
883 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
885 // Build a SELECT list containing just the primary key,
886 // i.e. like { "classname":["keyname"] }
887 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
888 jsonObjectPush( col_list_obj, // Load array with name of primary key
889 jsonNewObject( osrfHashGet( class_obj, "primarykey" ) ) );
890 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
891 jsonObjectSetKey( select_clause, osrfHashGet( class_obj, "classname" ), col_list_obj );
893 jsonObjectSetKey( rest_of_query, "select", select_clause );
895 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
897 jsonObjectFree( rest_of_query );
901 unsigned long res_idx = 0;
902 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
904 if(!verifyObjectPCRUD(ctx, cur)) continue;
908 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
911 osrfAppRespondComplete( ctx, NULL );
914 osrfAppRespondComplete( ctx, obj );
922 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
925 osrfHash* meta = (osrfHash*) ctx->method->userData;
926 osrfHash* class = osrfHashGet( meta, "class" );
928 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
930 const char* temp_classname = param->classname;
931 if( ! temp_classname )
932 temp_classname = "(null)";
934 growing_buffer* msg = buffer_init(128);
937 "%s: %s method for type %s was passed a %s",
939 osrfHashGet(meta, "methodtype"),
940 osrfHashGet(class, "classname"),
944 char* m = buffer_release(msg);
945 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException", ctx->request, m );
953 ret = verifyObjectPCRUD( ctx, param );
961 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
962 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
963 jsonObject* auth_object = jsonNewObject(auth);
964 jsonObject* user = oilsUtilsQuickReq("open-ils.auth","open-ils.auth.session.retrieve", auth_object);
965 jsonObjectFree(auth_object);
967 if (!user->classname || strcmp(user->classname, "au")) {
969 growing_buffer* msg = buffer_init(128);
972 "%s: permacrud received a bad auth token: %s",
977 char* m = buffer_release(msg);
978 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException", ctx->request, m );
981 jsonObjectFree(user);
989 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
991 dbhandle = writehandle;
993 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
994 osrfHash* class = osrfHashGet( method_metadata, "class" );
995 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
998 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
999 method_type = "retrieve"; // search and id_list are equivalant to retrieve for this
1000 } else if ( *method_type == 'u' || *method_type == 'd' ) {
1001 fetch = 1; // MUST go to the db for the object for update and delete
1004 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
1007 // No permacrud for this method type on this class
1009 growing_buffer* msg = buffer_init(128);
1012 "%s: %s on class %s has no permacrud IDL entry",
1014 osrfHashGet(method_metadata, "methodtype"),
1015 osrfHashGet(class, "classname")
1018 char* m = buffer_release(msg);
1019 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN, "osrfMethodException", ctx->request, m );
1026 jsonObject* user = verifyUserPCRUD( ctx );
1027 if (!user) return 0;
1029 int userid = atoi( oilsFMGetString( user, "id" ) );
1030 jsonObjectFree(user);
1032 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
1033 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
1034 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
1036 osrfStringArray* context_org_array = osrfNewStringArray(1);
1039 char* pkey_value = NULL;
1040 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1041 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions required, fetching top of the org tree" );
1043 // check for perm at top of org tree
1044 char* org_tree_root_id = org_tree_root( ctx );
1045 if( org_tree_root_id ) {
1046 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1047 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1049 osrfStringArrayFree( context_org_array );
1054 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, fetching context org ids" );
1055 const char* pkey = osrfHashGet(class, "primarykey");
1056 jsonObject *param = NULL;
1058 if (obj->classname) {
1059 pkey_value = oilsFMGetString( obj, pkey );
1060 if (!fetch) param = jsonObjectClone(obj);
1061 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s", pkey_value );
1063 pkey_value = jsonObjectToSimpleString( obj );
1065 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value of %s and retrieving from the database", pkey_value );
1069 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1070 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1071 jsonObjectFree(_tmp_params);
1073 param = jsonObjectExtractIndex(_list, 0);
1074 jsonObjectFree(_list);
1078 osrfLogDebug( OSRF_LOG_MARK, "Object not found in the database with primary key %s of %s", pkey, pkey_value );
1080 growing_buffer* msg = buffer_init(128);
1083 "%s: no object found with primary key %s of %s",
1089 char* m = buffer_release(msg);
1090 osrfAppSessionStatus(
1092 OSRF_STATUS_INTERNALSERVERERROR,
1093 "osrfMethodException",
1099 if (pkey_value) free(pkey_value);
1104 if (local_context->size > 0) {
1105 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified", local_context->size);
1107 const char* lcontext = NULL;
1108 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1109 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1112 "adding class-local field %s (value: %s) to the context org list",
1114 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1120 if (foreign_context) {
1121 unsigned long class_count = osrfHashGetCount( foreign_context );
1122 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count);
1124 if (class_count > 0) {
1126 osrfHash* fcontext = NULL;
1127 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1128 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1129 const char* class_name = osrfHashIteratorKey( class_itr );
1130 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1134 "%d foreign context fields(s) specified for class %s",
1135 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1139 char* foreign_pkey = osrfHashGet(fcontext, "field");
1140 char* foreign_pkey_value = oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1142 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1144 jsonObject* _list = doFieldmapperSearch(
1145 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1147 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1148 jsonObjectFree(_tmp_params);
1149 jsonObjectFree(_list);
1151 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1153 if (_fparam && jump_list) {
1154 const char* flink = NULL;
1156 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1157 free(foreign_pkey_value);
1159 osrfHash* foreign_link_hash = oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1161 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1162 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1164 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1166 _list = doFieldmapperSearch(
1168 osrfHashGet( oilsIDL(), osrfHashGet( foreign_link_hash, "class" ) ),
1174 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1175 jsonObjectFree(_tmp_params);
1176 jsonObjectFree(_list);
1183 growing_buffer* msg = buffer_init(128);
1186 "%s: no object found with primary key %s of %s",
1192 char* m = buffer_release(msg);
1193 osrfAppSessionStatus(
1195 OSRF_STATUS_INTERNALSERVERERROR,
1196 "osrfMethodException",
1202 osrfHashIteratorFree(class_itr);
1203 free(foreign_pkey_value);
1204 jsonObjectFree(param);
1209 free(foreign_pkey_value);
1212 const char* foreign_field = NULL;
1213 while ( (foreign_field = osrfStringArrayGetString(osrfHashGet(fcontext,"context"), j++)) ) {
1214 osrfStringArrayAdd( context_org_array, oilsFMGetString( _fparam, foreign_field ) );
1217 "adding foreign class %s field %s (value: %s) to the context org list",
1220 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1224 jsonObjectFree(_fparam);
1227 osrfHashIteratorFree( class_itr );
1231 jsonObjectFree(param);
1234 const char* context_org = NULL;
1235 const char* perm = NULL;
1238 if (permission->size == 0) {
1239 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1244 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1246 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1252 "Checking object permission [%s] for user %d on object %s (class %s) at org %d",
1256 osrfHashGet(class, "classname"),
1260 result = dbi_conn_queryf(
1262 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1265 osrfHashGet(class, "classname"),
1273 "Received a result for object permission [%s] for user %d on object %s (class %s) at org %d",
1277 osrfHashGet(class, "classname"),
1281 if (dbi_result_first_row(result)) {
1282 jsonObject* return_val = oilsMakeJSONFromResult( result );
1283 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1287 "Status of object permission [%s] for user %d on object %s (class %s) at org %d is %s",
1291 osrfHashGet(class, "classname"),
1296 if ( *has_perm == 't' ) OK = 1;
1297 jsonObjectFree(return_val);
1300 dbi_result_free(result);
1305 osrfLogDebug( OSRF_LOG_MARK, "Checking non-object permission [%s] for user %d at org %d", perm, userid, atoi(context_org) );
1306 result = dbi_conn_queryf(
1308 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1315 osrfLogDebug( OSRF_LOG_MARK, "Received a result for permission [%s] for user %d at org %d",
1316 perm, userid, atoi(context_org) );
1317 if ( dbi_result_first_row(result) ) {
1318 jsonObject* return_val = oilsMakeJSONFromResult( result );
1319 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1320 osrfLogDebug( OSRF_LOG_MARK, "Status of permission [%s] for user %d at org %d is [%s]",
1321 perm, userid, atoi(context_org), has_perm );
1322 if ( *has_perm == 't' ) OK = 1;
1323 jsonObjectFree(return_val);
1326 dbi_result_free(result);
1334 if (pkey_value) free(pkey_value);
1335 osrfStringArrayFree(context_org_array);
1341 * Look up the root of the org_unit tree. If you find it, return
1342 * a string containing the id, which the caller is responsible for freeing.
1343 * Otherwise return NULL.
1345 static char* org_tree_root( osrfMethodContext* ctx ) {
1347 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1348 static time_t last_lookup_time = 0;
1349 time_t current_time = time( NULL );
1351 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1352 // We successfully looked this up less than an hour ago.
1353 // It's not likely to have changed since then.
1354 return strdup( cached_root_id );
1356 last_lookup_time = current_time;
1359 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1360 jsonObject* result = doFieldmapperSearch(
1361 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1362 jsonObjectFree( where_clause );
1364 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1367 jsonObjectFree( result );
1369 growing_buffer* msg = buffer_init(128);
1370 OSRF_BUFFER_ADD( msg, MODULENAME );
1371 OSRF_BUFFER_ADD( msg,
1372 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1374 char* m = buffer_release(msg);
1375 osrfAppSessionStatus( ctx->session,
1376 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1379 cached_root_id[ 0 ] = '\0';
1383 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1384 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1386 jsonObjectFree( result );
1388 strcpy( cached_root_id, root_org_unit_id );
1389 return root_org_unit_id;
1393 Utility function: create a JSON_HASH with a single key/value pair.
1394 This function is equivalent to:
1396 jsonParseFmt( "{\"%s\":\"%s\"}", key, value )
1398 or, if value is NULL:
1400 jsonParseFmt( "{\"%s\":null}", key )
1402 ...but faster because it doesn't create and parse a JSON string.
1404 static jsonObject* single_hash( const char* key, const char* value ) {
1406 if( ! key ) key = "";
1408 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1409 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1415 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1417 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1419 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1420 jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1422 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1423 jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1426 if (!verifyObjectClass(ctx, target)) {
1431 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1433 char* trans_id = NULL;
1434 if( ctx->session && ctx->session->userData )
1435 trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
1438 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1440 osrfAppSessionStatus(
1442 OSRF_STATUS_BADREQUEST,
1443 "osrfMethodException",
1445 "No active transaction -- required for CREATE"
1451 // The following test is harmless but redundant. If a class is
1452 // readonly, we don't register a create method for it.
1453 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1454 osrfAppSessionStatus(
1456 OSRF_STATUS_BADREQUEST,
1457 "osrfMethodException",
1459 "Cannot INSERT readonly class"
1465 // Set the last_xact_id
1466 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1468 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
1469 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1472 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1474 dbhandle = writehandle;
1476 osrfHash* fields = osrfHashGet(meta, "fields");
1477 char* pkey = osrfHashGet(meta, "primarykey");
1478 char* seq = osrfHashGet(meta, "sequence");
1480 growing_buffer* table_buf = buffer_init(128);
1481 growing_buffer* col_buf = buffer_init(128);
1482 growing_buffer* val_buf = buffer_init(128);
1484 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1485 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1486 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1487 buffer_add(val_buf,"VALUES (");
1491 osrfHash* field = NULL;
1492 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1493 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1495 const char* field_name = osrfHashIteratorKey( field_itr );
1497 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1500 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1503 if (field_object && field_object->classname) {
1504 value = oilsFMGetString(
1506 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1508 } else if( field_object && JSON_BOOL == field_object->type ) {
1509 if( jsonBoolIsTrue( field_object ) )
1510 value = strdup( "t" );
1512 value = strdup( "f" );
1514 value = jsonObjectToSimpleString( field_object );
1520 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1521 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1524 buffer_add(col_buf, field_name);
1526 if (!field_object || field_object->type == JSON_NULL) {
1527 buffer_add( val_buf, "DEFAULT" );
1529 } else if ( !strcmp(get_primitive( field ), "number") ) {
1530 const char* numtype = get_datatype( field );
1531 if ( !strcmp( numtype, "INT8") ) {
1532 buffer_fadd( val_buf, "%lld", atoll(value) );
1534 } else if ( !strcmp( numtype, "INT") ) {
1535 buffer_fadd( val_buf, "%d", atoi(value) );
1537 } else if ( !strcmp( numtype, "NUMERIC") ) {
1538 buffer_fadd( val_buf, "%f", atof(value) );
1541 if ( dbi_conn_quote_string(writehandle, &value) ) {
1542 OSRF_BUFFER_ADD( val_buf, value );
1545 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1546 osrfAppSessionStatus(
1548 OSRF_STATUS_INTERNALSERVERERROR,
1549 "osrfMethodException",
1551 "Error quoting string -- please see the error log for more details"
1554 buffer_free(table_buf);
1555 buffer_free(col_buf);
1556 buffer_free(val_buf);
1566 osrfHashIteratorFree( field_itr );
1568 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1569 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1571 char* table_str = buffer_release(table_buf);
1572 char* col_str = buffer_release(col_buf);
1573 char* val_str = buffer_release(val_buf);
1574 growing_buffer* sql = buffer_init(128);
1575 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1580 char* query = buffer_release(sql);
1582 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1585 dbi_result result = dbi_conn_query(writehandle, query);
1587 jsonObject* obj = NULL;
1590 obj = jsonNewObject(NULL);
1593 "%s ERROR inserting %s object using query [%s]",
1595 osrfHashGet(meta, "fieldmapper"),
1598 osrfAppSessionStatus(
1600 OSRF_STATUS_INTERNALSERVERERROR,
1601 "osrfMethodException",
1603 "INSERT error -- please see the error log for more details"
1608 char* id = oilsFMGetString(target, pkey);
1610 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1611 growing_buffer* _id = buffer_init(10);
1612 buffer_fadd(_id, "%lld", new_id);
1613 id = buffer_release(_id);
1616 // Find quietness specification, if present
1617 const char* quiet_str = NULL;
1619 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1621 quiet_str = jsonObjectGetString( quiet_obj );
1624 if( str_is_true( quiet_str ) ) { // if quietness is specified
1625 obj = jsonNewObject(id);
1629 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1630 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1632 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1634 jsonObjectFree( where_clause );
1639 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1642 jsonObjectFree( list );
1655 * Fetch one row from a specified table, using a specified value
1656 * for the primary key
1658 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1668 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1670 const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos); // key value
1674 "%s retrieving %s object with primary key value of %s",
1676 osrfHashGet( class_def, "fieldmapper" ),
1677 jsonObjectGetString( id_obj )
1680 // Build a WHERE clause based on the key value
1681 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1684 osrfHashGet( class_def, "primarykey" ),
1685 jsonObjectClone( id_obj )
1688 jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
1690 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
1692 jsonObjectFree( where_clause );
1696 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
1697 jsonObjectFree( list );
1700 if(!verifyObjectPCRUD(ctx, obj)) {
1701 jsonObjectFree(obj);
1704 growing_buffer* msg = buffer_init(128);
1705 OSRF_BUFFER_ADD( msg, MODULENAME );
1706 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1708 char* m = buffer_release(msg);
1709 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1720 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1721 growing_buffer* val_buf = buffer_init(32);
1722 const char* numtype = get_datatype( field );
1724 if ( !strncmp( numtype, "INT", 3 ) ) {
1725 if (value->type == JSON_NUMBER)
1726 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1727 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1729 //const char* val_str = jsonObjectGetString( value );
1730 //buffer_fadd( val_buf, "%ld", atol(val_str) );
1731 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1734 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
1735 if (value->type == JSON_NUMBER)
1736 //buffer_fadd( val_buf, "%f", jsonObjectGetNumber(value) );
1737 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1739 //const char* val_str = jsonObjectGetString( value );
1740 //buffer_fadd( val_buf, "%f", atof(val_str) );
1741 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1745 // Presumably this was really intended ot be a string, so quote it
1746 char* str = jsonObjectToSimpleString( value );
1747 if ( dbi_conn_quote_string(dbhandle, &str) ) {
1748 OSRF_BUFFER_ADD( val_buf, str );
1751 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
1753 buffer_free(val_buf);
1758 return buffer_release(val_buf);
1761 static char* searchINPredicate (const char* class_alias, osrfHash* field,
1762 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1763 growing_buffer* sql_buf = buffer_init(32);
1769 osrfHashGet(field, "name")
1773 buffer_add(sql_buf, "IN (");
1774 } else if (!(strcasecmp(op,"not in"))) {
1775 buffer_add(sql_buf, "NOT IN (");
1777 buffer_add(sql_buf, "IN (");
1780 if (node->type == JSON_HASH) {
1781 // subquery predicate
1782 char* subpred = buildQuery( ctx, node, SUBSELECT );
1784 buffer_free( sql_buf );
1788 buffer_add(sql_buf, subpred);
1791 } else if (node->type == JSON_ARRAY) {
1792 // literal value list
1793 int in_item_index = 0;
1794 int in_item_first = 1;
1795 const jsonObject* in_item;
1796 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1801 buffer_add(sql_buf, ", ");
1804 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1805 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1806 MODULENAME, json_type( in_item->type ) );
1807 buffer_free(sql_buf);
1811 // Append the literal value -- quoted if not a number
1812 if ( JSON_NUMBER == in_item->type ) {
1813 char* val = jsonNumberToDBString( field, in_item );
1814 OSRF_BUFFER_ADD( sql_buf, val );
1817 } else if ( !strcmp( get_primitive( field ), "number") ) {
1818 char* val = jsonNumberToDBString( field, in_item );
1819 OSRF_BUFFER_ADD( sql_buf, val );
1823 char* key_string = jsonObjectToSimpleString(in_item);
1824 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1825 OSRF_BUFFER_ADD( sql_buf, key_string );
1828 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1830 buffer_free(sql_buf);
1836 if( in_item_first ) {
1837 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1838 buffer_free( sql_buf );
1842 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1843 MODULENAME, json_type( node->type ) );
1844 buffer_free(sql_buf);
1848 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1850 return buffer_release(sql_buf);
1853 // Receive a JSON_ARRAY representing a function call. The first
1854 // entry in the array is the function name. The rest are parameters.
1855 static char* searchValueTransform( const jsonObject* array ) {
1857 if( array->size < 1 ) {
1858 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
1862 // Get the function name
1863 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
1864 if( func_item->type != JSON_STRING ) {
1865 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
1866 MODULENAME, json_type( func_item->type ) );
1870 growing_buffer* sql_buf = buffer_init(32);
1872 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1873 OSRF_BUFFER_ADD( sql_buf, "( " );
1875 // Get the parameters
1876 int func_item_index = 1; // We already grabbed the zeroth entry
1877 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1879 // Add a separator comma, if we need one
1880 if( func_item_index > 2 )
1881 buffer_add( sql_buf, ", " );
1883 // Add the current parameter
1884 if (func_item->type == JSON_NULL) {
1885 buffer_add( sql_buf, "NULL" );
1887 char* val = jsonObjectToSimpleString(func_item);
1888 if ( dbi_conn_quote_string(dbhandle, &val) ) {
1889 OSRF_BUFFER_ADD( sql_buf, val );
1892 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1893 buffer_free(sql_buf);
1900 buffer_add( sql_buf, " )" );
1902 return buffer_release(sql_buf);
1905 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
1906 const jsonObject* node, const char* op) {
1908 if( ! is_good_operator( op ) ) {
1909 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
1913 char* val = searchValueTransform(node);
1917 growing_buffer* sql_buf = buffer_init(32);
1922 osrfHashGet(field, "name"),
1929 return buffer_release(sql_buf);
1932 // class_alias is a class name or other table alias
1933 // field is a field definition as stored in the IDL
1934 // node comes from the method parameter, and may represent an entry in the SELECT list
1935 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
1936 growing_buffer* sql_buf = buffer_init(32);
1938 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
1939 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
1941 if(transform_subcolumn) {
1942 if( ! is_identifier( transform_subcolumn ) ) {
1943 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
1944 MODULENAME, transform_subcolumn );
1945 buffer_free( sql_buf );
1948 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
1951 if (field_transform) {
1953 if( ! is_identifier( field_transform ) ) {
1954 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
1955 MODULENAME, field_transform );
1956 buffer_free( sql_buf );
1960 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class_alias, osrfHashGet(field, "name"));
1961 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
1964 if( array->type != JSON_ARRAY ) {
1965 osrfLogError( OSRF_LOG_MARK,
1966 "%s: Expected JSON_ARRAY for function params; found %s",
1967 MODULENAME, json_type( array->type ) );
1968 buffer_free( sql_buf );
1971 int func_item_index = 0;
1972 jsonObject* func_item;
1973 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1975 char* val = jsonObjectToSimpleString(func_item);
1978 buffer_add( sql_buf, ",NULL" );
1979 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
1980 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
1981 OSRF_BUFFER_ADD( sql_buf, val );
1983 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1985 buffer_free(sql_buf);
1992 buffer_add( sql_buf, " )" );
1995 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
1998 if (transform_subcolumn)
1999 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2001 return buffer_release(sql_buf);
2004 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2005 const jsonObject* node, const char* op ) {
2007 if( ! is_good_operator( op ) ) {
2008 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2012 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2013 if( ! field_transform )
2016 int extra_parens = 0; // boolean
2018 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2019 if ( ! value_obj ) {
2020 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2022 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
2023 free(field_transform);
2027 } else if ( value_obj->type == JSON_ARRAY ) {
2028 value = searchValueTransform( value_obj );
2030 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
2031 free( field_transform );
2034 } else if ( value_obj->type == JSON_HASH ) {
2035 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2037 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
2038 free(field_transform);
2042 } else if ( value_obj->type == JSON_NUMBER ) {
2043 value = jsonNumberToDBString( field, value_obj );
2044 } else if ( value_obj->type == JSON_NULL ) {
2045 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
2046 free(field_transform);
2048 } else if ( value_obj->type == JSON_BOOL ) {
2049 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
2050 free(field_transform);
2053 if ( !strcmp( get_primitive( field ), "number") ) {
2054 value = jsonNumberToDBString( field, value_obj );
2056 value = jsonObjectToSimpleString( value_obj );
2057 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2058 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
2060 free(field_transform);
2066 const char* left_parens = "";
2067 const char* right_parens = "";
2069 if( extra_parens ) {
2074 growing_buffer* sql_buf = buffer_init(32);
2078 "%s%s %s %s %s %s%s",
2089 free(field_transform);
2091 return buffer_release(sql_buf);
2094 static char* searchSimplePredicate (const char* op, const char* class_alias,
2095 osrfHash* field, const jsonObject* node) {
2097 if( ! is_good_operator( op ) ) {
2098 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2104 // Get the value to which we are comparing the specified column
2105 if (node->type != JSON_NULL) {
2106 if ( node->type == JSON_NUMBER ) {
2107 val = jsonNumberToDBString( field, node );
2108 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2109 val = jsonNumberToDBString( field, node );
2111 val = jsonObjectToSimpleString(node);
2116 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2117 // Value is not numeric; enclose it in quotes
2118 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2119 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2125 // Compare to a null value
2126 val = strdup( "NULL" );
2127 if (strcmp( op, "=" ))
2133 growing_buffer* sql_buf = buffer_init(32);
2134 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2135 char* pred = buffer_release( sql_buf );
2142 static char* searchBETWEENPredicate (const char* class_alias,
2143 osrfHash* field, const jsonObject* node) {
2145 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2146 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2148 if( NULL == y_node ) {
2149 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2152 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2153 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2160 if ( !strcmp( get_primitive( field ), "number") ) {
2161 x_string = jsonNumberToDBString(field, x_node);
2162 y_string = jsonNumberToDBString(field, y_node);
2165 x_string = jsonObjectToSimpleString(x_node);
2166 y_string = jsonObjectToSimpleString(y_node);
2167 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2168 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2169 MODULENAME, x_string, y_string);
2176 growing_buffer* sql_buf = buffer_init(32);
2177 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2178 class_alias, osrfHashGet(field, "name"), x_string, y_string );
2182 return buffer_release(sql_buf);
2185 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2186 jsonObject* node, osrfMethodContext* ctx ) {
2189 if (node->type == JSON_ARRAY) { // equality IN search
2190 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2191 } else if (node->type == JSON_HASH) { // other search
2192 jsonIterator* pred_itr = jsonNewIterator( node );
2193 if( !jsonIteratorHasNext( pred_itr ) ) {
2194 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2195 MODULENAME, osrfHashGet(field, "name") );
2197 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2199 // Verify that there are no additional predicates
2200 if( jsonIteratorHasNext( pred_itr ) ) {
2201 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2202 MODULENAME, osrfHashGet(field, "name") );
2203 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2204 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2205 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2206 pred = searchINPredicate( class_info->alias, field, pred_node, pred_itr->key, ctx );
2207 else if ( pred_node->type == JSON_ARRAY )
2208 pred = searchFunctionPredicate( class_info->alias, field, pred_node, pred_itr->key );
2209 else if ( pred_node->type == JSON_HASH )
2210 pred = searchFieldTransformPredicate( class_info, field, pred_node, pred_itr->key );
2212 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2214 jsonIteratorFree(pred_itr);
2216 } else if (node->type == JSON_NULL) { // IS NULL search
2217 growing_buffer* _p = buffer_init(64);
2220 "\"%s\".%s IS NULL",
2221 class_info->class_name,
2222 osrfHashGet(field, "name")
2224 pred = buffer_release(_p);
2225 } else { // equality search
2226 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2245 field : call_number,
2261 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2263 const jsonObject* working_hash;
2264 jsonObject* freeable_hash = NULL;
2266 if (join_hash->type == JSON_HASH) {
2267 working_hash = join_hash;
2268 } else if (join_hash->type == JSON_STRING) {
2269 // turn it into a JSON_HASH by creating a wrapper
2270 // around a copy of the original
2271 const char* _tmp = jsonObjectGetString( join_hash );
2272 freeable_hash = jsonNewObjectType(JSON_HASH);
2273 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2274 working_hash = freeable_hash;
2278 "%s: JOIN failed; expected JSON object type not found",
2284 growing_buffer* join_buf = buffer_init(128);
2285 const char* leftclass = left_info->class_name;
2287 jsonObject* snode = NULL;
2288 jsonIterator* search_itr = jsonNewIterator( working_hash );
2290 while ( (snode = jsonIteratorNext( search_itr )) ) {
2291 const char* right_alias = search_itr->key;
2293 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2295 class = right_alias;
2297 const ClassInfo* right_info = add_joined_class( right_alias, class );
2301 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2305 jsonIteratorFree( search_itr );
2306 buffer_free( join_buf );
2308 jsonObjectFree( freeable_hash );
2311 osrfHash* links = right_info->links;
2312 const char* table = right_info->source_def;
2314 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2315 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2317 if (field && !fkey) {
2318 // Look up the corresponding join column in the IDL.
2319 // The link must be defined in the child table,
2320 // and point to the right parent table.
2321 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2322 const char* reltype = NULL;
2323 const char* other_class = NULL;
2324 reltype = osrfHashGet( idl_link, "reltype" );
2325 if( reltype && strcmp( reltype, "has_many" ) )
2326 other_class = osrfHashGet( idl_link, "class" );
2327 if( other_class && !strcmp( other_class, leftclass ) )
2328 fkey = osrfHashGet( idl_link, "key" );
2332 "%s: JOIN failed. No link defined from %s.%s to %s",
2338 buffer_free(join_buf);
2340 jsonObjectFree(freeable_hash);
2341 jsonIteratorFree(search_itr);
2345 } else if (!field && fkey) {
2346 // Look up the corresponding join column in the IDL.
2347 // The link must be defined in the child table,
2348 // and point to the right parent table.
2349 osrfHash* left_links = left_info->links;
2350 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2351 const char* reltype = NULL;
2352 const char* other_class = NULL;
2353 reltype = osrfHashGet( idl_link, "reltype" );
2354 if( reltype && strcmp( reltype, "has_many" ) )
2355 other_class = osrfHashGet( idl_link, "class" );
2356 if( other_class && !strcmp( other_class, class ) )
2357 field = osrfHashGet( idl_link, "key" );
2361 "%s: JOIN failed. No link defined from %s.%s to %s",
2367 buffer_free(join_buf);
2369 jsonObjectFree(freeable_hash);
2370 jsonIteratorFree(search_itr);
2374 } else if (!field && !fkey) {
2375 osrfHash* left_links = left_info->links;
2377 // For each link defined for the left class:
2378 // see if the link references the joined class
2379 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2380 osrfHash* curr_link = NULL;
2381 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2382 const char* other_class = osrfHashGet( curr_link, "class" );
2383 if( other_class && !strcmp( other_class, class ) ) {
2385 // In the IDL, the parent class doesn't know then names of the child
2386 // columns that are pointing to it, so don't use that end of the link
2387 const char* reltype = osrfHashGet( curr_link, "reltype" );
2388 if( reltype && strcmp( reltype, "has_many" ) ) {
2389 // Found a link between the classes
2390 fkey = osrfHashIteratorKey( itr );
2391 field = osrfHashGet( curr_link, "key" );
2396 osrfHashIteratorFree( itr );
2398 if (!field || !fkey) {
2399 // Do another such search, with the classes reversed
2401 // For each link defined for the joined class:
2402 // see if the link references the left class
2403 osrfHashIterator* itr = osrfNewHashIterator( links );
2404 osrfHash* curr_link = NULL;
2405 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2406 const char* other_class = osrfHashGet( curr_link, "class" );
2407 if( other_class && !strcmp( other_class, leftclass ) ) {
2409 // In the IDL, the parent class doesn't know then names of the child
2410 // columns that are pointing to it, so don't use that end of the link
2411 const char* reltype = osrfHashGet( curr_link, "reltype" );
2412 if( reltype && strcmp( reltype, "has_many" ) ) {
2413 // Found a link between the classes
2414 field = osrfHashIteratorKey( itr );
2415 fkey = osrfHashGet( curr_link, "key" );
2420 osrfHashIteratorFree( itr );
2423 if (!field || !fkey) {
2426 "%s: JOIN failed. No link defined between %s and %s",
2431 buffer_free(join_buf);
2433 jsonObjectFree(freeable_hash);
2434 jsonIteratorFree(search_itr);
2440 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2442 if ( !strcasecmp(type,"left") ) {
2443 buffer_add(join_buf, " LEFT JOIN");
2444 } else if ( !strcasecmp(type,"right") ) {
2445 buffer_add(join_buf, " RIGHT JOIN");
2446 } else if ( !strcasecmp(type,"full") ) {
2447 buffer_add(join_buf, " FULL JOIN");
2449 buffer_add(join_buf, " INNER JOIN");
2452 buffer_add(join_buf, " INNER JOIN");
2455 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2456 table, right_alias, right_alias, field, left_info->alias, fkey);
2458 // Add any other join conditions as specified by "filter"
2459 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2461 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2462 if ( filter_op && !strcasecmp("or",filter_op) ) {
2463 buffer_add( join_buf, " OR " );
2465 buffer_add( join_buf, " AND " );
2468 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2470 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2471 OSRF_BUFFER_ADD( join_buf, jpred );
2476 "%s: JOIN failed. Invalid conditional expression.",
2479 jsonIteratorFree( search_itr );
2480 buffer_free( join_buf );
2482 jsonObjectFree( freeable_hash );
2487 buffer_add(join_buf, " ) ");
2489 // Recursively add a nested join, if one is present
2490 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2492 char* jpred = searchJOIN( join_filter, right_info );
2494 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2495 OSRF_BUFFER_ADD( join_buf, jpred );
2498 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2499 jsonIteratorFree( search_itr );
2500 buffer_free( join_buf );
2502 jsonObjectFree( freeable_hash );
2509 jsonObjectFree(freeable_hash);
2510 jsonIteratorFree(search_itr);
2512 return buffer_release(join_buf);
2517 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2518 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2519 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2521 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2523 search_hash is the JSON expression of the conditions.
2524 meta is the class definition from the IDL, for the relevant table.
2525 opjoin_type indicates whether multiple conditions, if present, should be
2526 connected by AND or OR.
2527 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2528 to pass it to other functions -- and all they do with it is to use the session
2529 and request members to send error messages back to the client.
2533 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
2534 int opjoin_type, osrfMethodContext* ctx ) {
2538 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2541 class_info->class_def,
2546 growing_buffer* sql_buf = buffer_init(128);
2548 jsonObject* node = NULL;
2551 if ( search_hash->type == JSON_ARRAY ) {
2552 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2553 if( 0 == search_hash->size ) {
2556 "%s: Invalid predicate structure: empty JSON array",
2559 buffer_free( sql_buf );
2563 unsigned long i = 0;
2564 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
2568 if (opjoin_type == OR_OP_JOIN)
2569 buffer_add(sql_buf, " OR ");
2571 buffer_add(sql_buf, " AND ");
2574 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2576 buffer_free( sql_buf );
2580 buffer_fadd(sql_buf, "( %s )", subpred);
2584 } else if ( search_hash->type == JSON_HASH ) {
2585 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2586 jsonIterator* search_itr = jsonNewIterator( search_hash );
2587 if( !jsonIteratorHasNext( search_itr ) ) {
2590 "%s: Invalid predicate structure: empty JSON object",
2593 jsonIteratorFree( search_itr );
2594 buffer_free( sql_buf );
2598 while ( (node = jsonIteratorNext( search_itr )) ) {
2603 if (opjoin_type == OR_OP_JOIN)
2604 buffer_add(sql_buf, " OR ");
2606 buffer_add(sql_buf, " AND ");
2609 if ( '+' == search_itr->key[ 0 ] ) {
2611 // This plus sign prefixes a class name or other table alias;
2612 // make sure the table alias is in scope
2613 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
2614 if( ! alias_info ) {
2617 "%s: Invalid table alias \"%s\" in WHERE clause",
2621 jsonIteratorFree( search_itr );
2622 buffer_free( sql_buf );
2626 if ( node->type == JSON_STRING ) {
2627 // It's the name of a column; make sure it belongs to the class
2628 const char* fieldname = jsonObjectGetString( node );
2629 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
2632 "%s: Invalid column name \"%s\" in WHERE clause for table alias \"%s\"",
2637 jsonIteratorFree( search_itr );
2638 buffer_free( sql_buf );
2642 buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
2644 // It's something more complicated
2645 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
2647 jsonIteratorFree( search_itr );
2648 buffer_free( sql_buf );
2652 buffer_fadd(sql_buf, "( %s )", subpred);
2655 } else if ( '-' == search_itr->key[ 0 ] ) {
2656 if ( !strcasecmp("-or",search_itr->key) ) {
2657 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
2659 jsonIteratorFree( search_itr );
2660 buffer_free( sql_buf );
2664 buffer_fadd(sql_buf, "( %s )", subpred);
2666 } else if ( !strcasecmp("-and",search_itr->key) ) {
2667 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2669 jsonIteratorFree( search_itr );
2670 buffer_free( sql_buf );
2674 buffer_fadd(sql_buf, "( %s )", subpred);
2676 } else if ( !strcasecmp("-not",search_itr->key) ) {
2677 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2679 jsonIteratorFree( search_itr );
2680 buffer_free( sql_buf );
2684 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2686 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2687 char* subpred = buildQuery( ctx, node, SUBSELECT );
2689 jsonIteratorFree( search_itr );
2690 buffer_free( sql_buf );
2694 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2696 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2697 char* subpred = buildQuery( ctx, node, SUBSELECT );
2699 jsonIteratorFree( search_itr );
2700 buffer_free( sql_buf );
2704 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2706 } else { // Invalid "minus" operator
2709 "%s: Invalid operator \"%s\" in WHERE clause",
2713 jsonIteratorFree( search_itr );
2714 buffer_free( sql_buf );
2720 const char* class = class_info->class_name;
2721 osrfHash* fields = class_info->fields;
2722 osrfHash* field = osrfHashGet( fields, search_itr->key );
2725 const char* table = class_info->source_def;
2728 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2731 table ? table : "?",
2734 jsonIteratorFree(search_itr);
2735 buffer_free(sql_buf);
2739 char* subpred = searchPredicate( class_info, field, node, ctx );
2741 buffer_free(sql_buf);
2742 jsonIteratorFree(search_itr);
2746 buffer_add( sql_buf, subpred );
2750 jsonIteratorFree(search_itr);
2753 // ERROR ... only hash and array allowed at this level
2754 char* predicate_string = jsonObjectToJSON( search_hash );
2757 "%s: Invalid predicate structure: %s",
2761 buffer_free(sql_buf);
2762 free(predicate_string);
2766 return buffer_release(sql_buf);
2769 /* Build a JSON_ARRAY of field names for a given table alias
2771 static jsonObject* defaultSelectList( const char* table_alias ) {
2776 ClassInfo* class_info = search_all_alias( table_alias );
2777 if( ! class_info ) {
2780 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
2787 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
2788 osrfHash* field_def = NULL;
2789 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
2790 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2791 const char* field_name = osrfHashIteratorKey( field_itr );
2792 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2793 jsonObjectPush( array, jsonNewObject( field_name ) );
2796 osrfHashIteratorFree( field_itr );
2801 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
2802 // The jsonObject must be a JSON_HASH with an single entry for "union",
2803 // "intersect", or "except". The data associated with this key must be an
2804 // array of hashes, each hash being a query.
2805 // Also allowed but currently ignored: entries for "order_by" and "alias".
2806 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
2808 if( ! combo || combo->type != JSON_HASH )
2809 return NULL; // should be impossible; validated by caller
2811 const jsonObject* query_array = NULL; // array of subordinate queries
2812 const char* op = NULL; // name of operator, e.g. UNION
2813 const char* alias = NULL; // alias for the query (needed for ORDER BY)
2814 int op_count = 0; // for detecting conflicting operators
2815 int excepting = 0; // boolean
2816 int all = 0; // boolean
2817 jsonObject* order_obj = NULL;
2819 // Identify the elements in the hash
2820 jsonIterator* query_itr = jsonNewIterator( combo );
2821 jsonObject* curr_obj = NULL;
2822 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
2823 if( ! strcmp( "union", query_itr->key ) ) {
2826 query_array = curr_obj;
2827 } else if( ! strcmp( "intersect", query_itr->key ) ) {
2830 query_array = curr_obj;
2831 } else if( ! strcmp( "except", query_itr->key ) ) {
2835 query_array = curr_obj;
2836 } else if( ! strcmp( "order_by", query_itr->key ) ) {
2839 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
2842 order_obj = curr_obj;
2843 } else if( ! strcmp( "alias", query_itr->key ) ) {
2844 if( curr_obj->type != JSON_STRING ) {
2845 jsonIteratorFree( query_itr );
2848 alias = jsonObjectGetString( curr_obj );
2849 } else if( ! strcmp( "all", query_itr->key ) ) {
2850 if( obj_is_true( curr_obj ) )
2854 osrfAppSessionStatus(
2856 OSRF_STATUS_INTERNALSERVERERROR,
2857 "osrfMethodException",
2859 "Malformed query; unexpected entry in query object"
2863 "%s: Unexpected entry for \"%s\" in%squery",
2868 jsonIteratorFree( query_itr );
2872 jsonIteratorFree( query_itr );
2874 // More sanity checks
2875 if( ! query_array ) {
2877 osrfAppSessionStatus(
2879 OSRF_STATUS_INTERNALSERVERERROR,
2880 "osrfMethodException",
2882 "Expected UNION, INTERSECT, or EXCEPT operator not found"
2886 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
2889 return NULL; // should be impossible...
2890 } else if( op_count > 1 ) {
2892 osrfAppSessionStatus(
2894 OSRF_STATUS_INTERNALSERVERERROR,
2895 "osrfMethodException",
2897 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
2901 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
2905 } if( query_array->type != JSON_ARRAY ) {
2907 osrfAppSessionStatus(
2909 OSRF_STATUS_INTERNALSERVERERROR,
2910 "osrfMethodException",
2912 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
2916 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
2919 json_type( query_array->type )
2922 } if( query_array->size < 2 ) {
2924 osrfAppSessionStatus(
2926 OSRF_STATUS_INTERNALSERVERERROR,
2927 "osrfMethodException",
2929 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
2933 "%s:%srequires multiple queries as operands",
2938 } else if( excepting && query_array->size > 2 ) {
2940 osrfAppSessionStatus(
2942 OSRF_STATUS_INTERNALSERVERERROR,
2943 "osrfMethodException",
2945 "EXCEPT operator has too many queries as operands"
2949 "%s:EXCEPT operator has too many queries as operands",
2953 } else if( order_obj && ! alias ) {
2955 osrfAppSessionStatus(
2957 OSRF_STATUS_INTERNALSERVERERROR,
2958 "osrfMethodException",
2960 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
2964 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
2970 // So far so good. Now build the SQL.
2971 growing_buffer* sql = buffer_init( 256 );
2973 // If we nested inside another UNION, INTERSECT, or EXCEPT,
2974 // Add a layer of parentheses
2975 if( flags & SUBCOMBO )
2976 OSRF_BUFFER_ADD( sql, "( " );
2978 // Traverse the query array. Each entry should be a hash.
2979 int first = 1; // boolean
2981 jsonObject* query = NULL;
2982 while((query = jsonObjectGetIndex( query_array, i++ ) )) {
2983 if( query->type != JSON_HASH ) {
2985 osrfAppSessionStatus(
2987 OSRF_STATUS_INTERNALSERVERERROR,
2988 "osrfMethodException",
2990 "Malformed query under UNION, INTERSECT or EXCEPT"
2994 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
2997 json_type( query->type )
3006 OSRF_BUFFER_ADD( sql, op );
3008 OSRF_BUFFER_ADD( sql, "ALL " );
3011 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3015 "%s: Error building query under%s",
3023 OSRF_BUFFER_ADD( sql, query_str );
3026 if( flags & SUBCOMBO )
3027 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3029 if ( !(flags & SUBSELECT) )
3030 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3032 return buffer_release( sql );
3035 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3036 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3037 // or "except" to indicate the type of query.
3038 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3042 osrfAppSessionStatus(
3044 OSRF_STATUS_INTERNALSERVERERROR,
3045 "osrfMethodException",
3047 "Malformed query; no query object"
3049 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", MODULENAME );
3051 } else if( query->type != JSON_HASH ) {
3053 osrfAppSessionStatus(
3055 OSRF_STATUS_INTERNALSERVERERROR,
3056 "osrfMethodException",
3058 "Malformed query object"
3062 "%s: Query object is %s instead of JSON_HASH",
3064 json_type( query->type )
3069 // Determine what kind of query it purports to be, and dispatch accordingly.
3070 if( jsonObjectGetKey( query, "union" ) ||
3071 jsonObjectGetKey( query, "intersect" ) ||
3072 jsonObjectGetKey( query, "except" ) ) {
3073 return doCombo( ctx, query, flags );
3075 // It is presumably a SELECT query
3077 // Push a node onto the stack for the current query. Every level of
3078 // subquery gets its own QueryFrame on the Stack.
3081 // Build an SQL SELECT statement
3084 jsonObjectGetKey( query, "select" ),
3085 jsonObjectGetKey( query, "from" ),
3086 jsonObjectGetKey( query, "where" ),
3087 jsonObjectGetKey( query, "having" ),
3088 jsonObjectGetKey( query, "order_by" ),
3089 jsonObjectGetKey( query, "limit" ),
3090 jsonObjectGetKey( query, "offset" ),
3099 /* method context */ osrfMethodContext* ctx,
3101 /* SELECT */ jsonObject* selhash,
3102 /* FROM */ jsonObject* join_hash,
3103 /* WHERE */ jsonObject* search_hash,
3104 /* HAVING */ jsonObject* having_hash,
3105 /* ORDER BY */ jsonObject* order_hash,
3106 /* LIMIT */ jsonObject* limit,
3107 /* OFFSET */ jsonObject* offset,
3108 /* flags */ int flags
3110 const char* locale = osrf_message_get_last_locale();
3112 // general tmp objects
3113 const jsonObject* tmp_const;
3114 jsonObject* selclass = NULL;
3115 jsonObject* snode = NULL;
3116 jsonObject* onode = NULL;
3118 char* string = NULL;
3119 int from_function = 0;
3124 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3126 // punt if there's no FROM clause
3127 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3130 "%s: FROM clause is missing or empty",
3134 osrfAppSessionStatus(
3136 OSRF_STATUS_INTERNALSERVERERROR,
3137 "osrfMethodException",
3139 "FROM clause is missing or empty in JSON query"
3144 // the core search class
3145 const char* core_class = NULL;
3147 // get the core class -- the only key of the top level FROM clause, or a string
3148 if (join_hash->type == JSON_HASH) {
3149 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3150 snode = jsonIteratorNext( tmp_itr );
3152 // Populate the current QueryFrame with information
3153 // about the core class
3154 if( add_query_core( NULL, tmp_itr->key ) ) {
3156 osrfAppSessionStatus(
3158 OSRF_STATUS_INTERNALSERVERERROR,
3159 "osrfMethodException",
3161 "Unable to look up core class"
3165 core_class = curr_query->core.class_name;
3168 jsonObject* extra = jsonIteratorNext( tmp_itr );
3170 jsonIteratorFree( tmp_itr );
3173 // There shouldn't be more than one entry in join_hash
3177 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3181 osrfAppSessionStatus(
3183 OSRF_STATUS_INTERNALSERVERERROR,
3184 "osrfMethodException",
3186 "Malformed FROM clause in JSON query"
3188 return NULL; // Malformed join_hash; extra entry
3190 } else if (join_hash->type == JSON_ARRAY) {
3191 // We're selecting from a function, not from a table
3193 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
3196 } else if (join_hash->type == JSON_STRING) {
3197 // Populate the current QueryFrame with information
3198 // about the core class
3199 core_class = jsonObjectGetString( join_hash );
3201 if( add_query_core( NULL, core_class ) ) {
3203 osrfAppSessionStatus(
3205 OSRF_STATUS_INTERNALSERVERERROR,
3206 "osrfMethodException",
3208 "Unable to look up core class"
3216 "%s: FROM clause is unexpected JSON type: %s",
3218 json_type( join_hash->type )
3221 osrfAppSessionStatus(
3223 OSRF_STATUS_INTERNALSERVERERROR,
3224 "osrfMethodException",
3226 "Ill-formed FROM clause in JSON query"
3231 // Build the join clause, if any, while filling out the list
3232 // of joined classes in the current QueryFrame.
3233 char* join_clause = NULL;
3234 if( join_hash && ! from_function ) {
3236 join_clause = searchJOIN( join_hash, &curr_query->core );
3237 if( ! join_clause ) {
3239 osrfAppSessionStatus(
3241 OSRF_STATUS_INTERNALSERVERERROR,
3242 "osrfMethodException",
3244 "Unable to construct JOIN clause(s)"
3250 // For in case we don't get a select list
3251 jsonObject* defaultselhash = NULL;
3253 // if there is no select list, build a default select list ...
3254 if (!selhash && !from_function) {
3255 jsonObject* default_list = defaultSelectList( core_class );
3256 if( ! default_list ) {
3258 osrfAppSessionStatus(
3260 OSRF_STATUS_INTERNALSERVERERROR,
3261 "osrfMethodException",
3263 "Unable to build default SELECT clause in JSON query"
3265 free( join_clause );
3270 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
3271 jsonObjectSetKey( selhash, core_class, default_list );
3274 // The SELECT clause can be encoded only by a hash
3275 if( !from_function && selhash->type != JSON_HASH ) {
3278 "%s: Expected JSON_HASH for SELECT clause; found %s",
3280 json_type( selhash->type )
3284 osrfAppSessionStatus(
3286 OSRF_STATUS_INTERNALSERVERERROR,
3287 "osrfMethodException",
3289 "Malformed SELECT clause in JSON query"
3291 free( join_clause );
3295 // If you see a null or wild card specifier for the core class, or an
3296 // empty array, replace it with a default SELECT list
3297 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3299 int default_needed = 0; // boolean
3300 if( JSON_STRING == tmp_const->type
3301 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3303 else if( JSON_NULL == tmp_const->type )
3306 if( default_needed ) {
3307 // Build a default SELECT list
3308 jsonObject* default_list = defaultSelectList( core_class );
3309 if( ! default_list ) {
3311 osrfAppSessionStatus(
3313 OSRF_STATUS_INTERNALSERVERERROR,
3314 "osrfMethodException",
3316 "Can't build default SELECT clause in JSON query"
3318 free( join_clause );
3323 jsonObjectSetKey( selhash, core_class, default_list );
3327 // temp buffers for the SELECT list and GROUP BY clause
3328 growing_buffer* select_buf = buffer_init(128);
3329 growing_buffer* group_buf = buffer_init(128);
3331 int aggregate_found = 0; // boolean
3333 // Build a select list
3334 if(from_function) // From a function we select everything
3335 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3338 // Build the SELECT list as SQL
3342 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3343 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3345 const char* cname = selclass_itr->key;
3347 // Make sure the target relation is in the FROM clause.
3349 // At this point join_hash is a step down from the join_hash we
3350 // received as a parameter. If the original was a JSON_STRING,
3351 // then json_hash is now NULL. If the original was a JSON_HASH,
3352 // then json_hash is now the first (and only) entry in it,
3353 // denoting the core class. We've already excluded the
3354 // possibility that the original was a JSON_ARRAY, because in
3355 // that case from_function would be non-NULL, and we wouldn't
3358 // If the current table alias isn't in scope, bail out
3359 ClassInfo* class_info = search_alias( cname );
3360 if( ! class_info ) {
3363 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3368 osrfAppSessionStatus(
3370 OSRF_STATUS_INTERNALSERVERERROR,
3371 "osrfMethodException",
3373 "Selected class not in FROM clause in JSON query"
3375 jsonIteratorFree( selclass_itr );
3376 buffer_free( select_buf );
3377 buffer_free( group_buf );
3378 if( defaultselhash ) jsonObjectFree( defaultselhash );
3379 free( join_clause );
3383 if( selclass->type != JSON_ARRAY ) {
3386 "%s: Malformed SELECT list for class \"%s\"; not an array",
3391 osrfAppSessionStatus(
3393 OSRF_STATUS_INTERNALSERVERERROR,
3394 "osrfMethodException",
3396 "Selected class not in FROM clause in JSON query"
3399 jsonIteratorFree( selclass_itr );
3400 buffer_free( select_buf );
3401 buffer_free( group_buf );
3402 if( defaultselhash ) jsonObjectFree( defaultselhash );
3403 free( join_clause );
3407 // Look up some attributes of the current class
3408 osrfHash* idlClass = class_info->class_def;
3409 osrfHash* class_field_set = class_info->fields;
3410 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3411 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3413 if( 0 == selclass->size ) {
3416 "%s: No columns selected from \"%s\"",
3422 // stitch together the column list for the current table alias...
3423 unsigned long field_idx = 0;
3424 jsonObject* selfield = NULL;
3425 while((selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3427 // If we need a separator comma, add one
3431 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3434 // if the field specification is a string, add it to the list
3435 if (selfield->type == JSON_STRING) {
3437 // Look up the field in the IDL
3438 const char* col_name = jsonObjectGetString( selfield );
3439 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3441 // No such field in current class
3444 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3450 osrfAppSessionStatus(
3452 OSRF_STATUS_INTERNALSERVERERROR,
3453 "osrfMethodException",
3455 "Selected column not defined in JSON query"
3457 jsonIteratorFree( selclass_itr );
3458 buffer_free( select_buf );
3459 buffer_free( group_buf );
3460 if( defaultselhash ) jsonObjectFree( defaultselhash );
3461 free( join_clause );
3463 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3464 // Virtual field not allowed
3467 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3473 osrfAppSessionStatus(
3475 OSRF_STATUS_INTERNALSERVERERROR,
3476 "osrfMethodException",
3478 "Selected column may not be virtual in JSON query"
3480 jsonIteratorFree( selclass_itr );
3481 buffer_free( select_buf );
3482 buffer_free( group_buf );
3483 if( defaultselhash ) jsonObjectFree( defaultselhash );
3484 free( join_clause );
3490 if (flags & DISABLE_I18N)
3493 i18n = osrfHashGet(field_def, "i18n");
3495 if( str_is_true( i18n ) ) {
3496 buffer_fadd( select_buf,
3497 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3498 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3500 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3503 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3506 // ... but it could be an object, in which case we check for a Field Transform
3507 } else if (selfield->type == JSON_HASH) {
3509 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3511 // Get the field definition from the IDL
3512 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3514 // No such field in current class
3517 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3523 osrfAppSessionStatus(
3525 OSRF_STATUS_INTERNALSERVERERROR,
3526 "osrfMethodException",
3528 "Selected column is not defined in JSON query"
3530 jsonIteratorFree( selclass_itr );
3531 buffer_free( select_buf );
3532 buffer_free( group_buf );
3533 if( defaultselhash ) jsonObjectFree( defaultselhash );
3534 free( join_clause );
3536 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3537 // No such field in current class
3540 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3546 osrfAppSessionStatus(
3548 OSRF_STATUS_INTERNALSERVERERROR,
3549 "osrfMethodException",
3551 "Selected column is virtual in JSON query"
3553 jsonIteratorFree( selclass_itr );
3554 buffer_free( select_buf );
3555 buffer_free( group_buf );
3556 if( defaultselhash ) jsonObjectFree( defaultselhash );
3557 free( join_clause );
3561 // Decide what to use as a column alias
3563 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3564 _alias = jsonObjectGetString( tmp_const );
3565 } else { // Use field name as the alias
3569 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3570 char* transform_str = searchFieldTransform(class_info->alias, field_def, selfield);
3571 if( transform_str ) {
3572 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3573 free(transform_str);
3576 osrfAppSessionStatus(
3578 OSRF_STATUS_INTERNALSERVERERROR,
3579 "osrfMethodException",
3581 "Unable to generate transform function in JSON query"
3583 jsonIteratorFree( selclass_itr );
3584 buffer_free( select_buf );
3585 buffer_free( group_buf );
3586 if( defaultselhash ) jsonObjectFree( defaultselhash );
3587 free( join_clause );
3594 if (flags & DISABLE_I18N)
3597 i18n = osrfHashGet(field_def, "i18n");
3599 if( str_is_true( i18n ) ) {
3600 buffer_fadd( select_buf,
3601 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3602 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3604 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3607 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3614 "%s: Selected item is unexpected JSON type: %s",
3616 json_type( selfield->type )
3619 osrfAppSessionStatus(
3621 OSRF_STATUS_INTERNALSERVERERROR,
3622 "osrfMethodException",
3624 "Ill-formed SELECT item in JSON query"
3626 jsonIteratorFree( selclass_itr );
3627 buffer_free( select_buf );
3628 buffer_free( group_buf );
3629 if( defaultselhash ) jsonObjectFree( defaultselhash );
3630 free( join_clause );
3634 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3635 if( obj_is_true( agg_obj ) )
3636 aggregate_found = 1;
3638 // Append a comma (except for the first one)
3639 // and add the column to a GROUP BY clause
3643 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3645 buffer_fadd(group_buf, " %d", sel_pos);
3649 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3651 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3652 if ( ! obj_is_true( aggregate_obj ) ) {
3656 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3659 buffer_fadd(group_buf, " %d", sel_pos);
3662 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3666 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3669 _column = searchFieldTransform(class_info->alias, field, selfield);
3670 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3671 OSRF_BUFFER_ADD(group_buf, _column);
3672 _column = searchFieldTransform(class_info->alias, field, selfield);
3679 } // end while -- iterating across SELECT columns
3681 } // end while -- iterating across classes
3683 jsonIteratorFree(selclass_itr);
3687 char* col_list = buffer_release(select_buf);
3689 // Make sure the SELECT list isn't empty. This can happen, for example,
3690 // if we try to build a default SELECT clause from a non-core table.
3693 osrfLogError(OSRF_LOG_MARK, "%s: SELECT clause is empty", MODULENAME );
3695 osrfAppSessionStatus(
3697 OSRF_STATUS_INTERNALSERVERERROR,
3698 "osrfMethodException",
3700 "SELECT list is empty"
3703 buffer_free( group_buf );
3704 if( defaultselhash ) jsonObjectFree( defaultselhash );
3705 free( join_clause );
3710 if (from_function) table = searchValueTransform(join_hash);
3711 else table = strdup( curr_query->core.source_def );
3715 osrfAppSessionStatus(
3717 OSRF_STATUS_INTERNALSERVERERROR,
3718 "osrfMethodException",
3720 "Unable to identify table for core class"
3723 buffer_free( group_buf );
3724 if( defaultselhash ) jsonObjectFree( defaultselhash );
3725 free( join_clause );
3729 // Put it all together
3730 growing_buffer* sql_buf = buffer_init(128);
3731 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3735 // Append the join clause, if any
3737 buffer_add(sql_buf, join_clause);
3741 char* order_by_list = NULL;
3742 char* having_buf = NULL;
3744 if (!from_function) {
3746 // Build a WHERE clause, if there is one
3747 if ( search_hash ) {
3748 buffer_add(sql_buf, " WHERE ");
3750 // and it's on the WHERE clause
3751 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
3754 osrfAppSessionStatus(
3756 OSRF_STATUS_INTERNALSERVERERROR,
3757 "osrfMethodException",
3759 "Severe query error in WHERE predicate -- see error log for more details"
3762 buffer_free(group_buf);
3763 buffer_free(sql_buf);
3764 if (defaultselhash) jsonObjectFree(defaultselhash);
3768 buffer_add(sql_buf, pred);
3772 // Build a HAVING clause, if there is one
3773 if ( having_hash ) {
3775 // and it's on the the WHERE clause
3776 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
3778 if( ! having_buf ) {
3780 osrfAppSessionStatus(
3782 OSRF_STATUS_INTERNALSERVERERROR,
3783 "osrfMethodException",
3785 "Severe query error in HAVING predicate -- see error log for more details"
3788 buffer_free(group_buf);
3789 buffer_free(sql_buf);
3790 if (defaultselhash) jsonObjectFree(defaultselhash);
3795 growing_buffer* order_buf = NULL; // to collect ORDER BY list
3797 // Build an ORDER BY clause, if there is one
3798 if( NULL == order_hash )
3799 ; // No ORDER BY? do nothing
3800 else if( JSON_ARRAY == order_hash->type ) {
3801 // Array of field specifications, each specification being a
3802 // hash to define the class, field, and other details
3804 jsonObject* order_spec;
3805 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
3807 if( JSON_HASH != order_spec->type ) {
3808 osrfLogError(OSRF_LOG_MARK,
3809 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
3810 MODULENAME, json_type( order_spec->type ) );
3812 osrfAppSessionStatus(
3814 OSRF_STATUS_INTERNALSERVERERROR,
3815 "osrfMethodException",
3817 "Malformed ORDER BY clause -- see error log for more details"
3819 buffer_free( order_buf );
3821 buffer_free(group_buf);
3822 buffer_free(sql_buf);
3823 if (defaultselhash) jsonObjectFree(defaultselhash);
3827 const char* class_alias =
3828 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
3830 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
3833 OSRF_BUFFER_ADD(order_buf, ", ");
3835 order_buf = buffer_init(128);
3837 if( !field || !class_alias ) {
3838 osrfLogError(OSRF_LOG_MARK,
3839 "%s: Missing class or field name in field specification of ORDER BY clause",
3842 osrfAppSessionStatus(
3844 OSRF_STATUS_INTERNALSERVERERROR,
3845 "osrfMethodException",
3847 "Malformed ORDER BY clause -- see error log for more details"
3849 buffer_free( order_buf );
3851 buffer_free(group_buf);
3852 buffer_free(sql_buf);
3853 if (defaultselhash) jsonObjectFree(defaultselhash);
3857 ClassInfo* order_class_info = search_alias( class_alias );
3858 if( ! order_class_info ) {
3859 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
3860 "not in FROM clause", MODULENAME, class_alias );
3862 osrfAppSessionStatus(
3864 OSRF_STATUS_INTERNALSERVERERROR,
3865 "osrfMethodException",
3867 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3870 buffer_free(group_buf);
3871 buffer_free(sql_buf);
3872 if (defaultselhash) jsonObjectFree(defaultselhash);
3876 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
3878 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
3879 MODULENAME, class_alias, field );
3881 osrfAppSessionStatus(
3883 OSRF_STATUS_INTERNALSERVERERROR,
3884 "osrfMethodException",
3886 "Invalid field referenced in ORDER BY clause -- see error log for more details"
3889 buffer_free(group_buf);
3890 buffer_free(sql_buf);
3891 if (defaultselhash) jsonObjectFree(defaultselhash);
3893 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3894 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3895 MODULENAME, field );
3897 osrfAppSessionStatus(
3899 OSRF_STATUS_INTERNALSERVERERROR,
3900 "osrfMethodException",
3902 "Virtual field in ORDER BY clause -- see error log for more details"
3904 buffer_free( order_buf );
3906 buffer_free(group_buf);
3907 buffer_free(sql_buf);
3908 if (defaultselhash) jsonObjectFree(defaultselhash);
3912 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
3913 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
3914 if( ! transform_str ) {
3916 osrfAppSessionStatus(
3918 OSRF_STATUS_INTERNALSERVERERROR,
3919 "osrfMethodException",
3921 "Severe query error in ORDER BY clause -- see error log for more details"
3923 buffer_free( order_buf );
3925 buffer_free(group_buf);
3926 buffer_free(sql_buf);
3927 if (defaultselhash) jsonObjectFree(defaultselhash);
3931 OSRF_BUFFER_ADD( order_buf, transform_str );
3932 free( transform_str );
3935 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
3937 const char* direction =
3938 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
3940 if( direction[ 0 ] || 'D' == direction[ 0 ] )
3941 OSRF_BUFFER_ADD( order_buf, " DESC" );
3943 OSRF_BUFFER_ADD( order_buf, " ASC" );
3946 } else if( JSON_HASH == order_hash->type ) {
3947 // This hash is keyed on class alias. Each class has either
3948 // an array of field names or a hash keyed on field name.
3949 jsonIterator* class_itr = jsonNewIterator( order_hash );
3950 while ( (snode = jsonIteratorNext( class_itr )) ) {
3952 ClassInfo* order_class_info = search_alias( class_itr->key );
3953 if( ! order_class_info ) {
3954 osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
3955 MODULENAME, class_itr->key );
3957 osrfAppSessionStatus(
3959 OSRF_STATUS_INTERNALSERVERERROR,
3960 "osrfMethodException",
3962 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3964 jsonIteratorFree( class_itr );
3965 buffer_free( order_buf );
3967 buffer_free(group_buf);
3968 buffer_free(sql_buf);
3969 if (defaultselhash) jsonObjectFree(defaultselhash);
3973 osrfHash* field_list_def = order_class_info->fields;
3975 if ( snode->type == JSON_HASH ) {
3977 // Hash is keyed on field names from the current class. For each field
3978 // there is another layer of hash to define the sorting details, if any,
3979 // or a string to indicate direction of sorting.
3980 jsonIterator* order_itr = jsonNewIterator( snode );
3981 while ( (onode = jsonIteratorNext( order_itr )) ) {
3983 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
3985 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3986 MODULENAME, order_itr->key );
3988 osrfAppSessionStatus(
3990 OSRF_STATUS_INTERNALSERVERERROR,
3991 "osrfMethodException",
3993 "Invalid field in ORDER BY clause -- see error log for more details"
3995 jsonIteratorFree( order_itr );
3996 jsonIteratorFree( class_itr );
3997 buffer_free( order_buf );
3999 buffer_free(group_buf);
4000 buffer_free(sql_buf);
4001 if (defaultselhash) jsonObjectFree(defaultselhash);
4003 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4004 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4005 MODULENAME, order_itr->key );
4007 osrfAppSessionStatus(
4009 OSRF_STATUS_INTERNALSERVERERROR,
4010 "osrfMethodException",
4012 "Virtual field in ORDER BY clause -- see error log for more details"
4014 jsonIteratorFree( order_itr );
4015 jsonIteratorFree( class_itr );
4016 buffer_free( order_buf );
4018 buffer_free(group_buf);
4019 buffer_free(sql_buf);
4020 if (defaultselhash) jsonObjectFree(defaultselhash);
4024 const char* direction = NULL;
4025 if ( onode->type == JSON_HASH ) {
4026 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4027 string = searchFieldTransform(
4029 osrfHashGet( field_list_def, order_itr->key ),
4033 if( ctx ) osrfAppSessionStatus(
4035 OSRF_STATUS_INTERNALSERVERERROR,
4036 "osrfMethodException",
4038 "Severe query error in ORDER BY clause -- see error log for more details"
4040 jsonIteratorFree( order_itr );
4041 jsonIteratorFree( class_itr );
4043 buffer_free(group_buf);
4044 buffer_free(order_buf);
4045 buffer_free(sql_buf);
4046 if (defaultselhash) jsonObjectFree(defaultselhash);
4050 growing_buffer* field_buf = buffer_init(16);
4051 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4052 string = buffer_release(field_buf);
4055 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4056 const char* dir = jsonObjectGetString(tmp_const);
4057 if (!strncasecmp(dir, "d", 1)) {
4058 direction = " DESC";
4064 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4065 osrfLogError( OSRF_LOG_MARK,
4066 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4067 MODULENAME, json_type( onode->type ) );
4069 osrfAppSessionStatus(
4071 OSRF_STATUS_INTERNALSERVERERROR,
4072 "osrfMethodException",
4074 "Malformed ORDER BY clause -- see error log for more details"
4076 jsonIteratorFree( order_itr );
4077 jsonIteratorFree( class_itr );
4079 buffer_free(group_buf);
4080 buffer_free(order_buf);
4081 buffer_free(sql_buf);
4082 if (defaultselhash) jsonObjectFree(defaultselhash);
4086 string = strdup(order_itr->key);
4087 const char* dir = jsonObjectGetString(onode);
4088 if (!strncasecmp(dir, "d", 1)) {
4089 direction = " DESC";
4096 OSRF_BUFFER_ADD(order_buf, ", ");
4098 order_buf = buffer_init(128);
4100 OSRF_BUFFER_ADD(order_buf, string);
4104 OSRF_BUFFER_ADD(order_buf, direction);
4108 jsonIteratorFree(order_itr);
4110 } else if ( snode->type == JSON_ARRAY ) {
4112 // Array is a list of fields from the current class
4113 unsigned long order_idx = 0;
4114 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4116 const char* _f = jsonObjectGetString( onode );
4118 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4120 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
4123 osrfAppSessionStatus(
4125 OSRF_STATUS_INTERNALSERVERERROR,
4126 "osrfMethodException",
4128 "Invalid field in ORDER BY clause -- see error log for more details"
4130 jsonIteratorFree( class_itr );
4131 buffer_free( order_buf );
4133 buffer_free(group_buf);
4134 buffer_free(sql_buf);
4135 if (defaultselhash) jsonObjectFree(defaultselhash);
4137 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4138 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4141 osrfAppSessionStatus(
4143 OSRF_STATUS_INTERNALSERVERERROR,
4144 "osrfMethodException",
4146 "Virtual field in ORDER BY clause -- see error log for more details"
4148 jsonIteratorFree( class_itr );
4149 buffer_free( order_buf );
4151 buffer_free(group_buf);
4152 buffer_free(sql_buf);
4153 if (defaultselhash) jsonObjectFree(defaultselhash);
4158 OSRF_BUFFER_ADD(order_buf, ", ");
4160 order_buf = buffer_init(128);
4162 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
4166 // IT'S THE OOOOOOOOOOOLD STYLE!
4168 osrfLogError(OSRF_LOG_MARK,
4169 "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
4171 osrfAppSessionStatus(
4173 OSRF_STATUS_INTERNALSERVERERROR,
4174 "osrfMethodException",
4176 "Severe query error -- see error log for more details"
4181 buffer_free(group_buf);
4182 buffer_free(order_buf);
4183 buffer_free(sql_buf);
4184 if (defaultselhash) jsonObjectFree(defaultselhash);
4185 jsonIteratorFree(class_itr);
4189 jsonIteratorFree( class_itr );
4191 osrfLogError(OSRF_LOG_MARK,
4192 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4193 MODULENAME, json_type( order_hash->type ) );
4195 osrfAppSessionStatus(
4197 OSRF_STATUS_INTERNALSERVERERROR,
4198 "osrfMethodException",
4200 "Malformed ORDER BY clause -- see error log for more details"
4202 buffer_free( order_buf );
4204 buffer_free(group_buf);
4205 buffer_free(sql_buf);
4206 if (defaultselhash) jsonObjectFree(defaultselhash);
4211 order_by_list = buffer_release( order_buf );
4215 string = buffer_release(group_buf);
4217 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4218 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4219 OSRF_BUFFER_ADD( sql_buf, string );
4224 if( having_buf && *having_buf ) {
4225 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4226 OSRF_BUFFER_ADD( sql_buf, having_buf );
4230 if( order_by_list ) {
4232 if ( *order_by_list ) {
4233 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4234 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4237 free( order_by_list );
4241 const char* str = jsonObjectGetString(limit);
4242 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
4246 const char* str = jsonObjectGetString(offset);
4247 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
4250 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4252 if (defaultselhash) jsonObjectFree(defaultselhash);
4254 return buffer_release(sql_buf);
4256 } // end of SELECT()
4258 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4260 const char* locale = osrf_message_get_last_locale();
4262 osrfHash* fields = osrfHashGet(meta, "fields");
4263 char* core_class = osrfHashGet(meta, "classname");
4265 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4267 jsonObject* node = NULL;
4268 jsonObject* snode = NULL;
4269 jsonObject* onode = NULL;
4270 const jsonObject* _tmp = NULL;
4271 jsonObject* selhash = NULL;
4272 jsonObject* defaultselhash = NULL;
4274 growing_buffer* sql_buf = buffer_init(128);
4275 growing_buffer* select_buf = buffer_init(128);
4277 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4278 defaultselhash = jsonNewObjectType(JSON_HASH);
4279 selhash = defaultselhash;
4282 // If there's no SELECT list for the core class, build one
4283 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
4284 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4286 // Add every non-virtual field to the field list
4287 osrfHash* field_def = NULL;
4288 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4289 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4290 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4291 const char* field = osrfHashIteratorKey( field_itr );
4292 jsonObjectPush( field_list, jsonNewObject( field ) );
4295 osrfHashIteratorFree( field_itr );
4296 jsonObjectSetKey( selhash, core_class, field_list );
4300 jsonIterator* class_itr = jsonNewIterator( selhash );
4301 while ( (snode = jsonIteratorNext( class_itr )) ) {
4303 const char* cname = class_itr->key;
4304 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4305 if (!idlClass) continue;
4307 if (strcmp(core_class,class_itr->key)) {
4308 if (!join_hash) continue;
4310 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
4312 jsonObjectFree(found);
4316 jsonObjectFree(found);
4319 jsonIterator* select_itr = jsonNewIterator( snode );
4320 while ( (node = jsonIteratorNext( select_itr )) ) {
4321 const char* item_str = jsonObjectGetString( node );
4322 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4323 char* fname = osrfHashGet(field, "name");
4325 if (!field) continue;
4330 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
4335 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4336 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4339 i18n = osrfHashGet(field, "i18n");
4341 if( str_is_true( i18n ) ) {
4342 char* pkey = osrfHashGet(idlClass, "primarykey");
4343 char* tname = osrfHashGet(idlClass, "tablename");
4345 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);
4347 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4350 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4354 jsonIteratorFree(select_itr);
4357 jsonIteratorFree(class_itr);
4359 char* col_list = buffer_release(select_buf);
4360 char* table = getSourceDefinition(meta);
4362 table = strdup( "(null)" );
4364 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4368 // Clear the query stack (as a fail-safe precaution against possible
4369 // leftover garbage); then push the first query frame onto the stack.
4370 clear_query_stack();
4372 if( add_query_core( NULL, core_class ) ) {
4374 osrfAppSessionStatus(
4376 OSRF_STATUS_INTERNALSERVERERROR,
4377 "osrfMethodException",
4379 "Unable to build query frame for core class"
4385 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4386 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
4387 OSRF_BUFFER_ADD(sql_buf, join_clause);
4391 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4392 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
4394 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
4396 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4398 osrfAppSessionStatus(
4400 OSRF_STATUS_INTERNALSERVERERROR,
4401 "osrfMethodException",
4403 "Severe query error -- see error log for more details"
4405 buffer_free(sql_buf);
4406 if(defaultselhash) jsonObjectFree(defaultselhash);
4407 clear_query_stack();
4410 buffer_add(sql_buf, pred);
4415 char* string = NULL;
4416 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4418 growing_buffer* order_buf = buffer_init(128);
4421 jsonIterator* class_itr = jsonNewIterator( _tmp );
4422 while ( (snode = jsonIteratorNext( class_itr )) ) {
4424 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4427 if ( snode->type == JSON_HASH ) {
4429 jsonIterator* order_itr = jsonNewIterator( snode );
4430 while ( (onode = jsonIteratorNext( order_itr )) ) {
4432 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4433 class_itr->key, order_itr->key );
4437 char* direction = NULL;
4438 if ( onode->type == JSON_HASH ) {
4439 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4440 string = searchFieldTransform( class_itr->key, field_def, onode );
4442 osrfAppSessionStatus(
4444 OSRF_STATUS_INTERNALSERVERERROR,
4445 "osrfMethodException",
4447 "Severe query error in ORDER BY clause -- see error log for more details"
4449 jsonIteratorFree( order_itr );
4450 jsonIteratorFree( class_itr );
4451 buffer_free( order_buf );
4452 buffer_free( sql_buf );
4453 if( defaultselhash ) jsonObjectFree( defaultselhash );
4454 clear_query_stack();
4458 growing_buffer* field_buf = buffer_init(16);
4459 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4460 string = buffer_release(field_buf);
4463 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4464 const char* dir = jsonObjectGetString(_tmp);
4465 if (!strncasecmp(dir, "d", 1)) {
4466 direction = " DESC";
4473 string = strdup(order_itr->key);
4474 const char* dir = jsonObjectGetString(onode);
4475 if (!strncasecmp(dir, "d", 1)) {
4476 direction = " DESC";
4485 buffer_add(order_buf, ", ");
4488 buffer_add(order_buf, string);
4492 buffer_add(order_buf, direction);
4497 jsonIteratorFree(order_itr);
4500 const char* str = jsonObjectGetString(snode);
4501 buffer_add(order_buf, str);
4507 jsonIteratorFree(class_itr);
4509 string = buffer_release(order_buf);
4512 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4513 OSRF_BUFFER_ADD( sql_buf, string );
4519 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4520 const char* str = jsonObjectGetString(_tmp);
4528 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4530 const char* str = jsonObjectGetString(_tmp);
4539 if (defaultselhash) jsonObjectFree(defaultselhash);
4540 clear_query_stack();
4542 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4543 return buffer_release(sql_buf);
4546 int doJSONSearch ( osrfMethodContext* ctx ) {
4547 if(osrfMethodVerifyContext( ctx )) {
4548 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4552 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
4557 dbhandle = writehandle;
4559 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4563 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4564 flags |= SELECT_DISTINCT;
4566 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4567 flags |= DISABLE_I18N;
4569 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4570 clear_query_stack(); // a possibly needless precaution
4571 char* sql = buildQuery( ctx, hash, flags );
4572 clear_query_stack();
4579 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4580 dbi_result result = dbi_conn_query(dbhandle, sql);
4583 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4585 if (dbi_result_first_row(result)) {
4586 /* JSONify the result */
4587 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4590 jsonObject* return_val = oilsMakeJSONFromResult( result );
4591 osrfAppRespond( ctx, return_val );
4592 jsonObjectFree( return_val );
4593 } while (dbi_result_next_row(result));
4596 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4599 osrfAppRespondComplete( ctx, NULL );
4601 /* clean up the query */
4602 dbi_result_free(result);
4606 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4607 osrfAppSessionStatus(
4609 OSRF_STATUS_INTERNALSERVERERROR,
4610 "osrfMethodException",
4612 "Severe query error -- see error log for more details"
4620 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4621 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4624 dbhandle = writehandle;
4626 osrfHash* links = osrfHashGet(meta, "links");
4627 osrfHash* fields = osrfHashGet(meta, "fields");
4628 char* core_class = osrfHashGet(meta, "classname");
4629 char* pkey = osrfHashGet(meta, "primarykey");
4631 const jsonObject* _tmp;
4634 char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4636 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4641 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4643 dbi_result result = dbi_conn_query(dbhandle, sql);
4644 if( NULL == result ) {
4645 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4646 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4647 osrfAppSessionStatus(
4649 OSRF_STATUS_INTERNALSERVERERROR,
4650 "osrfMethodException",
4652 "Severe query error -- see error log for more details"
4659 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4662 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
4663 osrfHash* dedup = osrfNewHash();
4665 if (dbi_result_first_row(result)) {
4666 /* JSONify the result */
4667 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4669 obj = oilsMakeFieldmapperFromResult( result, meta );
4670 char* pkey_val = oilsFMGetString( obj, pkey );
4671 if ( osrfHashGet( dedup, pkey_val ) ) {
4672 jsonObjectFree(obj);
4675 osrfHashSet( dedup, pkey_val, pkey_val );
4676 jsonObjectPush(res_list, obj);
4678 } while (dbi_result_next_row(result));
4680 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
4684 osrfHashFree(dedup);
4685 /* clean up the query */
4686 dbi_result_free(result);
4689 if (res_list->size && query_hash) {
4690 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
4692 int x = (int)jsonObjectGetNumber(_tmp);
4693 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
4695 const jsonObject* temp_blob;
4696 if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
4698 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
4699 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
4701 osrfStringArray* link_fields = NULL;
4704 if (flesh_fields->size == 1) {
4705 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
4706 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
4711 link_fields = osrfNewStringArray(1);
4712 jsonIterator* _i = jsonNewIterator( flesh_fields );
4713 while ((_f = jsonIteratorNext( _i ))) {
4714 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4716 jsonIteratorFree(_i);
4721 unsigned long res_idx = 0;
4722 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
4725 const char* link_field;
4727 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4729 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4731 osrfHash* kid_link = osrfHashGet(links, link_field);
4732 if (!kid_link) continue;
4734 osrfHash* field = osrfHashGet(fields, link_field);
4735 if (!field) continue;
4737 osrfHash* value_field = field;
4739 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4740 if (!kid_idl) continue;
4742 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4743 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4746 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4747 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4750 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4752 if (link_map->size > 0) {
4753 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4756 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4761 osrfHashGet(kid_link, "class"),
4768 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4769 osrfHashGet(kid_link, "field"),
4770 osrfHashGet(kid_link, "class"),
4771 osrfHashGet(kid_link, "key"),
4772 osrfHashGet(kid_link, "reltype")
4775 const char* search_key = jsonObjectGetString(
4778 atoi( osrfHashGet(value_field, "array_position") )
4783 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4787 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
4789 // construct WHERE clause
4790 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
4793 osrfHashGet(kid_link, "key"),
4794 jsonNewObject( search_key )
4797 // construct the rest of the query
4798 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
4799 jsonObjectSetKey( rest_of_query, "flesh",
4800 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4804 jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
4806 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
4807 jsonObjectSetKey( rest_of_query, "order_by",
4808 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
4812 if (jsonObjectGetKeyConst(query_hash, "select")) {
4813 jsonObjectSetKey( rest_of_query, "select",
4814 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
4818 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
4819 where_clause, rest_of_query, err);
4821 jsonObjectFree( where_clause );
4822 jsonObjectFree( rest_of_query );
4825 osrfStringArrayFree(link_fields);
4826 jsonObjectFree(res_list);
4827 jsonObjectFree(flesh_blob);
4831 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4833 jsonObject* X = NULL;
4834 if ( link_map->size > 0 && kids->size > 0 ) {
4836 kids = jsonNewObjectType(JSON_ARRAY);
4838 jsonObject* _k_node;
4839 unsigned long res_idx = 0;
4840 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
4846 (unsigned long)atoi(
4852 osrfHashGet(kid_link, "class")
4856 osrfStringArrayGetString( link_map, 0 )
4864 } // end while loop traversing X
4867 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4868 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4871 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4872 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4876 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4877 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4880 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4881 jsonObjectClone( kids )
4886 jsonObjectFree(kids);
4890 jsonObjectFree( kids );
4892 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4893 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4896 } // end while loop traversing res_list
4897 jsonObjectFree( flesh_blob );
4898 osrfStringArrayFree(link_fields);
4907 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
4909 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4911 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
4913 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
4916 if (!verifyObjectClass(ctx, target)) {
4921 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4922 osrfAppSessionStatus(
4924 OSRF_STATUS_BADREQUEST,
4925 "osrfMethodException",
4927 "No active transaction -- required for UPDATE"
4933 // The following test is harmless but redundant. If a class is
4934 // readonly, we don't register an update method for it.
4935 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4936 osrfAppSessionStatus(
4938 OSRF_STATUS_BADREQUEST,
4939 "osrfMethodException",
4941 "Cannot UPDATE readonly class"
4947 dbhandle = writehandle;
4949 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4951 // Set the last_xact_id
4952 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4954 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
4955 trans_id, target->classname, index);
4956 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4959 char* pkey = osrfHashGet(meta, "primarykey");
4960 osrfHash* fields = osrfHashGet(meta, "fields");
4962 char* id = oilsFMGetString( target, pkey );
4966 "%s updating %s object with %s = %s",
4968 osrfHashGet(meta, "fieldmapper"),
4973 growing_buffer* sql = buffer_init(128);
4974 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4977 osrfHash* field_def = NULL;
4978 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4979 while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
4981 // Skip virtual fields, and the primary key
4982 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
4985 const char* field_name = osrfHashIteratorKey( field_itr );
4986 if( ! strcmp( field_name, pkey ) )
4989 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4991 int value_is_numeric = 0; // boolean
4993 if (field_object && field_object->classname) {
4994 value = oilsFMGetString(
4996 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4998 } else if( JSON_BOOL == field_object->type ) {
4999 if( jsonBoolIsTrue( field_object ) )
5000 value = strdup( "t" );
5002 value = strdup( "f" );
5004 value = jsonObjectToSimpleString( field_object );
5005 if( field_object && JSON_NUMBER == field_object->type )
5006 value_is_numeric = 1;
5009 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5010 osrfHashGet(meta, "fieldmapper"), field_name, value);
5012 if (!field_object || field_object->type == JSON_NULL) {
5013 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
5014 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5015 if (first) first = 0;
5016 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5017 buffer_fadd( sql, " %s = NULL", field_name );
5020 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5021 if (first) first = 0;
5022 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5024 const char* numtype = get_datatype( field_def );
5025 if ( !strncmp( numtype, "INT", 3 ) ) {
5026 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
5027 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
5028 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
5030 // Must really be intended as a string, so quote it
5031 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5032 buffer_fadd( sql, " %s = %s", field_name, value );
5034 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5035 osrfAppSessionStatus(
5037 OSRF_STATUS_INTERNALSERVERERROR,
5038 "osrfMethodException",
5040 "Error quoting string -- please see the error log for more details"
5044 osrfHashIteratorFree( field_itr );
5051 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5054 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5055 if (first) first = 0;
5056 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5057 buffer_fadd( sql, " %s = %s", field_name, value );
5060 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5061 osrfAppSessionStatus(
5063 OSRF_STATUS_INTERNALSERVERERROR,
5064 "osrfMethodException",
5066 "Error quoting string -- please see the error log for more details"
5070 osrfHashIteratorFree( field_itr );
5081 osrfHashIteratorFree( field_itr );
5083 jsonObject* obj = jsonNewObject(id);
5085 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5086 dbi_conn_quote_string(dbhandle, &id);
5088 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5090 char* query = buffer_release(sql);
5091 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
5093 dbi_result result = dbi_conn_query(dbhandle, query);
5097 jsonObjectFree(obj);
5098 obj = jsonNewObject(NULL);
5101 "%s ERROR updating %s object with %s = %s",
5103 osrfHashGet(meta, "fieldmapper"),
5114 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
5116 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5118 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
5119 osrfAppSessionStatus(
5121 OSRF_STATUS_BADREQUEST,
5122 "osrfMethodException",
5124 "No active transaction -- required for DELETE"
5130 // The following test is harmless but redundant. If a class is
5131 // readonly, we don't register a delete method for it.
5132 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5133 osrfAppSessionStatus(
5135 OSRF_STATUS_BADREQUEST,
5136 "osrfMethodException",
5138 "Cannot DELETE readonly class"
5144 dbhandle = writehandle;
5148 char* pkey = osrfHashGet(meta, "primarykey");
5156 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
5157 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5162 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5165 if (!verifyObjectPCRUD( ctx, NULL )) {
5170 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
5175 "%s deleting %s object with %s = %s",
5177 osrfHashGet(meta, "fieldmapper"),
5182 obj = jsonNewObject(id);
5184 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5185 dbi_conn_quote_string(writehandle, &id);
5187 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
5190 jsonObjectFree(obj);
5191 obj = jsonNewObject(NULL);
5194 "%s ERROR deleting %s object with %s = %s",
5196 osrfHashGet(meta, "fieldmapper"),
5209 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5210 if(!(result && meta)) return jsonNULL;
5212 jsonObject* object = jsonNewObject(NULL);
5213 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
5215 osrfHash* fields = osrfHashGet(meta, "fields");
5217 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
5221 char dt_string[256];
5225 int columnIndex = 1;
5227 unsigned short type;
5228 const char* columnName;
5230 /* cycle through the column list */
5231 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5233 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5235 fmIndex = -1; // reset the position
5237 /* determine the field type and storage attributes */
5238 type = dbi_result_get_field_type_idx(result, columnIndex);
5239 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5241 /* fetch the fieldmapper index */
5242 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
5244 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
5247 const char* pos = (char*)osrfHashGet(_f, "array_position");
5248 if ( !pos ) continue;
5250 fmIndex = atoi( pos );
5251 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
5256 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5257 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
5262 case DBI_TYPE_INTEGER :
5264 if( attr & DBI_INTEGER_SIZE8 )
5265 jsonObjectSetIndex( object, fmIndex,
5266 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
5268 jsonObjectSetIndex( object, fmIndex,
5269 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
5273 case DBI_TYPE_DECIMAL :
5274 jsonObjectSetIndex( object, fmIndex,
5275 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
5278 case DBI_TYPE_STRING :
5284 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
5289 case DBI_TYPE_DATETIME :
5291 memset(dt_string, '\0', sizeof(dt_string));
5292 memset(&gmdt, '\0', sizeof(gmdt));
5294 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5297 if (!(attr & DBI_DATETIME_DATE)) {
5298 gmtime_r( &_tmp_dt, &gmdt );
5299 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5300 } else if (!(attr & DBI_DATETIME_TIME)) {
5301 localtime_r( &_tmp_dt, &gmdt );
5302 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5304 localtime_r( &_tmp_dt, &gmdt );
5305 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5308 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
5312 case DBI_TYPE_BINARY :
5313 osrfLogError( OSRF_LOG_MARK,
5314 "Can't do binary at column %s : index %d", columnName, columnIndex);
5323 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5324 if(!result) return jsonNULL;
5326 jsonObject* object = jsonNewObject(NULL);
5329 char dt_string[256];
5333 int columnIndex = 1;
5335 unsigned short type;
5336 const char* columnName;
5338 /* cycle through the column list */
5339 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5341 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5343 fmIndex = -1; // reset the position
5345 /* determine the field type and storage attributes */
5346 type = dbi_result_get_field_type_idx(result, columnIndex);
5347 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5349 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5350 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
5355 case DBI_TYPE_INTEGER :
5357 if( attr & DBI_INTEGER_SIZE8 )
5358 jsonObjectSetKey( object, columnName,
5359 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)) );
5361 jsonObjectSetKey( object, columnName,
5362 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
5365 case DBI_TYPE_DECIMAL :
5366 jsonObjectSetKey( object, columnName,
5367 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
5370 case DBI_TYPE_STRING :
5371 jsonObjectSetKey( object, columnName,
5372 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
5375 case DBI_TYPE_DATETIME :
5377 memset(dt_string, '\0', sizeof(dt_string));
5378 memset(&gmdt, '\0', sizeof(gmdt));
5380 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5383 if (!(attr & DBI_DATETIME_DATE)) {
5384 gmtime_r( &_tmp_dt, &gmdt );
5385 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5386 } else if (!(attr & DBI_DATETIME_TIME)) {
5387 localtime_r( &_tmp_dt, &gmdt );
5388 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5390 localtime_r( &_tmp_dt, &gmdt );
5391 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5394 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
5397 case DBI_TYPE_BINARY :
5398 osrfLogError( OSRF_LOG_MARK,
5399 "Can't do binary at column %s : index %d", columnName, columnIndex );
5403 } // end while loop traversing result
5408 // Interpret a string as true or false
5409 static int str_is_true( const char* str ) {
5410 if( NULL == str || strcasecmp( str, "true" ) )
5416 // Interpret a jsonObject as true or false
5417 static int obj_is_true( const jsonObject* obj ) {
5420 else switch( obj->type )
5428 if( strcasecmp( obj->value.s, "true" ) )
5432 case JSON_NUMBER : // Support 1/0 for perl's sake
5433 if( jsonObjectGetNumber( obj ) == 1.0 )
5442 // Translate a numeric code into a text string identifying a type of
5443 // jsonObject. To be used for building error messages.
5444 static const char* json_type( int code ) {
5450 return "JSON_ARRAY";
5452 return "JSON_STRING";
5454 return "JSON_NUMBER";
5460 return "(unrecognized)";
5464 // Extract the "primitive" attribute from an IDL field definition.
5465 // If we haven't initialized the app, then we must be running in
5466 // some kind of testbed. In that case, default to "string".
5467 static const char* get_primitive( osrfHash* field ) {
5468 const char* s = osrfHashGet( field, "primitive" );
5470 if( child_initialized )
5473 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5475 osrfHashGet( field, "name" )
5483 // Extract the "datatype" attribute from an IDL field definition.
5484 // If we haven't initialized the app, then we must be running in
5485 // some kind of testbed. In that case, default to to NUMERIC,
5486 // since we look at the datatype only for numbers.
5487 static const char* get_datatype( osrfHash* field ) {
5488 const char* s = osrfHashGet( field, "datatype" );
5490 if( child_initialized )
5493 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5495 osrfHashGet( field, "name" )
5504 If the input string is potentially a valid SQL identifier, return 1.
5507 Purpose: to prevent certain kinds of SQL injection. To that end we
5508 don't necessarily need to follow all the rules exactly, such as requiring
5509 that the first character not be a digit.
5511 We allow leading and trailing white space. In between, we do not allow
5512 punctuation (except for underscores and dollar signs), control
5513 characters, or embedded white space.
5515 More pedantically we should allow quoted identifiers containing arbitrary
5516 characters, but for the foreseeable future such quoted identifiers are not
5517 likely to be an issue.
5519 static int is_identifier( const char* s) {
5523 // Skip leading white space
5524 while( isspace( (unsigned char) *s ) )
5528 return 0; // Nothing but white space? Not okay.
5530 // Check each character until we reach white space or
5531 // end-of-string. Letters, digits, underscores, and
5532 // dollar signs are okay. With the exception of periods
5533 // (as in schema.identifier), control characters and other
5534 // punctuation characters are not okay. Anything else
5535 // is okay -- it could for example be part of a multibyte
5536 // UTF8 character such as a letter with diacritical marks,
5537 // and those are allowed.
5539 if( isalnum( (unsigned char) *s )
5543 ; // Fine; keep going
5544 else if( ispunct( (unsigned char) *s )
5545 || iscntrl( (unsigned char) *s ) )
5548 } while( *s && ! isspace( (unsigned char) *s ) );
5550 // If we found any white space in the above loop,
5551 // the rest had better be all white space.
5553 while( isspace( (unsigned char) *s ) )
5557 return 0; // White space was embedded within non-white space
5563 Determine whether to accept a character string as a comparison operator.
5564 Return 1 if it's good, or 0 if it's bad.
5566 We don't validate it for real. We just make sure that it doesn't contain
5567 any semicolons or white space (with special exceptions for a few specific
5568 operators). The idea is to block certain kinds of SQL injection. If it
5569 has no semicolons or white space but it's still not a valid operator, then
5570 the database will complain.
5572 Another approach would be to compare the string against a short list of
5573 approved operators. We don't do that because we want to allow custom
5574 operators like ">100*", which would be difficult or impossible to
5575 express otherwise in a JSON query.
5577 static int is_good_operator( const char* op ) {
5578 if( !op ) return 0; // Sanity check
5582 if( isspace( (unsigned char) *s ) ) {
5583 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
5584 // and IS NOT DISTINCT FROM.
5585 if( !strcasecmp( op, "similar to" ) )
5587 else if( !strcasecmp( op, "is distinct from" ) )
5589 else if( !strcasecmp( op, "is not distinct from" ) )
5594 else if( ';' == *s )
5601 /* ----------------------------------------------------------------------------------
5602 The following machinery supports a stack of query frames for use by SELECT().
5604 A query frame caches information about one level of a SELECT query. When we enter
5605 a subquery, we push another query frame onto the stack, and pop it off when we leave.
5607 The query frame stores information about the core class, and about any joined classes
5610 The main purpose is to map table aliases to classes and tables, so that a query can
5611 join to the same table more than once. A secondary goal is to reduce the number of
5612 lookups in the IDL by caching the results.
5613 ----------------------------------------------------------------------------------*/
5615 #define STATIC_CLASS_INFO_COUNT 3
5617 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
5619 /* ---------------------------------------------------------------------------
5620 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5622 ---------------------------------------------------------------------------*/
5623 static ClassInfo* allocate_class_info( void ) {
5624 // In order to reduce the number of mallocs and frees, we return a static
5625 // instance of ClassInfo, if we can find one that we're not already using.
5626 // We rely on the fact that the compiler will implicitly initialize the
5627 // static instances so that in_use == 0.
5630 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5631 if( ! static_class_info[ i ].in_use ) {
5632 static_class_info[ i ].in_use = 1;
5633 return static_class_info + i;
5637 // The static ones are all in use. Malloc one.
5639 return safe_malloc( sizeof( ClassInfo ) );
5642 /* --------------------------------------------------------------------------
5643 Free any malloc'd memory owned by a ClassInfo; return it to a pristine state
5644 ---------------------------------------------------------------------------*/
5645 static void clear_class_info( ClassInfo* info ) {
5650 // Free any malloc'd strings
5652 if( info->alias != info->alias_store )
5653 free( info->alias );
5655 if( info->class_name != info->class_name_store )
5656 free( info->class_name );
5658 free( info->source_def );
5660 info->alias = info->class_name = info->source_def = NULL;
5664 /* --------------------------------------------------------------------------
5665 Deallocate a ClassInfo and everything it owns
5666 ---------------------------------------------------------------------------*/
5667 static void free_class_info( ClassInfo* info ) {
5672 clear_class_info( info );
5674 // If it's one of the static instances, just mark it as not in use
5677 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5678 if( info == static_class_info + i ) {
5679 static_class_info[ i ].in_use = 0;
5684 // Otherwise it must have been malloc'd, so free it
5689 /* --------------------------------------------------------------------------
5690 Populate an already-allocated ClassInfo. Return 0 if successful, 1 if not.
5691 ---------------------------------------------------------------------------*/
5692 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
5695 osrfLogError( OSRF_LOG_MARK,
5696 "%s ERROR: No ClassInfo available to populate", MODULENAME );
5697 info->alias = info->class_name = info->source_def = NULL;
5698 info->class_def = info->fields = info->links = NULL;
5703 osrfLogError( OSRF_LOG_MARK,
5704 "%s ERROR: No class name provided for lookup", MODULENAME );
5705 info->alias = info->class_name = info->source_def = NULL;
5706 info->class_def = info->fields = info->links = NULL;
5710 // Alias defaults to class name if not supplied
5711 if( ! alias || ! alias[ 0 ] )
5714 // Look up class info in the IDL
5715 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
5717 osrfLogError( OSRF_LOG_MARK,
5718 "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
5719 info->alias = info->class_name = info->source_def = NULL;
5720 info->class_def = info->fields = info->links = NULL;
5722 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
5723 osrfLogError( OSRF_LOG_MARK,
5724 "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
5725 info->alias = info->class_name = info->source_def = NULL;
5726 info->class_def = info->fields = info->links = NULL;
5730 osrfHash* links = osrfHashGet( class_def, "links" );
5732 osrfLogError( OSRF_LOG_MARK,
5733 "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
5734 info->alias = info->class_name = info->source_def = NULL;
5735 info->class_def = info->fields = info->links = NULL;
5739 osrfHash* fields = osrfHashGet( class_def, "fields" );
5741 osrfLogError( OSRF_LOG_MARK,
5742 "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
5743 info->alias = info->class_name = info->source_def = NULL;
5744 info->class_def = info->fields = info->links = NULL;
5748 char* source_def = getSourceDefinition( class_def );
5752 // We got everything we need, so populate the ClassInfo
5753 if( strlen( alias ) > ALIAS_STORE_SIZE )
5754 info->alias = strdup( alias );
5756 strcpy( info->alias_store, alias );
5757 info->alias = info->alias_store;
5760 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
5761 info->class_name = strdup( class );
5763 strcpy( info->class_name_store, class );
5764 info->class_name = info->class_name_store;
5767 info->source_def = source_def;
5769 info->class_def = class_def;
5770 info->links = links;
5771 info->fields = fields;
5776 #define STATIC_FRAME_COUNT 3
5778 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
5780 /* ---------------------------------------------------------------------------
5781 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5783 ---------------------------------------------------------------------------*/
5784 static QueryFrame* allocate_frame( void ) {
5785 // In order to reduce the number of mallocs and frees, we return a static
5786 // instance of QueryFrame, if we can find one that we're not already using.
5787 // We rely on the fact that the compiler will implicitly initialize the
5788 // static instances so that in_use == 0.
5791 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5792 if( ! static_frame[ i ].in_use ) {
5793 static_frame[ i ].in_use = 1;
5794 return static_frame + i;
5798 // The static ones are all in use. Malloc one.
5800 return safe_malloc( sizeof( QueryFrame ) );
5803 /* --------------------------------------------------------------------------
5804 Free a QueryFrame, and all the memory it owns.
5805 ---------------------------------------------------------------------------*/
5806 static void free_query_frame( QueryFrame* frame ) {
5811 clear_class_info( &frame->core );
5813 // Free the join list
5815 ClassInfo* info = frame->join_list;
5818 free_class_info( info );
5822 frame->join_list = NULL;
5825 // If the frame is a static instance, just mark it as unused
5827 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5828 if( frame == static_frame + i ) {
5829 static_frame[ i ].in_use = 0;
5834 // Otherwise it must have been malloc'd, so free it
5839 /* --------------------------------------------------------------------------
5840 Search a given QueryFrame for a specified alias. If you find it, return
5841 a pointer to the corresponding ClassInfo. Otherwise return NULL.
5842 ---------------------------------------------------------------------------*/
5843 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
5844 if( ! frame || ! target ) {
5848 ClassInfo* found_class = NULL;
5850 if( !strcmp( target, frame->core.alias ) )
5851 return &(frame->core);
5853 ClassInfo* curr_class = frame->join_list;
5854 while( curr_class ) {
5855 if( strcmp( target, curr_class->alias ) )
5856 curr_class = curr_class->next;
5858 found_class = curr_class;
5867 /* --------------------------------------------------------------------------
5868 Push a new (blank) QueryFrame onto the stack.
5869 ---------------------------------------------------------------------------*/
5870 static void push_query_frame( void ) {
5871 QueryFrame* frame = allocate_frame();
5872 frame->join_list = NULL;
5873 frame->next = curr_query;
5875 // Initialize the ClassInfo for the core class
5876 ClassInfo* core = &frame->core;
5877 core->alias = core->class_name = core->source_def = NULL;
5878 core->class_def = core->fields = core->links = NULL;
5883 /* --------------------------------------------------------------------------
5884 Pop a QueryFrame off the stack and destroy it
5885 ---------------------------------------------------------------------------*/
5886 static void pop_query_frame( void ) {
5891 QueryFrame* popped = curr_query;
5892 curr_query = popped->next;
5894 free_query_frame( popped );
5897 /* --------------------------------------------------------------------------
5898 Populate the ClassInfo for the core class. Return 0 if successful, 1 if not.
5899 ---------------------------------------------------------------------------*/
5900 static int add_query_core( const char* alias, const char* class_name ) {
5903 if( ! curr_query ) {
5904 osrfLogError( OSRF_LOG_MARK,
5905 "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
5907 } else if( curr_query->core.alias ) {
5908 osrfLogError( OSRF_LOG_MARK,
5909 "%s ERROR: Core class %s already populated as %s",
5910 MODULENAME, curr_query->core.class_name, curr_query->core.alias );
5914 build_class_info( &curr_query->core, alias, class_name );
5915 if( curr_query->core.alias )
5918 osrfLogError( OSRF_LOG_MARK,
5919 "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
5924 /* --------------------------------------------------------------------------
5925 Search the current QueryFrame for a specified alias. If you find it,
5926 return a pointer to the corresponding ClassInfo. Otherwise return NULL.
5927 ---------------------------------------------------------------------------*/
5928 static ClassInfo* search_alias( const char* target ) {
5929 return search_alias_in_frame( curr_query, target );
5932 /* --------------------------------------------------------------------------
5933 Search all levels of query for a specified alias, starting with the
5934 current query. If you find it, return a pointer to the corresponding
5935 ClassInfo. Otherwise return NULL.
5936 ---------------------------------------------------------------------------*/
5937 static ClassInfo* search_all_alias( const char* target ) {
5938 ClassInfo* found_class = NULL;
5939 QueryFrame* curr_frame = curr_query;
5941 while( curr_frame ) {
5942 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
5945 curr_frame = curr_frame->next;
5951 /* --------------------------------------------------------------------------
5952 Add a class to the list of classes joined to the current query.
5953 ---------------------------------------------------------------------------*/
5954 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
5956 if( ! classname || ! *classname ) { // sanity check
5957 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
5964 const ClassInfo* conflict = search_alias( alias );
5966 osrfLogError( OSRF_LOG_MARK,
5967 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
5968 MODULENAME, alias, conflict->class_name );
5972 ClassInfo* info = allocate_class_info();
5974 if( build_class_info( info, alias, classname ) ) {
5975 free_class_info( info );
5979 // Add the new ClassInfo to the join list of the current QueryFrame
5980 info->next = curr_query->join_list;
5981 curr_query->join_list = info;
5986 /* --------------------------------------------------------------------------
5987 Destroy all nodes on the query stack.
5988 ---------------------------------------------------------------------------*/
5989 static void clear_query_stack( void ) {