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"
27 #define DISABLE_I18N 2
28 #define SELECT_DISTINCT 1
32 struct ClassInfoStruct;
33 typedef struct ClassInfoStruct ClassInfo;
35 #define ALIAS_STORE_SIZE 16
36 #define CLASS_NAME_STORE_SIZE 16
38 struct ClassInfoStruct {
42 osrfHash* class_def; // Points into IDL
43 osrfHash* fields; // Points into IDL
44 osrfHash* links; // Points into IDL
46 // The remaining members are private and internal. Client code should not
47 // access them directly.
49 ClassInfo* next; // Supports linked list of joined classes
50 int in_use; // boolean
52 // We usually store the alias and class name in the following arrays, and
53 // point the corresponding pointers at them. When the string is too big
54 // for the array (which will probably never happen in practice), we strdup it.
56 char alias_store[ ALIAS_STORE_SIZE + 1 ];
57 char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
60 struct QueryFrameStruct;
61 typedef struct QueryFrameStruct QueryFrame;
63 struct QueryFrameStruct {
65 ClassInfo* join_list; // linked list of classes joined to the core class
66 QueryFrame* next; // implements stack as linked list
67 int in_use; // boolean
70 int osrfAppChildInit();
71 int osrfAppInitialize();
72 void osrfAppChildExit();
74 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
76 int beginTransaction ( osrfMethodContext* );
77 int commitTransaction ( osrfMethodContext* );
78 int rollbackTransaction ( osrfMethodContext* );
80 int setSavepoint ( osrfMethodContext* );
81 int releaseSavepoint ( osrfMethodContext* );
82 int rollbackSavepoint ( osrfMethodContext* );
84 int doJSONSearch ( osrfMethodContext* );
86 int dispatchCRUDMethod ( osrfMethodContext* );
87 static jsonObject* doCreate ( osrfMethodContext*, int* );
88 static jsonObject* doRetrieve ( osrfMethodContext*, int* );
89 static jsonObject* doUpdate ( osrfMethodContext*, int* );
90 static jsonObject* doDelete ( osrfMethodContext*, int* );
91 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
92 jsonObject* where_hash, jsonObject* query_hash, int* err );
93 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
94 static jsonObject* oilsMakeJSONFromResult( dbi_result );
96 static char* searchSimplePredicate ( const char* op, const char* class_alias,
97 osrfHash* field, const jsonObject* node );
98 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
99 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject*);
100 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*, const char* );
101 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
102 static char* searchINPredicate ( const char*, osrfHash*,
103 jsonObject*, const char*, osrfMethodContext* );
104 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
105 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
106 static char* searchWHERE ( const jsonObject*, const ClassInfo*, int, osrfMethodContext* );
107 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
109 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
111 void userDataFree( void* );
112 static void sessionDataFree( char*, void* );
113 static char* getSourceDefinition( osrfHash* );
114 static int str_is_true( const char* str );
115 static int obj_is_true( const jsonObject* obj );
116 static const char* json_type( int code );
117 static const char* get_primitive( osrfHash* field );
118 static const char* get_datatype( osrfHash* field );
119 static int is_identifier( const char* s);
120 static int is_good_operator( const char* op );
121 static void pop_query_frame( void );
122 static void push_query_frame( void );
123 static int add_query_core( const char* alias, const char* class_name );
124 static ClassInfo* search_alias( const char* target );
125 static ClassInfo* search_all_alias( const char* target );
126 static ClassInfo* add_joined_class( const char* alias, const char* classname );
127 static void clear_query_stack( void );
130 static jsonObject* verifyUserPCRUD( osrfMethodContext* );
131 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
132 static char* org_tree_root( osrfMethodContext* ctx );
133 static jsonObject* single_hash( const char* key, const char* value );
136 static int child_initialized = 0; /* boolean */
138 static dbi_conn writehandle; /* our MASTER db connection */
139 static dbi_conn dbhandle; /* our CURRENT db connection */
140 //static osrfHash * readHandles;
141 static jsonObject* const jsonNULL = NULL; //
142 static int max_flesh_depth = 100;
144 // The following points the top of a stack of QueryFrames. It's a little
145 // confusing because the top level of the query is at the bottom of the stack.
146 static QueryFrame* curr_query = NULL;
148 /* called when this process is about to exit */
149 void osrfAppChildExit() {
150 osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
153 if (writehandle == dbhandle) same = 1;
155 dbi_conn_query(writehandle, "ROLLBACK;");
156 dbi_conn_close(writehandle);
159 if (dbhandle && !same)
160 dbi_conn_close(dbhandle);
162 // XXX add cleanup of readHandles whenever that gets used
167 int osrfAppInitialize() {
169 osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
170 osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
172 if (!oilsIDLInit( osrf_settings_host_value("/IDL") )) return 1; /* return non-zero to indicate error */
174 growing_buffer* method_name = buffer_init(64);
176 // Generic search thingy
177 buffer_add(method_name, MODULENAME);
178 buffer_add(method_name, ".json_query");
179 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
180 "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
183 // first we register all the transaction and savepoint methods
184 buffer_reset(method_name);
185 OSRF_BUFFER_ADD(method_name, MODULENAME);
186 OSRF_BUFFER_ADD(method_name, ".transaction.begin");
187 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
188 "beginTransaction", "", 0, 0 );
190 buffer_reset(method_name);
191 OSRF_BUFFER_ADD(method_name, MODULENAME);
192 OSRF_BUFFER_ADD(method_name, ".transaction.commit");
193 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
194 "commitTransaction", "", 0, 0 );
196 buffer_reset(method_name);
197 OSRF_BUFFER_ADD(method_name, MODULENAME);
198 OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
199 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
200 "rollbackTransaction", "", 0, 0 );
202 buffer_reset(method_name);
203 OSRF_BUFFER_ADD(method_name, MODULENAME);
204 OSRF_BUFFER_ADD(method_name, ".savepoint.set");
205 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
206 "setSavepoint", "", 1, 0 );
208 buffer_reset(method_name);
209 OSRF_BUFFER_ADD(method_name, MODULENAME);
210 OSRF_BUFFER_ADD(method_name, ".savepoint.release");
211 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
212 "releaseSavepoint", "", 1, 0 );
214 buffer_reset(method_name);
215 OSRF_BUFFER_ADD(method_name, MODULENAME);
216 OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
217 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
218 "rollbackSavepoint", "", 1, 0 );
220 static const char* global_method[] = {
228 const int global_method_count
229 = sizeof( global_method ) / sizeof ( global_method[0] );
231 unsigned long class_count = osrfHashGetCount( oilsIDL() );
232 osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
233 osrfLogDebug(OSRF_LOG_MARK,
234 "At most %lu methods will be generated",
235 (unsigned long) (class_count * global_method_count) );
237 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
238 osrfHash* idlClass = NULL;
240 // For each class in IDL...
241 while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
243 const char* classname = osrfHashIteratorKey( class_itr );
244 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
246 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), MODULENAME )) {
247 osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on", MODULENAME, classname);
251 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
252 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
256 // Look up some other attributes of the current class
257 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
258 if( !idlClass_fieldmapper ) {
259 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL", classname );
264 osrfHash* idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
265 if (!idlClass_permacrud) {
266 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no permacrud in IDL", classname );
270 const char* readonly = osrfHashGet(idlClass, "readonly");
273 for( i = 0; i < global_method_count; ++i ) { // for each global method
274 const char* method_type = global_method[ i ];
275 osrfLogDebug(OSRF_LOG_MARK,
276 "Using files to build %s class methods for %s", method_type, classname);
279 const char* tmp_method = method_type;
280 if ( *tmp_method == 'i' || *tmp_method == 's') {
281 tmp_method = "retrieve";
283 if (!osrfHashGet( idlClass_permacrud, tmp_method )) continue;
286 if ( str_is_true( readonly ) &&
287 ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd')
290 buffer_reset( method_name );
292 buffer_fadd(method_name, "%s.%s.%s", MODULENAME, method_type, classname);
296 char* _fm = strdup( idlClass_fieldmapper );
297 part = strtok_r(_fm, ":", &st_tmp);
299 buffer_fadd(method_name, "%s.direct.%s", MODULENAME, part);
301 while ((part = strtok_r(NULL, ":", &st_tmp))) {
302 OSRF_BUFFER_ADD_CHAR(method_name, '.');
303 OSRF_BUFFER_ADD(method_name, part);
305 OSRF_BUFFER_ADD_CHAR(method_name, '.');
306 OSRF_BUFFER_ADD(method_name, method_type);
310 char* method = buffer_data(method_name);
313 if (*method_type == 'i' || *method_type == 's') {
314 flags = flags | OSRF_METHOD_STREAMING;
317 osrfHash* method_meta = osrfNewHash();
318 osrfHashSet( method_meta, idlClass, "class");
319 osrfHashSet( method_meta, method, "methodname" );
320 osrfHashSet( method_meta, strdup(method_type), "methodtype" );
322 osrfAppRegisterExtendedMethod(
325 "dispatchCRUDMethod",
333 } // end for each global method
334 } // end for each class in IDL
336 buffer_free( method_name );
337 osrfHashIteratorFree( class_itr );
342 static char* getSourceDefinition( osrfHash* class ) {
344 char* tabledef = osrfHashGet(class, "tablename");
347 tabledef = strdup(tabledef);
349 tabledef = osrfHashGet(class, "source_definition");
351 growing_buffer* tablebuf = buffer_init(128);
352 buffer_fadd( tablebuf, "(%s)", tabledef );
353 tabledef = buffer_release(tablebuf);
355 const char* classname = osrfHashGet( class, "classname" );
360 "%s ERROR No tablename or source_definition for class \"%s\"",
371 * Connects to the database
373 int osrfAppChildInit() {
375 osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
376 dbi_initialize(NULL);
377 osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
379 char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
380 char* user = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
381 char* host = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
382 char* port = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
383 char* db = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
384 char* pw = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
385 char* md = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion", MODULENAME);
387 osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
388 writehandle = dbi_conn_new(driver);
391 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
394 osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
396 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
397 "port=%s, user=%s, pw=%s, db=%s", MODULENAME, host, port, user, pw, db );
399 if(host) dbi_conn_set_option(writehandle, "host", host );
400 if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
401 if(user) dbi_conn_set_option(writehandle, "username", user);
402 if(pw) dbi_conn_set_option(writehandle, "password", pw );
403 if(db) dbi_conn_set_option(writehandle, "dbname", db );
405 if(md) max_flesh_depth = atoi(md);
406 if(max_flesh_depth < 0) max_flesh_depth = 1;
407 if(max_flesh_depth > 1000) max_flesh_depth = 1000;
416 if (dbi_conn_connect(writehandle) < 0) {
418 if (dbi_conn_connect(writehandle) < 0) {
419 dbi_conn_error(writehandle, &err);
420 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
425 osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
427 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
428 osrfHash* class = NULL;
430 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
431 const char* classname = osrfHashIteratorKey( class_itr );
432 osrfHash* fields = osrfHashGet( class, "fields" );
434 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
435 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
439 char* tabledef = getSourceDefinition(class);
441 tabledef = strdup( "(null)" );
443 growing_buffer* sql_buf = buffer_init(32);
444 buffer_fadd( sql_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
448 char* sql = buffer_release(sql_buf);
449 osrfLogDebug(OSRF_LOG_MARK, "%s Investigatory SQL = %s", MODULENAME, sql);
451 dbi_result result = dbi_conn_query(writehandle, sql);
457 const char* columnName;
459 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
461 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
463 /* fetch the fieldmapper index */
464 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
466 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", (char*)columnName);
468 /* determine the field type and storage attributes */
470 switch( dbi_result_get_field_type_idx(result, columnIndex) ) {
472 case DBI_TYPE_INTEGER : {
474 if ( !osrfHashGet(_f, "primitive") )
475 osrfHashSet(_f,"number", "primitive");
477 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
478 if( attr & DBI_INTEGER_SIZE8 )
479 osrfHashSet(_f,"INT8", "datatype");
481 osrfHashSet(_f,"INT", "datatype");
484 case DBI_TYPE_DECIMAL :
485 if ( !osrfHashGet(_f, "primitive") )
486 osrfHashSet(_f,"number", "primitive");
488 osrfHashSet(_f,"NUMERIC", "datatype");
491 case DBI_TYPE_STRING :
492 if ( !osrfHashGet(_f, "primitive") )
493 osrfHashSet(_f,"string", "primitive");
494 osrfHashSet(_f,"TEXT", "datatype");
497 case DBI_TYPE_DATETIME :
498 if ( !osrfHashGet(_f, "primitive") )
499 osrfHashSet(_f,"string", "primitive");
501 osrfHashSet(_f,"TIMESTAMP", "datatype");
504 case DBI_TYPE_BINARY :
505 if ( !osrfHashGet(_f, "primitive") )
506 osrfHashSet(_f,"string", "primitive");
508 osrfHashSet(_f,"BYTEA", "datatype");
513 "Setting [%s] to primitive [%s] and datatype [%s]...",
515 osrfHashGet(_f, "primitive"),
516 osrfHashGet(_f, "datatype")
520 } // end while loop for traversing result
521 dbi_result_free(result);
523 osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", (char*)classname);
525 } // end for each class in IDL
527 osrfHashIteratorFree( class_itr );
528 child_initialized = 1;
533 This function is a sleazy hack intended *only* for testing and
534 debugging. Any real server process should initialize the
535 database connection by calling osrfAppChildInit().
537 void set_cstore_dbi_conn( dbi_conn conn ) {
538 dbhandle = writehandle = conn;
541 void userDataFree( void* blob ) {
542 osrfHashFree( (osrfHash*)blob );
546 static void sessionDataFree( char* key, void* item ) {
547 if (!(strcmp(key,"xact_id"))) {
549 dbi_conn_query(writehandle, "ROLLBACK;");
556 int beginTransaction ( osrfMethodContext* ctx ) {
557 if(osrfMethodVerifyContext( ctx )) {
558 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
563 jsonObject* user = verifyUserPCRUD( ctx );
564 if (!user) return -1;
565 jsonObjectFree(user);
568 dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
570 osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
571 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error starting transaction" );
574 jsonObject* ret = jsonNewObject(ctx->session->session_id);
575 osrfAppRespondComplete( ctx, ret );
578 if (!ctx->session->userData) {
579 ctx->session->userData = osrfNewHash();
580 osrfHashSetCallback((osrfHash*)ctx->session->userData, &sessionDataFree);
583 osrfHashSet( (osrfHash*)ctx->session->userData, strdup( ctx->session->session_id ), "xact_id" );
584 ctx->session->userDataFree = &userDataFree;
590 int setSavepoint ( osrfMethodContext* ctx ) {
591 if(osrfMethodVerifyContext( ctx )) {
592 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
599 jsonObject* user = verifyUserPCRUD( ctx );
600 if (!user) return -1;
601 jsonObjectFree(user);
604 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
605 osrfAppSessionStatus(
607 OSRF_STATUS_INTERNALSERVERERROR,
608 "osrfMethodException",
610 "No active transaction -- required for savepoints"
615 const char* spName = jsonObjectGetString(jsonObjectGetIndex(ctx->params, spNamePos));
617 dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
621 "%s: Error creating savepoint %s in transaction %s",
624 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
626 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
627 "osrfMethodException", ctx->request, "Error creating savepoint" );
630 jsonObject* ret = jsonNewObject(spName);
631 osrfAppRespondComplete( ctx, ret );
637 int releaseSavepoint ( osrfMethodContext* ctx ) {
638 if(osrfMethodVerifyContext( ctx )) {
639 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
646 jsonObject* user = verifyUserPCRUD( ctx );
647 if (!user) return -1;
648 jsonObjectFree(user);
651 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
652 osrfAppSessionStatus(
654 OSRF_STATUS_INTERNALSERVERERROR,
655 "osrfMethodException",
657 "No active transaction -- required for savepoints"
662 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
664 dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
668 "%s: Error releasing savepoint %s in transaction %s",
671 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
673 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
674 "osrfMethodException", ctx->request, "Error releasing savepoint" );
677 jsonObject* ret = jsonNewObject(spName);
678 osrfAppRespondComplete( ctx, ret );
684 int rollbackSavepoint ( osrfMethodContext* ctx ) {
685 if(osrfMethodVerifyContext( ctx )) {
686 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
693 jsonObject* user = verifyUserPCRUD( ctx );
694 if (!user) return -1;
695 jsonObjectFree(user);
698 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
699 osrfAppSessionStatus(
701 OSRF_STATUS_INTERNALSERVERERROR,
702 "osrfMethodException",
704 "No active transaction -- required for savepoints"
709 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
711 dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
715 "%s: Error rolling back savepoint %s in transaction %s",
718 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
720 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
721 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
724 jsonObject* ret = jsonNewObject(spName);
725 osrfAppRespondComplete( ctx, ret );
731 int commitTransaction ( osrfMethodContext* ctx ) {
732 if(osrfMethodVerifyContext( ctx )) {
733 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
738 jsonObject* user = verifyUserPCRUD( ctx );
739 if (!user) return -1;
740 jsonObjectFree(user);
743 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
744 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to commit" );
748 dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
750 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
751 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error committing transaction" );
754 osrfHashRemove(ctx->session->userData, "xact_id");
755 jsonObject* ret = jsonNewObject(ctx->session->session_id);
756 osrfAppRespondComplete( ctx, ret );
762 int rollbackTransaction ( osrfMethodContext* ctx ) {
763 if(osrfMethodVerifyContext( ctx )) {
764 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
769 jsonObject* user = verifyUserPCRUD( ctx );
770 if (!user) return -1;
771 jsonObjectFree(user);
774 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
775 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to roll back" );
779 dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
781 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
782 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error rolling back transaction" );
785 osrfHashRemove(ctx->session->userData, "xact_id");
786 jsonObject* ret = jsonNewObject(ctx->session->session_id);
787 osrfAppRespondComplete( ctx, ret );
793 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
794 if(osrfMethodVerifyContext( ctx )) {
795 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
799 osrfHash* meta = (osrfHash*) ctx->method->userData;
800 osrfHash* class_obj = osrfHashGet( meta, "class" );
804 const char* methodtype = osrfHashGet(meta, "methodtype");
805 jsonObject * obj = NULL;
807 if (!strcmp(methodtype, "create")) {
808 obj = doCreate(ctx, &err);
809 osrfAppRespondComplete( ctx, obj );
811 else if (!strcmp(methodtype, "retrieve")) {
812 obj = doRetrieve(ctx, &err);
813 osrfAppRespondComplete( ctx, obj );
815 else if (!strcmp(methodtype, "update")) {
816 obj = doUpdate(ctx, &err);
817 osrfAppRespondComplete( ctx, obj );
819 else if (!strcmp(methodtype, "delete")) {
820 obj = doDelete(ctx, &err);
821 osrfAppRespondComplete( ctx, obj );
823 else if (!strcmp(methodtype, "search")) {
825 jsonObject* where_clause;
826 jsonObject* rest_of_query;
829 where_clause = jsonObjectGetIndex( ctx->params, 1 );
830 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
832 where_clause = jsonObjectGetIndex( ctx->params, 0 );
833 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
836 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
841 unsigned long res_idx = 0;
842 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
844 if(!verifyObjectPCRUD(ctx, cur)) continue;
846 osrfAppRespond( ctx, cur );
848 osrfAppRespondComplete( ctx, NULL );
850 } else if (!strcmp(methodtype, "id_list")) {
852 jsonObject* where_clause;
853 jsonObject* rest_of_query;
855 // We use the where clause without change. But we need
856 // to massage the rest of the query, so we work with a copy
857 // of it instead of modifying the original.
859 where_clause = jsonObjectGetIndex( ctx->params, 1 );
860 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
862 where_clause = jsonObjectGetIndex( ctx->params, 0 );
863 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
866 if ( rest_of_query ) {
867 jsonObjectRemoveKey( rest_of_query, "select" );
868 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
869 jsonObjectRemoveKey( rest_of_query, "flesh" );
870 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
872 rest_of_query = jsonNewObjectType( JSON_HASH );
875 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
877 // Build a SELECT list containing just the primary key,
878 // i.e. like { "classname":["keyname"] }
879 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
880 jsonObjectPush( col_list_obj, // Load array with name of primary key
881 jsonNewObject( osrfHashGet( class_obj, "primarykey" ) ) );
882 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
883 jsonObjectSetKey( select_clause, osrfHashGet( class_obj, "classname" ), col_list_obj );
885 jsonObjectSetKey( rest_of_query, "select", select_clause );
887 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
889 jsonObjectFree( rest_of_query );
893 unsigned long res_idx = 0;
894 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
896 if(!verifyObjectPCRUD(ctx, cur)) continue;
900 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
903 osrfAppRespondComplete( ctx, NULL );
906 osrfAppRespondComplete( ctx, obj );
914 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
917 osrfHash* meta = (osrfHash*) ctx->method->userData;
918 osrfHash* class = osrfHashGet( meta, "class" );
920 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
922 const char* temp_classname = param->classname;
923 if( ! temp_classname )
924 temp_classname = "(null)";
926 growing_buffer* msg = buffer_init(128);
929 "%s: %s method for type %s was passed a %s",
931 osrfHashGet(meta, "methodtype"),
932 osrfHashGet(class, "classname"),
936 char* m = buffer_release(msg);
937 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException", ctx->request, m );
945 ret = verifyObjectPCRUD( ctx, param );
953 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
954 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
955 jsonObject* auth_object = jsonNewObject(auth);
956 jsonObject* user = oilsUtilsQuickReq("open-ils.auth","open-ils.auth.session.retrieve", auth_object);
957 jsonObjectFree(auth_object);
959 if (!user->classname || strcmp(user->classname, "au")) {
961 growing_buffer* msg = buffer_init(128);
964 "%s: permacrud received a bad auth token: %s",
969 char* m = buffer_release(msg);
970 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException", ctx->request, m );
973 jsonObjectFree(user);
981 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
983 dbhandle = writehandle;
985 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
986 osrfHash* class = osrfHashGet( method_metadata, "class" );
987 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
990 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
991 method_type = "retrieve"; // search and id_list are equivalant to retrieve for this
992 } else if ( *method_type == 'u' || *method_type == 'd' ) {
993 fetch = 1; // MUST go to the db for the object for update and delete
996 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
999 // No permacrud for this method type on this class
1001 growing_buffer* msg = buffer_init(128);
1004 "%s: %s on class %s has no permacrud IDL entry",
1006 osrfHashGet(method_metadata, "methodtype"),
1007 osrfHashGet(class, "classname")
1010 char* m = buffer_release(msg);
1011 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN, "osrfMethodException", ctx->request, m );
1018 jsonObject* user = verifyUserPCRUD( ctx );
1019 if (!user) return 0;
1021 int userid = atoi( oilsFMGetString( user, "id" ) );
1022 jsonObjectFree(user);
1024 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
1025 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
1026 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
1028 osrfStringArray* context_org_array = osrfNewStringArray(1);
1031 char* pkey_value = NULL;
1032 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1033 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions required, fetching top of the org tree" );
1035 // check for perm at top of org tree
1036 char* org_tree_root_id = org_tree_root( ctx );
1037 if( org_tree_root_id ) {
1038 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1039 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1041 osrfStringArrayFree( context_org_array );
1046 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, fetching context org ids" );
1047 const char* pkey = osrfHashGet(class, "primarykey");
1048 jsonObject *param = NULL;
1050 if (obj->classname) {
1051 pkey_value = oilsFMGetString( obj, pkey );
1052 if (!fetch) param = jsonObjectClone(obj);
1053 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s", pkey_value );
1055 pkey_value = jsonObjectToSimpleString( obj );
1057 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value of %s and retrieving from the database", pkey_value );
1061 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1062 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1063 jsonObjectFree(_tmp_params);
1065 param = jsonObjectExtractIndex(_list, 0);
1066 jsonObjectFree(_list);
1070 osrfLogDebug( OSRF_LOG_MARK, "Object not found in the database with primary key %s of %s", pkey, pkey_value );
1072 growing_buffer* msg = buffer_init(128);
1075 "%s: no object found with primary key %s of %s",
1081 char* m = buffer_release(msg);
1082 osrfAppSessionStatus(
1084 OSRF_STATUS_INTERNALSERVERERROR,
1085 "osrfMethodException",
1091 if (pkey_value) free(pkey_value);
1096 if (local_context->size > 0) {
1097 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified", local_context->size);
1099 char* lcontext = NULL;
1100 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1101 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1104 "adding class-local field %s (value: %s) to the context org list",
1106 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1112 if (foreign_context) {
1113 unsigned long class_count = osrfHashGetCount( foreign_context );
1114 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count);
1116 if (class_count > 0) {
1118 osrfHash* fcontext = NULL;
1119 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1120 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1121 const char* class_name = osrfHashIteratorKey( class_itr );
1122 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1126 "%d foreign context fields(s) specified for class %s",
1127 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1131 char* foreign_pkey = osrfHashGet(fcontext, "field");
1132 char* foreign_pkey_value = oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1134 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1136 jsonObject* _list = doFieldmapperSearch(
1137 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1139 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1140 jsonObjectFree(_tmp_params);
1141 jsonObjectFree(_list);
1143 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1145 if (_fparam && jump_list) {
1148 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1149 free(foreign_pkey_value);
1151 osrfHash* foreign_link_hash = oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1153 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1154 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1156 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1158 _list = doFieldmapperSearch(
1160 osrfHashGet( oilsIDL(), osrfHashGet( foreign_link_hash, "class" ) ),
1166 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1167 jsonObjectFree(_tmp_params);
1168 jsonObjectFree(_list);
1175 growing_buffer* msg = buffer_init(128);
1178 "%s: no object found with primary key %s of %s",
1184 char* m = buffer_release(msg);
1185 osrfAppSessionStatus(
1187 OSRF_STATUS_INTERNALSERVERERROR,
1188 "osrfMethodException",
1194 osrfHashIteratorFree(class_itr);
1195 free(foreign_pkey_value);
1196 jsonObjectFree(param);
1201 free(foreign_pkey_value);
1204 char* foreign_field = NULL;
1205 while ( (foreign_field = osrfStringArrayGetString(osrfHashGet(fcontext,"context"), j++)) ) {
1206 osrfStringArrayAdd( context_org_array, oilsFMGetString( _fparam, foreign_field ) );
1209 "adding foreign class %s field %s (value: %s) to the context org list",
1212 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1216 jsonObjectFree(_fparam);
1219 osrfHashIteratorFree( class_itr );
1223 jsonObjectFree(param);
1226 char* context_org = NULL;
1230 if (permission->size == 0) {
1231 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1236 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1238 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1244 "Checking object permission [%s] for user %d on object %s (class %s) at org %d",
1248 osrfHashGet(class, "classname"),
1252 result = dbi_conn_queryf(
1254 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1257 osrfHashGet(class, "classname"),
1265 "Received a result for object permission [%s] for user %d on object %s (class %s) at org %d",
1269 osrfHashGet(class, "classname"),
1273 if (dbi_result_first_row(result)) {
1274 jsonObject* return_val = oilsMakeJSONFromResult( result );
1275 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1279 "Status of object permission [%s] for user %d on object %s (class %s) at org %d is %s",
1283 osrfHashGet(class, "classname"),
1288 if ( *has_perm == 't' ) OK = 1;
1289 jsonObjectFree(return_val);
1292 dbi_result_free(result);
1297 osrfLogDebug( OSRF_LOG_MARK, "Checking non-object permission [%s] for user %d at org %d", perm, userid, atoi(context_org) );
1298 result = dbi_conn_queryf(
1300 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1307 osrfLogDebug( OSRF_LOG_MARK, "Received a result for permission [%s] for user %d at org %d",
1308 perm, userid, atoi(context_org) );
1309 if ( dbi_result_first_row(result) ) {
1310 jsonObject* return_val = oilsMakeJSONFromResult( result );
1311 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1312 osrfLogDebug( OSRF_LOG_MARK, "Status of permission [%s] for user %d at org %d is [%s]",
1313 perm, userid, atoi(context_org), has_perm );
1314 if ( *has_perm == 't' ) OK = 1;
1315 jsonObjectFree(return_val);
1318 dbi_result_free(result);
1326 if (pkey_value) free(pkey_value);
1327 osrfStringArrayFree(context_org_array);
1333 * Look up the root of the org_unit tree. If you find it, return
1334 * a string containing the id, which the caller is responsible for freeing.
1335 * Otherwise return NULL.
1337 static char* org_tree_root( osrfMethodContext* ctx ) {
1339 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1340 static time_t last_lookup_time = 0;
1341 time_t current_time = time( NULL );
1343 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1344 // We successfully looked this up less than an hour ago.
1345 // It's not likely to have changed since then.
1346 return strdup( cached_root_id );
1348 last_lookup_time = current_time;
1351 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1352 jsonObject* result = doFieldmapperSearch(
1353 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1354 jsonObjectFree( where_clause );
1356 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1359 jsonObjectFree( result );
1361 growing_buffer* msg = buffer_init(128);
1362 OSRF_BUFFER_ADD( msg, MODULENAME );
1363 OSRF_BUFFER_ADD( msg,
1364 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1366 char* m = buffer_release(msg);
1367 osrfAppSessionStatus( ctx->session,
1368 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1371 cached_root_id[ 0 ] = '\0';
1375 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1376 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1378 jsonObjectFree( result );
1380 strcpy( cached_root_id, root_org_unit_id );
1381 return root_org_unit_id;
1385 Utility function: create a JSON_HASH with a single key/value pair.
1386 This function is equivalent to:
1388 jsonParseStringFmt( "{\"%s\":\"%s\"}", key, value )
1390 or, if value is NULL:
1392 jsonParseStringFmt( "{\"%s\":null}", key )
1394 ...but faster because it doesn't create and parse a JSON string.
1396 static jsonObject* single_hash( const char* key, const char* value ) {
1398 if( ! key ) key = "";
1400 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1401 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1407 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1409 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1411 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1412 jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1414 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1415 jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1418 if (!verifyObjectClass(ctx, target)) {
1423 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1425 char* trans_id = NULL;
1426 if( ctx->session && ctx->session->userData )
1427 trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
1430 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1432 osrfAppSessionStatus(
1434 OSRF_STATUS_BADREQUEST,
1435 "osrfMethodException",
1437 "No active transaction -- required for CREATE"
1443 // The following test is harmless but redundant. If a class is
1444 // readonly, we don't register a create method for it.
1445 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1446 osrfAppSessionStatus(
1448 OSRF_STATUS_BADREQUEST,
1449 "osrfMethodException",
1451 "Cannot INSERT readonly class"
1457 // Set the last_xact_id
1458 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1460 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
1461 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1464 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1466 dbhandle = writehandle;
1468 osrfHash* fields = osrfHashGet(meta, "fields");
1469 char* pkey = osrfHashGet(meta, "primarykey");
1470 char* seq = osrfHashGet(meta, "sequence");
1472 growing_buffer* table_buf = buffer_init(128);
1473 growing_buffer* col_buf = buffer_init(128);
1474 growing_buffer* val_buf = buffer_init(128);
1476 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1477 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1478 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1479 buffer_add(val_buf,"VALUES (");
1483 osrfHash* field = NULL;
1484 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1485 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1487 const char* field_name = osrfHashIteratorKey( field_itr );
1489 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1492 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1495 if (field_object && field_object->classname) {
1496 value = oilsFMGetString(
1498 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1501 value = jsonObjectToSimpleString( field_object );
1507 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1508 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1511 buffer_add(col_buf, field_name);
1513 if (!field_object || field_object->type == JSON_NULL) {
1514 buffer_add( val_buf, "DEFAULT" );
1516 } else if ( !strcmp(get_primitive( field ), "number") ) {
1517 const char* numtype = get_datatype( field );
1518 if ( !strcmp( numtype, "INT8") ) {
1519 buffer_fadd( val_buf, "%lld", atoll(value) );
1521 } else if ( !strcmp( numtype, "INT") ) {
1522 buffer_fadd( val_buf, "%d", atoi(value) );
1524 } else if ( !strcmp( numtype, "NUMERIC") ) {
1525 buffer_fadd( val_buf, "%f", atof(value) );
1528 if ( dbi_conn_quote_string(writehandle, &value) ) {
1529 OSRF_BUFFER_ADD( val_buf, value );
1532 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1533 osrfAppSessionStatus(
1535 OSRF_STATUS_INTERNALSERVERERROR,
1536 "osrfMethodException",
1538 "Error quoting string -- please see the error log for more details"
1541 buffer_free(table_buf);
1542 buffer_free(col_buf);
1543 buffer_free(val_buf);
1553 osrfHashIteratorFree( field_itr );
1555 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1556 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1558 char* table_str = buffer_release(table_buf);
1559 char* col_str = buffer_release(col_buf);
1560 char* val_str = buffer_release(val_buf);
1561 growing_buffer* sql = buffer_init(128);
1562 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1567 char* query = buffer_release(sql);
1569 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1572 dbi_result result = dbi_conn_query(writehandle, query);
1574 jsonObject* obj = NULL;
1577 obj = jsonNewObject(NULL);
1580 "%s ERROR inserting %s object using query [%s]",
1582 osrfHashGet(meta, "fieldmapper"),
1585 osrfAppSessionStatus(
1587 OSRF_STATUS_INTERNALSERVERERROR,
1588 "osrfMethodException",
1590 "INSERT error -- please see the error log for more details"
1595 char* id = oilsFMGetString(target, pkey);
1597 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1598 growing_buffer* _id = buffer_init(10);
1599 buffer_fadd(_id, "%lld", new_id);
1600 id = buffer_release(_id);
1603 // Find quietness specification, if present
1604 const char* quiet_str = NULL;
1606 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1608 quiet_str = jsonObjectGetString( quiet_obj );
1611 if( str_is_true( quiet_str ) ) { // if quietness is specified
1612 obj = jsonNewObject(id);
1616 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1617 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1619 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1621 jsonObjectFree( where_clause );
1626 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1629 jsonObjectFree( list );
1642 * Fetch one row from a specified table, using a specified value
1643 * for the primary key
1645 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1655 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1657 const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos); // key value
1661 "%s retrieving %s object with primary key value of %s",
1663 osrfHashGet( class_def, "fieldmapper" ),
1664 jsonObjectGetString( id_obj )
1667 // Build a WHERE clause based on the key value
1668 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1671 osrfHashGet( class_def, "primarykey" ),
1672 jsonObjectClone( id_obj )
1675 jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
1677 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
1679 jsonObjectFree( where_clause );
1683 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
1684 jsonObjectFree( list );
1687 if(!verifyObjectPCRUD(ctx, obj)) {
1688 jsonObjectFree(obj);
1691 growing_buffer* msg = buffer_init(128);
1692 OSRF_BUFFER_ADD( msg, MODULENAME );
1693 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1695 char* m = buffer_release(msg);
1696 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1707 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1708 growing_buffer* val_buf = buffer_init(32);
1709 const char* numtype = get_datatype( field );
1711 if ( !strncmp( numtype, "INT", 3 ) ) {
1712 if (value->type == JSON_NUMBER)
1713 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1714 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1716 //const char* val_str = jsonObjectGetString( value );
1717 //buffer_fadd( val_buf, "%ld", atol(val_str) );
1718 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1721 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
1722 if (value->type == JSON_NUMBER)
1723 //buffer_fadd( val_buf, "%f", jsonObjectGetNumber(value) );
1724 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1726 //const char* val_str = jsonObjectGetString( value );
1727 //buffer_fadd( val_buf, "%f", atof(val_str) );
1728 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1732 // Presumably this was really intended ot be a string, so quote it
1733 char* str = jsonObjectToSimpleString( value );
1734 if ( dbi_conn_quote_string(dbhandle, &str) ) {
1735 OSRF_BUFFER_ADD( val_buf, str );
1738 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
1740 buffer_free(val_buf);
1745 return buffer_release(val_buf);
1748 static char* searchINPredicate (const char* class_alias, osrfHash* field,
1749 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1750 growing_buffer* sql_buf = buffer_init(32);
1756 osrfHashGet(field, "name")
1760 buffer_add(sql_buf, "IN (");
1761 } else if (!(strcasecmp(op,"not in"))) {
1762 buffer_add(sql_buf, "NOT IN (");
1764 buffer_add(sql_buf, "IN (");
1767 if (node->type == JSON_HASH) {
1768 // subquery predicate
1769 char* subpred = SELECT(
1771 jsonObjectGetKey( node, "select" ),
1772 jsonObjectGetKey( node, "from" ),
1773 jsonObjectGetKey( node, "where" ),
1774 jsonObjectGetKey( node, "having" ),
1775 jsonObjectGetKey( node, "order_by" ),
1776 jsonObjectGetKey( node, "limit" ),
1777 jsonObjectGetKey( node, "offset" ),
1783 buffer_add(sql_buf, subpred);
1786 buffer_free( sql_buf );
1790 } else if (node->type == JSON_ARRAY) {
1791 // literal value list
1792 int in_item_index = 0;
1793 int in_item_first = 1;
1794 const jsonObject* in_item;
1795 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1800 buffer_add(sql_buf, ", ");
1803 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1804 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1805 MODULENAME, json_type( in_item->type ) );
1806 buffer_free(sql_buf);
1810 // Append the literal value -- quoted if not a number
1811 if ( JSON_NUMBER == in_item->type ) {
1812 char* val = jsonNumberToDBString( field, in_item );
1813 OSRF_BUFFER_ADD( sql_buf, val );
1816 } else if ( !strcmp( get_primitive( field ), "number") ) {
1817 char* val = jsonNumberToDBString( field, in_item );
1818 OSRF_BUFFER_ADD( sql_buf, val );
1822 char* key_string = jsonObjectToSimpleString(in_item);
1823 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1824 OSRF_BUFFER_ADD( sql_buf, key_string );
1827 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1829 buffer_free(sql_buf);
1835 if( in_item_first ) {
1836 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1837 buffer_free( sql_buf );
1841 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1842 MODULENAME, json_type( node->type ) );
1843 buffer_free(sql_buf);
1847 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1849 return buffer_release(sql_buf);
1852 // Receive a JSON_ARRAY representing a function call. The first
1853 // entry in the array is the function name. The rest are parameters.
1854 static char* searchValueTransform( const jsonObject* array ) {
1856 if( array->size < 1 ) {
1857 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
1861 // Get the function name
1862 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
1863 if( func_item->type != JSON_STRING ) {
1864 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
1865 MODULENAME, json_type( func_item->type ) );
1869 growing_buffer* sql_buf = buffer_init(32);
1871 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1872 OSRF_BUFFER_ADD( sql_buf, "( " );
1874 // Get the parameters
1875 int func_item_index = 1; // We already grabbed the zeroth entry
1876 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1878 // Add a separator comma, if we need one
1879 if( func_item_index > 2 )
1880 buffer_add( sql_buf, ", " );
1882 // Add the current parameter
1883 if (func_item->type == JSON_NULL) {
1884 buffer_add( sql_buf, "NULL" );
1886 char* val = jsonObjectToSimpleString(func_item);
1887 if ( dbi_conn_quote_string(dbhandle, &val) ) {
1888 OSRF_BUFFER_ADD( sql_buf, val );
1891 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1892 buffer_free(sql_buf);
1899 buffer_add( sql_buf, " )" );
1901 return buffer_release(sql_buf);
1904 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
1905 const jsonObject* node, const char* op) {
1907 if( ! is_good_operator( op ) ) {
1908 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
1912 char* val = searchValueTransform(node);
1916 growing_buffer* sql_buf = buffer_init(32);
1921 osrfHashGet(field, "name"),
1928 return buffer_release(sql_buf);
1931 // class_alias is a class name or other table alias
1932 // field is a field definition as stored in the IDL
1933 // node comes from the method parameter, and may represent an entry in the SELECT list
1934 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
1935 growing_buffer* sql_buf = buffer_init(32);
1937 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
1938 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
1940 if(transform_subcolumn) {
1941 if( ! is_identifier( transform_subcolumn ) ) {
1942 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
1943 MODULENAME, transform_subcolumn );
1944 buffer_free( sql_buf );
1947 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
1950 if (field_transform) {
1952 if( ! is_identifier( field_transform ) ) {
1953 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
1954 MODULENAME, field_transform );
1955 buffer_free( sql_buf );
1959 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class_alias, osrfHashGet(field, "name"));
1960 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
1963 if( array->type != JSON_ARRAY ) {
1964 osrfLogError( OSRF_LOG_MARK,
1965 "%s: Expected JSON_ARRAY for function params; found %s",
1966 MODULENAME, json_type( array->type ) );
1967 buffer_free( sql_buf );
1970 int func_item_index = 0;
1971 jsonObject* func_item;
1972 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1974 char* val = jsonObjectToSimpleString(func_item);
1977 buffer_add( sql_buf, ",NULL" );
1978 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
1979 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
1980 OSRF_BUFFER_ADD( sql_buf, val );
1982 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1984 buffer_free(sql_buf);
1991 buffer_add( sql_buf, " )" );
1994 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
1997 if (transform_subcolumn)
1998 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2000 return buffer_release(sql_buf);
2003 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2004 const jsonObject* node, const char* op ) {
2006 if( ! is_good_operator( op ) ) {
2007 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2011 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2012 if( ! field_transform )
2015 int extra_parens = 0; // boolean
2017 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2018 if ( ! value_obj ) {
2019 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2021 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
2022 free(field_transform);
2026 } else if ( value_obj->type == JSON_ARRAY ) {
2027 value = searchValueTransform( value_obj );
2029 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
2030 free( field_transform );
2033 } else if ( value_obj->type == JSON_HASH ) {
2034 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2036 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
2037 free(field_transform);
2041 } else if ( value_obj->type == JSON_NUMBER ) {
2042 value = jsonNumberToDBString( field, value_obj );
2043 } else if ( value_obj->type == JSON_NULL ) {
2044 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
2045 free(field_transform);
2047 } else if ( value_obj->type == JSON_BOOL ) {
2048 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
2049 free(field_transform);
2052 if ( !strcmp( get_primitive( field ), "number") ) {
2053 value = jsonNumberToDBString( field, value_obj );
2055 value = jsonObjectToSimpleString( value_obj );
2056 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2057 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
2059 free(field_transform);
2065 const char* left_parens = "";
2066 const char* right_parens = "";
2068 if( extra_parens ) {
2073 growing_buffer* sql_buf = buffer_init(32);
2077 "%s%s %s %s %s %s%s",
2088 free(field_transform);
2090 return buffer_release(sql_buf);
2093 static char* searchSimplePredicate (const char* op, const char* class_alias,
2094 osrfHash* field, const jsonObject* node) {
2096 if( ! is_good_operator( op ) ) {
2097 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2103 // Get the value to which we are comparing the specified column
2104 if (node->type != JSON_NULL) {
2105 if ( node->type == JSON_NUMBER ) {
2106 val = jsonNumberToDBString( field, node );
2107 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2108 val = jsonNumberToDBString( field, node );
2110 val = jsonObjectToSimpleString(node);
2115 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2116 // Value is not numeric; enclose it in quotes
2117 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2118 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2124 // Compare to a null value
2125 val = strdup( "NULL" );
2126 if (strcmp( op, "=" ))
2132 growing_buffer* sql_buf = buffer_init(32);
2133 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2134 char* pred = buffer_release( sql_buf );
2141 static char* searchBETWEENPredicate (const char* class_alias,
2142 osrfHash* field, const jsonObject* node) {
2144 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2145 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2147 if( NULL == y_node ) {
2148 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2151 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2152 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2159 if ( !strcmp( get_primitive( field ), "number") ) {
2160 x_string = jsonNumberToDBString(field, x_node);
2161 y_string = jsonNumberToDBString(field, y_node);
2164 x_string = jsonObjectToSimpleString(x_node);
2165 y_string = jsonObjectToSimpleString(y_node);
2166 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2167 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2168 MODULENAME, x_string, y_string);
2175 growing_buffer* sql_buf = buffer_init(32);
2176 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2177 class_alias, osrfHashGet(field, "name"), x_string, y_string );
2181 return buffer_release(sql_buf);
2184 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2185 jsonObject* node, osrfMethodContext* ctx ) {
2188 if (node->type == JSON_ARRAY) { // equality IN search
2189 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2190 } else if (node->type == JSON_HASH) { // other search
2191 jsonIterator* pred_itr = jsonNewIterator( node );
2192 if( !jsonIteratorHasNext( pred_itr ) ) {
2193 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2194 MODULENAME, osrfHashGet(field, "name") );
2196 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2198 // Verify that there are no additional predicates
2199 if( jsonIteratorHasNext( pred_itr ) ) {
2200 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2201 MODULENAME, osrfHashGet(field, "name") );
2202 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2203 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2204 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2205 pred = searchINPredicate( class_info->alias, field, pred_node, pred_itr->key, ctx );
2206 else if ( pred_node->type == JSON_ARRAY )
2207 pred = searchFunctionPredicate( class_info->alias, field, pred_node, pred_itr->key );
2208 else if ( pred_node->type == JSON_HASH )
2209 pred = searchFieldTransformPredicate( class_info, field, pred_node, pred_itr->key );
2211 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2213 jsonIteratorFree(pred_itr);
2215 } else if (node->type == JSON_NULL) { // IS NULL search
2216 growing_buffer* _p = buffer_init(64);
2219 "\"%s\".%s IS NULL",
2220 class_info->class_name,
2221 osrfHashGet(field, "name")
2223 pred = buffer_release(_p);
2224 } else { // equality search
2225 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2244 field : call_number,
2260 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2262 const jsonObject* working_hash;
2263 jsonObject* freeable_hash = NULL;
2265 if (join_hash->type == JSON_HASH) {
2266 working_hash = join_hash;
2267 } else if (join_hash->type == JSON_STRING) {
2268 // turn it into a JSON_HASH by creating a wrapper
2269 // around a copy of the original
2270 const char* _tmp = jsonObjectGetString( join_hash );
2271 freeable_hash = jsonNewObjectType(JSON_HASH);
2272 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2273 working_hash = freeable_hash;
2277 "%s: JOIN failed; expected JSON object type not found",
2283 growing_buffer* join_buf = buffer_init(128);
2284 const char* leftclass = left_info->class_name;
2286 jsonObject* snode = NULL;
2287 jsonIterator* search_itr = jsonNewIterator( working_hash );
2289 while ( (snode = jsonIteratorNext( search_itr )) ) {
2290 const char* right_alias = search_itr->key;
2292 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2294 class = right_alias;
2296 const ClassInfo* right_info = add_joined_class( right_alias, class );
2300 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2304 jsonIteratorFree( search_itr );
2305 buffer_free( join_buf );
2307 jsonObjectFree( freeable_hash );
2310 osrfHash* links = right_info->links;
2311 const char* table = right_info->source_def;
2313 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2314 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2316 if (field && !fkey) {
2317 // Look up the corresponding join column in the IDL.
2318 // The link must be defined in the child table,
2319 // and point to the right parent table.
2320 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2321 const char* reltype = NULL;
2322 const char* other_class = NULL;
2323 reltype = osrfHashGet( idl_link, "reltype" );
2324 if( reltype && strcmp( reltype, "has_many" ) )
2325 other_class = osrfHashGet( idl_link, "class" );
2326 if( other_class && !strcmp( other_class, leftclass ) )
2327 fkey = osrfHashGet( idl_link, "key" );
2331 "%s: JOIN failed. No link defined from %s.%s to %s",
2337 buffer_free(join_buf);
2339 jsonObjectFree(freeable_hash);
2340 jsonIteratorFree(search_itr);
2344 } else if (!field && fkey) {
2345 // Look up the corresponding join column in the IDL.
2346 // The link must be defined in the child table,
2347 // and point to the right parent table.
2348 osrfHash* left_links = left_info->links;
2349 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2350 const char* reltype = NULL;
2351 const char* other_class = NULL;
2352 reltype = osrfHashGet( idl_link, "reltype" );
2353 if( reltype && strcmp( reltype, "has_many" ) )
2354 other_class = osrfHashGet( idl_link, "class" );
2355 if( other_class && !strcmp( other_class, class ) )
2356 field = osrfHashGet( idl_link, "key" );
2360 "%s: JOIN failed. No link defined from %s.%s to %s",
2366 buffer_free(join_buf);
2368 jsonObjectFree(freeable_hash);
2369 jsonIteratorFree(search_itr);
2373 } else if (!field && !fkey) {
2374 osrfHash* left_links = left_info->links;
2376 // For each link defined for the left class:
2377 // see if the link references the joined class
2378 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2379 osrfHash* curr_link = NULL;
2380 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2381 const char* other_class = osrfHashGet( curr_link, "class" );
2382 if( other_class && !strcmp( other_class, class ) ) {
2384 // In the IDL, the parent class doesn't know then names of the child
2385 // columns that are pointing to it, so don't use that end of the link
2386 const char* reltype = osrfHashGet( curr_link, "reltype" );
2387 if( reltype && strcmp( reltype, "has_many" ) ) {
2388 // Found a link between the classes
2389 fkey = osrfHashIteratorKey( itr );
2390 field = osrfHashGet( curr_link, "key" );
2395 osrfHashIteratorFree( itr );
2397 if (!field || !fkey) {
2398 // Do another such search, with the classes reversed
2400 // For each link defined for the joined class:
2401 // see if the link references the left class
2402 osrfHashIterator* itr = osrfNewHashIterator( links );
2403 osrfHash* curr_link = NULL;
2404 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2405 const char* other_class = osrfHashGet( curr_link, "class" );
2406 if( other_class && !strcmp( other_class, leftclass ) ) {
2408 // In the IDL, the parent class doesn't know then names of the child
2409 // columns that are pointing to it, so don't use that end of the link
2410 const char* reltype = osrfHashGet( curr_link, "reltype" );
2411 if( reltype && strcmp( reltype, "has_many" ) ) {
2412 // Found a link between the classes
2413 field = osrfHashIteratorKey( itr );
2414 fkey = osrfHashGet( curr_link, "key" );
2419 osrfHashIteratorFree( itr );
2422 if (!field || !fkey) {
2425 "%s: JOIN failed. No link defined between %s and %s",
2430 buffer_free(join_buf);
2432 jsonObjectFree(freeable_hash);
2433 jsonIteratorFree(search_itr);
2439 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2441 if ( !strcasecmp(type,"left") ) {
2442 buffer_add(join_buf, " LEFT JOIN");
2443 } else if ( !strcasecmp(type,"right") ) {
2444 buffer_add(join_buf, " RIGHT JOIN");
2445 } else if ( !strcasecmp(type,"full") ) {
2446 buffer_add(join_buf, " FULL JOIN");
2448 buffer_add(join_buf, " INNER JOIN");
2451 buffer_add(join_buf, " INNER JOIN");
2454 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2455 table, right_alias, right_alias, field, left_info->alias, fkey);
2457 // Add any other join conditions as specified by "filter"
2458 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2460 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2461 if ( filter_op && !strcasecmp("or",filter_op) ) {
2462 buffer_add( join_buf, " OR " );
2464 buffer_add( join_buf, " AND " );
2467 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2469 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2470 OSRF_BUFFER_ADD( join_buf, jpred );
2475 "%s: JOIN failed. Invalid conditional expression.",
2478 jsonIteratorFree( search_itr );
2479 buffer_free( join_buf );
2481 jsonObjectFree( freeable_hash );
2486 buffer_add(join_buf, " ) ");
2488 // Recursively add a nested join, if one is present
2489 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2491 char* jpred = searchJOIN( join_filter, right_info );
2493 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2494 OSRF_BUFFER_ADD( join_buf, jpred );
2497 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2498 jsonIteratorFree( search_itr );
2499 buffer_free( join_buf );
2501 jsonObjectFree( freeable_hash );
2508 jsonObjectFree(freeable_hash);
2509 jsonIteratorFree(search_itr);
2511 return buffer_release(join_buf);
2516 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2517 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2518 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2520 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2522 search_hash is the JSON expression of the conditions.
2523 meta is the class definition from the IDL, for the relevant table.
2524 opjoin_type indicates whether multiple conditions, if present, should be
2525 connected by AND or OR.
2526 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2527 to pass it to other functions -- and all they do with it is to use the session
2528 and request members to send error messages back to the client.
2532 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
2533 int opjoin_type, osrfMethodContext* ctx ) {
2537 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2540 class_info->class_def,
2545 growing_buffer* sql_buf = buffer_init(128);
2547 jsonObject* node = NULL;
2550 if ( search_hash->type == JSON_ARRAY ) {
2551 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2552 if( 0 == search_hash->size ) {
2555 "%s: Invalid predicate structure: empty JSON array",
2558 buffer_free( sql_buf );
2562 unsigned long i = 0;
2563 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
2567 if (opjoin_type == OR_OP_JOIN)
2568 buffer_add(sql_buf, " OR ");
2570 buffer_add(sql_buf, " AND ");
2573 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2575 buffer_free( sql_buf );
2579 buffer_fadd(sql_buf, "( %s )", subpred);
2583 } else if ( search_hash->type == JSON_HASH ) {
2584 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2585 jsonIterator* search_itr = jsonNewIterator( search_hash );
2586 if( !jsonIteratorHasNext( search_itr ) ) {
2589 "%s: Invalid predicate structure: empty JSON object",
2592 jsonIteratorFree( search_itr );
2593 buffer_free( sql_buf );
2597 while ( (node = jsonIteratorNext( search_itr )) ) {
2602 if (opjoin_type == OR_OP_JOIN)
2603 buffer_add(sql_buf, " OR ");
2605 buffer_add(sql_buf, " AND ");
2608 if ( '+' == search_itr->key[ 0 ] ) {
2610 // This plus sign prefixes a class name or other table alias;
2611 // make sure the table alias is in scope
2612 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
2613 if( ! alias_info ) {
2616 "%s: Invalid table alias \"%s\" in WHERE clause",
2620 jsonIteratorFree( search_itr );
2621 buffer_free( sql_buf );
2625 if ( node->type == JSON_STRING ) {
2626 // It's the name of a column; make sure it belongs to the class
2627 const char* fieldname = jsonObjectGetString( node );
2628 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
2631 "%s: Invalid column name \"%s\" in WHERE clause for table alias \"%s\"",
2636 jsonIteratorFree( search_itr );
2637 buffer_free( sql_buf );
2641 buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
2643 // It's something more complicated
2644 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
2646 jsonIteratorFree( search_itr );
2647 buffer_free( sql_buf );
2651 buffer_fadd(sql_buf, "( %s )", subpred);
2654 } else if ( '-' == search_itr->key[ 0 ] ) {
2655 if ( !strcasecmp("-or",search_itr->key) ) {
2656 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
2658 jsonIteratorFree( search_itr );
2659 buffer_free( sql_buf );
2663 buffer_fadd(sql_buf, "( %s )", subpred);
2665 } else if ( !strcasecmp("-and",search_itr->key) ) {
2666 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2668 jsonIteratorFree( search_itr );
2669 buffer_free( sql_buf );
2673 buffer_fadd(sql_buf, "( %s )", subpred);
2675 } else if ( !strcasecmp("-not",search_itr->key) ) {
2676 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2678 jsonIteratorFree( search_itr );
2679 buffer_free( sql_buf );
2683 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2685 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2686 char* subpred = SELECT(
2688 jsonObjectGetKey( node, "select" ),
2689 jsonObjectGetKey( node, "from" ),
2690 jsonObjectGetKey( node, "where" ),
2691 jsonObjectGetKey( node, "having" ),
2692 jsonObjectGetKey( node, "order_by" ),
2693 jsonObjectGetKey( node, "limit" ),
2694 jsonObjectGetKey( node, "offset" ),
2700 jsonIteratorFree( search_itr );
2701 buffer_free( sql_buf );
2705 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2707 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2708 char* subpred = SELECT(
2710 jsonObjectGetKey( node, "select" ),
2711 jsonObjectGetKey( node, "from" ),
2712 jsonObjectGetKey( node, "where" ),
2713 jsonObjectGetKey( node, "having" ),
2714 jsonObjectGetKey( node, "order_by" ),
2715 jsonObjectGetKey( node, "limit" ),
2716 jsonObjectGetKey( node, "offset" ),
2722 jsonIteratorFree( search_itr );
2723 buffer_free( sql_buf );
2727 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2729 } else { // Invalid "minus" operator
2732 "%s: Invalid operator \"%s\" in WHERE clause",
2736 jsonIteratorFree( search_itr );
2737 buffer_free( sql_buf );
2743 const char* class = class_info->class_name;
2744 osrfHash* fields = class_info->fields;
2745 osrfHash* field = osrfHashGet( fields, search_itr->key );
2748 const char* table = class_info->source_def;
2751 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2754 table ? table : "?",
2757 jsonIteratorFree(search_itr);
2758 buffer_free(sql_buf);
2762 char* subpred = searchPredicate( class_info, field, node, ctx );
2764 buffer_free(sql_buf);
2765 jsonIteratorFree(search_itr);
2769 buffer_add( sql_buf, subpred );
2773 jsonIteratorFree(search_itr);
2776 // ERROR ... only hash and array allowed at this level
2777 char* predicate_string = jsonObjectToJSON( search_hash );
2780 "%s: Invalid predicate structure: %s",
2784 buffer_free(sql_buf);
2785 free(predicate_string);
2789 return buffer_release(sql_buf);
2792 /* Build a JSON_ARRAY of field names for a given table alias
2794 static jsonObject* defaultSelectList( const char* table_alias ) {
2799 ClassInfo* class_info = search_all_alias( table_alias );
2800 if( ! class_info ) {
2803 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
2810 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
2811 osrfHash* field_def = NULL;
2812 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
2813 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2814 const char* field_name = osrfHashIteratorKey( field_itr );
2815 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2816 jsonObjectPush( array, jsonNewObject( field_name ) );
2819 osrfHashIteratorFree( field_itr );
2825 /* method context */ osrfMethodContext* ctx,
2827 /* SELECT */ jsonObject* selhash,
2828 /* FROM */ jsonObject* join_hash,
2829 /* WHERE */ jsonObject* search_hash,
2830 /* HAVING */ jsonObject* having_hash,
2831 /* ORDER BY */ jsonObject* order_hash,
2832 /* LIMIT */ jsonObject* limit,
2833 /* OFFSET */ jsonObject* offset,
2834 /* flags */ int flags
2836 const char* locale = osrf_message_get_last_locale();
2838 // general tmp objects
2839 const jsonObject* tmp_const;
2840 jsonObject* selclass = NULL;
2841 jsonObject* snode = NULL;
2842 jsonObject* onode = NULL;
2844 char* string = NULL;
2845 int from_function = 0;
2850 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale);
2852 // punt if there's no FROM clause
2853 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2856 "%s: FROM clause is missing or empty",
2860 osrfAppSessionStatus(
2862 OSRF_STATUS_INTERNALSERVERERROR,
2863 "osrfMethodException",
2865 "FROM clause is missing or empty in JSON query"
2870 // Push a node onto the stack for the current query. Every level of
2871 // subquery gets its own QueryFrame on the Stack.
2874 // the core search class
2875 const char* core_class = NULL;
2877 // get the core class -- the only key of the top level FROM clause, or a string
2878 if (join_hash->type == JSON_HASH) {
2879 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2880 snode = jsonIteratorNext( tmp_itr );
2882 // Populate the current QueryFrame with information
2883 // about the core class
2884 if( add_query_core( NULL, tmp_itr->key ) ) {
2886 osrfAppSessionStatus(
2888 OSRF_STATUS_INTERNALSERVERERROR,
2889 "osrfMethodException",
2891 "Unable to look up core class"
2895 core_class = curr_query->core.class_name;
2898 jsonObject* extra = jsonIteratorNext( tmp_itr );
2900 jsonIteratorFree( tmp_itr );
2903 // There shouldn't be more than one entry in join_hash
2907 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2911 osrfAppSessionStatus(
2913 OSRF_STATUS_INTERNALSERVERERROR,
2914 "osrfMethodException",
2916 "Malformed FROM clause in JSON query"
2918 return NULL; // Malformed join_hash; extra entry
2920 } else if (join_hash->type == JSON_ARRAY) {
2921 // We're selecting from a function, not from a table
2923 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
2926 } else if (join_hash->type == JSON_STRING) {
2927 // Populate the current QueryFrame with information
2928 // about the core class
2929 core_class = jsonObjectGetString( join_hash );
2931 if( add_query_core( NULL, core_class ) ) {
2933 osrfAppSessionStatus(
2935 OSRF_STATUS_INTERNALSERVERERROR,
2936 "osrfMethodException",
2938 "Unable to look up core class"
2946 "%s: FROM clause is unexpected JSON type: %s",
2948 json_type( join_hash->type )
2951 osrfAppSessionStatus(
2953 OSRF_STATUS_INTERNALSERVERERROR,
2954 "osrfMethodException",
2956 "Ill-formed FROM clause in JSON query"
2961 // Build the join clause, if any, while filling out the list
2962 // of joined classes in the current QueryFrame.
2963 char* join_clause = NULL;
2964 if( join_hash && ! from_function ) {
2966 join_clause = searchJOIN( join_hash, &curr_query->core );
2967 if( ! join_clause ) {
2969 osrfAppSessionStatus(
2971 OSRF_STATUS_INTERNALSERVERERROR,
2972 "osrfMethodException",
2974 "Unable to construct JOIN clause(s)"
2980 // For in case we don't get a select list
2981 jsonObject* defaultselhash = NULL;
2983 // if there is no select list, build a default select list ...
2984 if (!selhash && !from_function) {
2985 jsonObject* default_list = defaultSelectList( core_class );
2986 if( ! default_list ) {
2988 osrfAppSessionStatus(
2990 OSRF_STATUS_INTERNALSERVERERROR,
2991 "osrfMethodException",
2993 "Unable to build default SELECT clause in JSON query"
2995 free( join_clause );
3000 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
3001 jsonObjectSetKey( selhash, core_class, default_list );
3004 // The SELECT clause can be encoded only by a hash
3005 if( !from_function && selhash->type != JSON_HASH ) {
3008 "%s: Expected JSON_HASH for SELECT clause; found %s",
3010 json_type( selhash->type )
3014 osrfAppSessionStatus(
3016 OSRF_STATUS_INTERNALSERVERERROR,
3017 "osrfMethodException",
3019 "Malformed SELECT clause in JSON query"
3021 free( join_clause );
3025 // If you see a null or wild card specifier for the core class, or an
3026 // empty array, replace it with a default SELECT list
3027 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3029 int default_needed = 0; // boolean
3030 if( JSON_STRING == tmp_const->type
3031 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3033 else if( JSON_NULL == tmp_const->type )
3036 if( default_needed ) {
3037 // Build a default SELECT list
3038 jsonObject* default_list = defaultSelectList( core_class );
3039 if( ! default_list ) {
3041 osrfAppSessionStatus(
3043 OSRF_STATUS_INTERNALSERVERERROR,
3044 "osrfMethodException",
3046 "Can't build default SELECT clause in JSON query"
3048 free( join_clause );
3053 jsonObjectSetKey( selhash, core_class, default_list );
3057 // temp buffers for the SELECT list and GROUP BY clause
3058 growing_buffer* select_buf = buffer_init(128);
3059 growing_buffer* group_buf = buffer_init(128);
3061 int aggregate_found = 0; // boolean
3063 // Build a select list
3064 if(from_function) // From a function we select everything
3065 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3068 // Build the SELECT list as SQL
3072 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3073 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3075 const char* cname = selclass_itr->key;
3077 // Make sure the target relation is in the FROM clause.
3079 // At this point join_hash is a step down from the join_hash we
3080 // received as a parameter. If the original was a JSON_STRING,
3081 // then json_hash is now NULL. If the original was a JSON_HASH,
3082 // then json_hash is now the first (and only) entry in it,
3083 // denoting the core class. We've already excluded the
3084 // possibility that the original was a JSON_ARRAY, because in
3085 // that case from_function would be non-NULL, and we wouldn't
3088 // If the current table alias isn't in scope, bail out
3089 ClassInfo* class_info = search_alias( cname );
3090 if( ! class_info ) {
3093 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3098 osrfAppSessionStatus(
3100 OSRF_STATUS_INTERNALSERVERERROR,
3101 "osrfMethodException",
3103 "Selected class not in FROM clause in JSON query"
3105 jsonIteratorFree( selclass_itr );
3106 buffer_free( select_buf );
3107 buffer_free( group_buf );
3108 if( defaultselhash ) jsonObjectFree( defaultselhash );
3109 free( join_clause );
3113 if( selclass->type != JSON_ARRAY ) {
3116 "%s: Malformed SELECT list for class \"%s\"; not an array",
3121 osrfAppSessionStatus(
3123 OSRF_STATUS_INTERNALSERVERERROR,
3124 "osrfMethodException",
3126 "Selected class not in FROM clause in JSON query"
3129 jsonIteratorFree( selclass_itr );
3130 buffer_free( select_buf );
3131 buffer_free( group_buf );
3132 if( defaultselhash ) jsonObjectFree( defaultselhash );
3133 free( join_clause );
3137 // Look up some attributes of the current class
3138 osrfHash* idlClass = class_info->class_def;
3139 osrfHash* class_field_set = class_info->fields;
3140 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3141 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3143 if( 0 == selclass->size ) {
3146 "%s: No columns selected from \"%s\"",
3152 // stitch together the column list for the current table alias...
3153 unsigned long field_idx = 0;
3154 jsonObject* selfield = NULL;
3155 while((selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3157 // If we need a separator comma, add one
3161 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3164 // if the field specification is a string, add it to the list
3165 if (selfield->type == JSON_STRING) {
3167 // Look up the field in the IDL
3168 const char* col_name = jsonObjectGetString( selfield );
3169 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3171 // No such field in current class
3174 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3180 osrfAppSessionStatus(
3182 OSRF_STATUS_INTERNALSERVERERROR,
3183 "osrfMethodException",
3185 "Selected column not defined in JSON query"
3187 jsonIteratorFree( selclass_itr );
3188 buffer_free( select_buf );
3189 buffer_free( group_buf );
3190 if( defaultselhash ) jsonObjectFree( defaultselhash );
3191 free( join_clause );
3193 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3194 // Virtual field not allowed
3197 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3203 osrfAppSessionStatus(
3205 OSRF_STATUS_INTERNALSERVERERROR,
3206 "osrfMethodException",
3208 "Selected column may not be virtual in JSON query"
3210 jsonIteratorFree( selclass_itr );
3211 buffer_free( select_buf );
3212 buffer_free( group_buf );
3213 if( defaultselhash ) jsonObjectFree( defaultselhash );
3214 free( join_clause );
3220 if (flags & DISABLE_I18N)
3223 i18n = osrfHashGet(field_def, "i18n");
3225 if( str_is_true( i18n ) ) {
3226 buffer_fadd( select_buf,
3227 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3228 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3230 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3233 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3236 // ... but it could be an object, in which case we check for a Field Transform
3237 } else if (selfield->type == JSON_HASH) {
3239 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3241 // Get the field definition from the IDL
3242 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3244 // No such field in current class
3247 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3253 osrfAppSessionStatus(
3255 OSRF_STATUS_INTERNALSERVERERROR,
3256 "osrfMethodException",
3258 "Selected column is not defined in JSON query"
3260 jsonIteratorFree( selclass_itr );
3261 buffer_free( select_buf );
3262 buffer_free( group_buf );
3263 if( defaultselhash ) jsonObjectFree( defaultselhash );
3264 free( join_clause );
3266 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3267 // No such field in current class
3270 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3276 osrfAppSessionStatus(
3278 OSRF_STATUS_INTERNALSERVERERROR,
3279 "osrfMethodException",
3281 "Selected column is virtual in JSON query"
3283 jsonIteratorFree( selclass_itr );
3284 buffer_free( select_buf );
3285 buffer_free( group_buf );
3286 if( defaultselhash ) jsonObjectFree( defaultselhash );
3287 free( join_clause );
3291 // Decide what to use as a column alias
3293 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3294 _alias = jsonObjectGetString( tmp_const );
3295 } else { // Use field name as the alias
3299 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3300 char* transform_str = searchFieldTransform(class_info->alias, field_def, selfield);
3301 if( transform_str ) {
3302 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3303 free(transform_str);
3306 osrfAppSessionStatus(
3308 OSRF_STATUS_INTERNALSERVERERROR,
3309 "osrfMethodException",
3311 "Unable to generate transform function in JSON query"
3313 jsonIteratorFree( selclass_itr );
3314 buffer_free( select_buf );
3315 buffer_free( group_buf );
3316 if( defaultselhash ) jsonObjectFree( defaultselhash );
3317 free( join_clause );
3324 if (flags & DISABLE_I18N)
3327 i18n = osrfHashGet(field_def, "i18n");
3329 if( str_is_true( i18n ) ) {
3330 buffer_fadd( select_buf,
3331 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3332 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3334 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3337 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3344 "%s: Selected item is unexpected JSON type: %s",
3346 json_type( selfield->type )
3349 osrfAppSessionStatus(
3351 OSRF_STATUS_INTERNALSERVERERROR,
3352 "osrfMethodException",
3354 "Ill-formed SELECT item in JSON query"
3356 jsonIteratorFree( selclass_itr );
3357 buffer_free( select_buf );
3358 buffer_free( group_buf );
3359 if( defaultselhash ) jsonObjectFree( defaultselhash );
3360 free( join_clause );
3364 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3365 if( obj_is_true( agg_obj ) )
3366 aggregate_found = 1;
3368 // Append a comma (except for the first one)
3369 // and add the column to a GROUP BY clause
3373 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3375 buffer_fadd(group_buf, " %d", sel_pos);
3379 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3381 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3382 if ( ! obj_is_true( aggregate_obj ) ) {
3386 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3389 buffer_fadd(group_buf, " %d", sel_pos);
3392 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3396 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3399 _column = searchFieldTransform(class_info->alias, field, selfield);
3400 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3401 OSRF_BUFFER_ADD(group_buf, _column);
3402 _column = searchFieldTransform(class_info->alias, field, selfield);
3409 } // end while -- iterating across SELECT columns
3411 } // end while -- iterating across classes
3413 jsonIteratorFree(selclass_itr);
3417 char* col_list = buffer_release(select_buf);
3419 // Make sure the SELECT list isn't empty. This can happen, for example,
3420 // if we try to build a default SELECT clause from a non-core table.
3423 osrfLogError(OSRF_LOG_MARK, "%s: SELECT clause is empty", MODULENAME );
3425 osrfAppSessionStatus(
3427 OSRF_STATUS_INTERNALSERVERERROR,
3428 "osrfMethodException",
3430 "SELECT list is empty"
3433 buffer_free( group_buf );
3434 if( defaultselhash ) jsonObjectFree( defaultselhash );
3435 free( join_clause );
3440 if (from_function) table = searchValueTransform(join_hash);
3441 else table = strdup( curr_query->core.source_def );
3445 osrfAppSessionStatus(
3447 OSRF_STATUS_INTERNALSERVERERROR,
3448 "osrfMethodException",
3450 "Unable to identify table for core class"
3453 buffer_free( group_buf );
3454 if( defaultselhash ) jsonObjectFree( defaultselhash );
3455 free( join_clause );
3459 // Put it all together
3460 growing_buffer* sql_buf = buffer_init(128);
3461 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3465 // Append the join clause, if any
3467 buffer_add(sql_buf, join_clause);
3471 char* order_by_list = NULL;
3472 char* having_buf = NULL;
3474 if (!from_function) {
3476 // Build a WHERE clause, if there is one
3477 if ( search_hash ) {
3478 buffer_add(sql_buf, " WHERE ");
3480 // and it's on the WHERE clause
3481 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
3484 osrfAppSessionStatus(
3486 OSRF_STATUS_INTERNALSERVERERROR,
3487 "osrfMethodException",
3489 "Severe query error in WHERE predicate -- see error log for more details"
3492 buffer_free(group_buf);
3493 buffer_free(sql_buf);
3494 if (defaultselhash) jsonObjectFree(defaultselhash);
3498 buffer_add(sql_buf, pred);
3502 // Build a HAVING clause, if there is one
3503 if ( having_hash ) {
3505 // and it's on the the WHERE clause
3506 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
3508 if( ! having_buf ) {
3510 osrfAppSessionStatus(
3512 OSRF_STATUS_INTERNALSERVERERROR,
3513 "osrfMethodException",
3515 "Severe query error in HAVING predicate -- see error log for more details"
3518 buffer_free(group_buf);
3519 buffer_free(sql_buf);
3520 if (defaultselhash) jsonObjectFree(defaultselhash);
3525 growing_buffer* order_buf = NULL; // to collect ORDER BY list
3527 // Build an ORDER BY clause, if there is one
3528 if( NULL == order_hash )
3529 ; // No ORDER BY? do nothing
3530 else if( JSON_ARRAY == order_hash->type ) {
3531 // Array of field specifications, each specification being a
3532 // hash to define the class, field, and other details
3534 jsonObject* order_spec;
3535 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
3537 if( JSON_HASH != order_spec->type ) {
3538 osrfLogError(OSRF_LOG_MARK,
3539 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
3540 MODULENAME, json_type( order_spec->type ) );
3542 osrfAppSessionStatus(
3544 OSRF_STATUS_INTERNALSERVERERROR,
3545 "osrfMethodException",
3547 "Malformed ORDER BY clause -- see error log for more details"
3549 buffer_free( order_buf );
3551 buffer_free(group_buf);
3552 buffer_free(sql_buf);
3553 if (defaultselhash) jsonObjectFree(defaultselhash);
3557 const char* class_alias =
3558 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
3560 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
3563 OSRF_BUFFER_ADD(order_buf, ", ");
3565 order_buf = buffer_init(128);
3567 if( !field || !class_alias ) {
3568 osrfLogError(OSRF_LOG_MARK,
3569 "%s: Missing class or field name in field specification of ORDER BY clause",
3572 osrfAppSessionStatus(
3574 OSRF_STATUS_INTERNALSERVERERROR,
3575 "osrfMethodException",
3577 "Malformed ORDER BY clause -- see error log for more details"
3579 buffer_free( order_buf );
3581 buffer_free(group_buf);
3582 buffer_free(sql_buf);
3583 if (defaultselhash) jsonObjectFree(defaultselhash);
3587 ClassInfo* order_class_info = search_alias( class_alias );
3588 if( ! order_class_info ) {
3589 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
3590 "not in FROM clause", MODULENAME, class_alias );
3592 osrfAppSessionStatus(
3594 OSRF_STATUS_INTERNALSERVERERROR,
3595 "osrfMethodException",
3597 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3600 buffer_free(group_buf);
3601 buffer_free(sql_buf);
3602 if (defaultselhash) jsonObjectFree(defaultselhash);
3606 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
3608 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
3609 MODULENAME, class_alias, field );
3611 osrfAppSessionStatus(
3613 OSRF_STATUS_INTERNALSERVERERROR,
3614 "osrfMethodException",
3616 "Invalid field referenced in ORDER BY clause -- see error log for more details"
3619 buffer_free(group_buf);
3620 buffer_free(sql_buf);
3621 if (defaultselhash) jsonObjectFree(defaultselhash);
3623 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3624 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3625 MODULENAME, field );
3627 osrfAppSessionStatus(
3629 OSRF_STATUS_INTERNALSERVERERROR,
3630 "osrfMethodException",
3632 "Virtual field in ORDER BY clause -- see error log for more details"
3634 buffer_free( order_buf );
3636 buffer_free(group_buf);
3637 buffer_free(sql_buf);
3638 if (defaultselhash) jsonObjectFree(defaultselhash);
3642 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
3643 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
3644 if( ! transform_str ) {
3646 osrfAppSessionStatus(
3648 OSRF_STATUS_INTERNALSERVERERROR,
3649 "osrfMethodException",
3651 "Severe query error in ORDER BY clause -- see error log for more details"
3653 buffer_free( order_buf );
3655 buffer_free(group_buf);
3656 buffer_free(sql_buf);
3657 if (defaultselhash) jsonObjectFree(defaultselhash);
3661 OSRF_BUFFER_ADD( order_buf, transform_str );
3662 free( transform_str );
3665 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
3667 const char* direction =
3668 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
3670 if( direction[ 0 ] || 'D' == direction[ 0 ] )
3671 OSRF_BUFFER_ADD( order_buf, " DESC" );
3673 OSRF_BUFFER_ADD( order_buf, " ASC" );
3676 } else if( JSON_HASH == order_hash->type ) {
3677 // This hash is keyed on class alias. Each class has either
3678 // an array of field names or a hash keyed on field name.
3679 jsonIterator* class_itr = jsonNewIterator( order_hash );
3680 while ( (snode = jsonIteratorNext( class_itr )) ) {
3682 ClassInfo* order_class_info = search_alias( class_itr->key );
3683 if( ! order_class_info ) {
3684 osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
3685 MODULENAME, class_itr->key );
3687 osrfAppSessionStatus(
3689 OSRF_STATUS_INTERNALSERVERERROR,
3690 "osrfMethodException",
3692 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3694 jsonIteratorFree( class_itr );
3695 buffer_free( order_buf );
3697 buffer_free(group_buf);
3698 buffer_free(sql_buf);
3699 if (defaultselhash) jsonObjectFree(defaultselhash);
3703 osrfHash* field_list_def = order_class_info->fields;
3705 if ( snode->type == JSON_HASH ) {
3707 // Hash is keyed on field names from the current class. For each field
3708 // there is another layer of hash to define the sorting details, if any,
3709 // or a string to indicate direction of sorting.
3710 jsonIterator* order_itr = jsonNewIterator( snode );
3711 while ( (onode = jsonIteratorNext( order_itr )) ) {
3713 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
3715 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3716 MODULENAME, order_itr->key );
3718 osrfAppSessionStatus(
3720 OSRF_STATUS_INTERNALSERVERERROR,
3721 "osrfMethodException",
3723 "Invalid field in ORDER BY clause -- see error log for more details"
3725 jsonIteratorFree( order_itr );
3726 jsonIteratorFree( class_itr );
3727 buffer_free( order_buf );
3729 buffer_free(group_buf);
3730 buffer_free(sql_buf);
3731 if (defaultselhash) jsonObjectFree(defaultselhash);
3733 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3734 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3735 MODULENAME, order_itr->key );
3737 osrfAppSessionStatus(
3739 OSRF_STATUS_INTERNALSERVERERROR,
3740 "osrfMethodException",
3742 "Virtual field in ORDER BY clause -- see error log for more details"
3744 jsonIteratorFree( order_itr );
3745 jsonIteratorFree( class_itr );
3746 buffer_free( order_buf );
3748 buffer_free(group_buf);
3749 buffer_free(sql_buf);
3750 if (defaultselhash) jsonObjectFree(defaultselhash);
3754 const char* direction = NULL;
3755 if ( onode->type == JSON_HASH ) {
3756 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3757 string = searchFieldTransform(
3759 osrfHashGet( field_list_def, order_itr->key ),
3763 if( ctx ) osrfAppSessionStatus(
3765 OSRF_STATUS_INTERNALSERVERERROR,
3766 "osrfMethodException",
3768 "Severe query error in ORDER BY clause -- see error log for more details"
3770 jsonIteratorFree( order_itr );
3771 jsonIteratorFree( class_itr );
3773 buffer_free(group_buf);
3774 buffer_free(order_buf);
3775 buffer_free(sql_buf);
3776 if (defaultselhash) jsonObjectFree(defaultselhash);
3780 growing_buffer* field_buf = buffer_init(16);
3781 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3782 string = buffer_release(field_buf);
3785 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3786 const char* dir = jsonObjectGetString(tmp_const);
3787 if (!strncasecmp(dir, "d", 1)) {
3788 direction = " DESC";
3794 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
3795 osrfLogError( OSRF_LOG_MARK,
3796 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
3797 MODULENAME, json_type( onode->type ) );
3799 osrfAppSessionStatus(
3801 OSRF_STATUS_INTERNALSERVERERROR,
3802 "osrfMethodException",
3804 "Malformed ORDER BY clause -- see error log for more details"
3806 jsonIteratorFree( order_itr );
3807 jsonIteratorFree( class_itr );
3809 buffer_free(group_buf);
3810 buffer_free(order_buf);
3811 buffer_free(sql_buf);
3812 if (defaultselhash) jsonObjectFree(defaultselhash);
3816 string = strdup(order_itr->key);
3817 const char* dir = jsonObjectGetString(onode);
3818 if (!strncasecmp(dir, "d", 1)) {
3819 direction = " DESC";
3826 OSRF_BUFFER_ADD(order_buf, ", ");
3828 order_buf = buffer_init(128);
3830 OSRF_BUFFER_ADD(order_buf, string);
3834 OSRF_BUFFER_ADD(order_buf, direction);
3838 jsonIteratorFree(order_itr);
3840 } else if ( snode->type == JSON_ARRAY ) {
3842 // Array is a list of fields from the current class
3843 unsigned long order_idx = 0;
3844 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
3846 const char* _f = jsonObjectGetString( onode );
3848 osrfHash* field_def = osrfHashGet( field_list_def, _f );
3850 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3853 osrfAppSessionStatus(
3855 OSRF_STATUS_INTERNALSERVERERROR,
3856 "osrfMethodException",
3858 "Invalid field in ORDER BY clause -- see error log for more details"
3860 jsonIteratorFree( class_itr );
3861 buffer_free( order_buf );
3863 buffer_free(group_buf);
3864 buffer_free(sql_buf);
3865 if (defaultselhash) jsonObjectFree(defaultselhash);
3867 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3868 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3871 osrfAppSessionStatus(
3873 OSRF_STATUS_INTERNALSERVERERROR,
3874 "osrfMethodException",
3876 "Virtual field in ORDER BY clause -- see error log for more details"
3878 jsonIteratorFree( class_itr );
3879 buffer_free( order_buf );
3881 buffer_free(group_buf);
3882 buffer_free(sql_buf);
3883 if (defaultselhash) jsonObjectFree(defaultselhash);
3888 OSRF_BUFFER_ADD(order_buf, ", ");
3890 order_buf = buffer_init(128);
3892 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
3896 // IT'S THE OOOOOOOOOOOLD STYLE!
3898 osrfLogError(OSRF_LOG_MARK,
3899 "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3901 osrfAppSessionStatus(
3903 OSRF_STATUS_INTERNALSERVERERROR,
3904 "osrfMethodException",
3906 "Severe query error -- see error log for more details"
3911 buffer_free(group_buf);
3912 buffer_free(order_buf);
3913 buffer_free(sql_buf);
3914 if (defaultselhash) jsonObjectFree(defaultselhash);
3915 jsonIteratorFree(class_itr);
3919 jsonIteratorFree( class_itr );
3921 osrfLogError(OSRF_LOG_MARK,
3922 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
3923 MODULENAME, json_type( order_hash->type ) );
3925 osrfAppSessionStatus(
3927 OSRF_STATUS_INTERNALSERVERERROR,
3928 "osrfMethodException",
3930 "Malformed ORDER BY clause -- see error log for more details"
3932 buffer_free( order_buf );
3934 buffer_free(group_buf);
3935 buffer_free(sql_buf);
3936 if (defaultselhash) jsonObjectFree(defaultselhash);
3941 order_by_list = buffer_release( order_buf );
3945 string = buffer_release(group_buf);
3947 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
3948 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3949 OSRF_BUFFER_ADD( sql_buf, string );
3954 if( having_buf && *having_buf ) {
3955 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3956 OSRF_BUFFER_ADD( sql_buf, having_buf );
3960 if( order_by_list ) {
3962 if ( *order_by_list ) {
3963 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3964 OSRF_BUFFER_ADD( sql_buf, order_by_list );
3967 free( order_by_list );
3971 const char* str = jsonObjectGetString(limit);
3972 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
3976 const char* str = jsonObjectGetString(offset);
3977 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
3980 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3982 if (defaultselhash) jsonObjectFree(defaultselhash);
3984 return buffer_release(sql_buf);
3986 } // end of SELECT()
3988 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
3990 const char* locale = osrf_message_get_last_locale();
3992 osrfHash* fields = osrfHashGet(meta, "fields");
3993 char* core_class = osrfHashGet(meta, "classname");
3995 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
3997 jsonObject* node = NULL;
3998 jsonObject* snode = NULL;
3999 jsonObject* onode = NULL;
4000 const jsonObject* _tmp = NULL;
4001 jsonObject* selhash = NULL;
4002 jsonObject* defaultselhash = NULL;
4004 growing_buffer* sql_buf = buffer_init(128);
4005 growing_buffer* select_buf = buffer_init(128);
4007 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4008 defaultselhash = jsonNewObjectType(JSON_HASH);
4009 selhash = defaultselhash;
4012 // If there's no SELECT list for the core class, build one
4013 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
4014 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4016 // Add every non-virtual field to the field list
4017 osrfHash* field_def = NULL;
4018 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4019 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4020 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4021 const char* field = osrfHashIteratorKey( field_itr );
4022 jsonObjectPush( field_list, jsonNewObject( field ) );
4025 osrfHashIteratorFree( field_itr );
4026 jsonObjectSetKey( selhash, core_class, field_list );
4030 jsonIterator* class_itr = jsonNewIterator( selhash );
4031 while ( (snode = jsonIteratorNext( class_itr )) ) {
4033 const char* cname = class_itr->key;
4034 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4035 if (!idlClass) continue;
4037 if (strcmp(core_class,class_itr->key)) {
4038 if (!join_hash) continue;
4040 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
4042 jsonObjectFree(found);
4046 jsonObjectFree(found);
4049 jsonIterator* select_itr = jsonNewIterator( snode );
4050 while ( (node = jsonIteratorNext( select_itr )) ) {
4051 const char* item_str = jsonObjectGetString( node );
4052 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4053 char* fname = osrfHashGet(field, "name");
4055 if (!field) continue;
4060 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
4065 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4066 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4069 i18n = osrfHashGet(field, "i18n");
4071 if( str_is_true( i18n ) ) {
4072 char* pkey = osrfHashGet(idlClass, "primarykey");
4073 char* tname = osrfHashGet(idlClass, "tablename");
4075 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);
4077 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4080 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4084 jsonIteratorFree(select_itr);
4087 jsonIteratorFree(class_itr);
4089 char* col_list = buffer_release(select_buf);
4090 char* table = getSourceDefinition(meta);
4092 table = strdup( "(null)" );
4094 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4098 // Clear the query stack (as a fail-safe precaution against possible
4099 // leftover garbage); then push the first query frame onto the stack.
4100 clear_query_stack();
4102 if( add_query_core( NULL, core_class ) ) {
4104 osrfAppSessionStatus(
4106 OSRF_STATUS_INTERNALSERVERERROR,
4107 "osrfMethodException",
4109 "Unable to build query frame for core class"
4115 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4116 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
4117 OSRF_BUFFER_ADD(sql_buf, join_clause);
4121 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4122 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
4124 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
4126 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4128 osrfAppSessionStatus(
4130 OSRF_STATUS_INTERNALSERVERERROR,
4131 "osrfMethodException",
4133 "Severe query error -- see error log for more details"
4135 buffer_free(sql_buf);
4136 if(defaultselhash) jsonObjectFree(defaultselhash);
4137 clear_query_stack();
4140 buffer_add(sql_buf, pred);
4145 char* string = NULL;
4146 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4148 growing_buffer* order_buf = buffer_init(128);
4151 jsonIterator* class_itr = jsonNewIterator( _tmp );
4152 while ( (snode = jsonIteratorNext( class_itr )) ) {
4154 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4157 if ( snode->type == JSON_HASH ) {
4159 jsonIterator* order_itr = jsonNewIterator( snode );
4160 while ( (onode = jsonIteratorNext( order_itr )) ) {
4162 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4163 class_itr->key, order_itr->key );
4167 char* direction = NULL;
4168 if ( onode->type == JSON_HASH ) {
4169 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4170 string = searchFieldTransform( class_itr->key, field_def, onode );
4172 osrfAppSessionStatus(
4174 OSRF_STATUS_INTERNALSERVERERROR,
4175 "osrfMethodException",
4177 "Severe query error in ORDER BY clause -- see error log for more details"
4179 jsonIteratorFree( order_itr );
4180 jsonIteratorFree( class_itr );
4181 buffer_free( order_buf );
4182 buffer_free( sql_buf );
4183 if( defaultselhash ) jsonObjectFree( defaultselhash );
4184 clear_query_stack();
4188 growing_buffer* field_buf = buffer_init(16);
4189 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4190 string = buffer_release(field_buf);
4193 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4194 const char* dir = jsonObjectGetString(_tmp);
4195 if (!strncasecmp(dir, "d", 1)) {
4196 direction = " DESC";
4203 string = strdup(order_itr->key);
4204 const char* dir = jsonObjectGetString(onode);
4205 if (!strncasecmp(dir, "d", 1)) {
4206 direction = " DESC";
4215 buffer_add(order_buf, ", ");
4218 buffer_add(order_buf, string);
4222 buffer_add(order_buf, direction);
4227 jsonIteratorFree(order_itr);
4230 const char* str = jsonObjectGetString(snode);
4231 buffer_add(order_buf, str);
4237 jsonIteratorFree(class_itr);
4239 string = buffer_release(order_buf);
4242 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4243 OSRF_BUFFER_ADD( sql_buf, string );
4249 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4250 const char* str = jsonObjectGetString(_tmp);
4258 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4260 const char* str = jsonObjectGetString(_tmp);
4269 if (defaultselhash) jsonObjectFree(defaultselhash);
4270 clear_query_stack();
4272 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4273 return buffer_release(sql_buf);
4276 int doJSONSearch ( osrfMethodContext* ctx ) {
4277 if(osrfMethodVerifyContext( ctx )) {
4278 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4282 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
4287 dbhandle = writehandle;
4289 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4293 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4294 flags |= SELECT_DISTINCT;
4296 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4297 flags |= DISABLE_I18N;
4299 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4302 jsonObjectGetKey( hash, "select" ),
4303 jsonObjectGetKey( hash, "from" ),
4304 jsonObjectGetKey( hash, "where" ),
4305 jsonObjectGetKey( hash, "having" ),
4306 jsonObjectGetKey( hash, "order_by" ),
4307 jsonObjectGetKey( hash, "limit" ),
4308 jsonObjectGetKey( hash, "offset" ),
4311 clear_query_stack();
4318 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4319 dbi_result result = dbi_conn_query(dbhandle, sql);
4322 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4324 if (dbi_result_first_row(result)) {
4325 /* JSONify the result */
4326 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4329 jsonObject* return_val = oilsMakeJSONFromResult( result );
4330 osrfAppRespond( ctx, return_val );
4331 jsonObjectFree( return_val );
4332 } while (dbi_result_next_row(result));
4335 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4338 osrfAppRespondComplete( ctx, NULL );
4340 /* clean up the query */
4341 dbi_result_free(result);
4345 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4346 osrfAppSessionStatus(
4348 OSRF_STATUS_INTERNALSERVERERROR,
4349 "osrfMethodException",
4351 "Severe query error -- see error log for more details"
4359 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4360 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4363 dbhandle = writehandle;
4365 osrfHash* links = osrfHashGet(meta, "links");
4366 osrfHash* fields = osrfHashGet(meta, "fields");
4367 char* core_class = osrfHashGet(meta, "classname");
4368 char* pkey = osrfHashGet(meta, "primarykey");
4370 const jsonObject* _tmp;
4373 char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4375 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4380 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4382 dbi_result result = dbi_conn_query(dbhandle, sql);
4383 if( NULL == result ) {
4384 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4385 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4386 osrfAppSessionStatus(
4388 OSRF_STATUS_INTERNALSERVERERROR,
4389 "osrfMethodException",
4391 "Severe query error -- see error log for more details"
4398 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4401 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
4402 osrfHash* dedup = osrfNewHash();
4404 if (dbi_result_first_row(result)) {
4405 /* JSONify the result */
4406 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4408 obj = oilsMakeFieldmapperFromResult( result, meta );
4409 char* pkey_val = oilsFMGetString( obj, pkey );
4410 if ( osrfHashGet( dedup, pkey_val ) ) {
4411 jsonObjectFree(obj);
4414 osrfHashSet( dedup, pkey_val, pkey_val );
4415 jsonObjectPush(res_list, obj);
4417 } while (dbi_result_next_row(result));
4419 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
4423 osrfHashFree(dedup);
4424 /* clean up the query */
4425 dbi_result_free(result);
4428 if (res_list->size && query_hash) {
4429 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
4431 int x = (int)jsonObjectGetNumber(_tmp);
4432 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
4434 const jsonObject* temp_blob;
4435 if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
4437 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
4438 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
4440 osrfStringArray* link_fields = NULL;
4443 if (flesh_fields->size == 1) {
4444 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
4445 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
4450 link_fields = osrfNewStringArray(1);
4451 jsonIterator* _i = jsonNewIterator( flesh_fields );
4452 while ((_f = jsonIteratorNext( _i ))) {
4453 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4455 jsonIteratorFree(_i);
4460 unsigned long res_idx = 0;
4461 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
4466 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4468 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4470 osrfHash* kid_link = osrfHashGet(links, link_field);
4471 if (!kid_link) continue;
4473 osrfHash* field = osrfHashGet(fields, link_field);
4474 if (!field) continue;
4476 osrfHash* value_field = field;
4478 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4479 if (!kid_idl) continue;
4481 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4482 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4485 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4486 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4489 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4491 if (link_map->size > 0) {
4492 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4495 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4500 osrfHashGet(kid_link, "class"),
4507 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4508 osrfHashGet(kid_link, "field"),
4509 osrfHashGet(kid_link, "class"),
4510 osrfHashGet(kid_link, "key"),
4511 osrfHashGet(kid_link, "reltype")
4514 const char* search_key = jsonObjectGetString(
4517 atoi( osrfHashGet(value_field, "array_position") )
4522 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4526 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
4528 // construct WHERE clause
4529 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
4532 osrfHashGet(kid_link, "key"),
4533 jsonNewObject( search_key )
4536 // construct the rest of the query
4537 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
4538 jsonObjectSetKey( rest_of_query, "flesh",
4539 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4543 jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
4545 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
4546 jsonObjectSetKey( rest_of_query, "order_by",
4547 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
4551 if (jsonObjectGetKeyConst(query_hash, "select")) {
4552 jsonObjectSetKey( rest_of_query, "select",
4553 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
4557 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
4558 where_clause, rest_of_query, err);
4560 jsonObjectFree( where_clause );
4561 jsonObjectFree( rest_of_query );
4564 osrfStringArrayFree(link_fields);
4565 jsonObjectFree(res_list);
4566 jsonObjectFree(flesh_blob);
4570 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4572 jsonObject* X = NULL;
4573 if ( link_map->size > 0 && kids->size > 0 ) {
4575 kids = jsonNewObjectType(JSON_ARRAY);
4577 jsonObject* _k_node;
4578 unsigned long res_idx = 0;
4579 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
4585 (unsigned long)atoi(
4591 osrfHashGet(kid_link, "class")
4595 osrfStringArrayGetString( link_map, 0 )
4603 } // end while loop traversing X
4606 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4607 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4610 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4611 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4615 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4616 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4619 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4620 jsonObjectClone( kids )
4625 jsonObjectFree(kids);
4629 jsonObjectFree( kids );
4631 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4632 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4635 } // end while loop traversing res_list
4636 jsonObjectFree( flesh_blob );
4637 osrfStringArrayFree(link_fields);
4646 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
4648 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4650 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
4652 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
4655 if (!verifyObjectClass(ctx, target)) {
4660 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4661 osrfAppSessionStatus(
4663 OSRF_STATUS_BADREQUEST,
4664 "osrfMethodException",
4666 "No active transaction -- required for UPDATE"
4672 // The following test is harmless but redundant. If a class is
4673 // readonly, we don't register an update method for it.
4674 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4675 osrfAppSessionStatus(
4677 OSRF_STATUS_BADREQUEST,
4678 "osrfMethodException",
4680 "Cannot UPDATE readonly class"
4686 dbhandle = writehandle;
4688 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4690 // Set the last_xact_id
4691 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4693 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
4694 trans_id, target->classname, index);
4695 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4698 char* pkey = osrfHashGet(meta, "primarykey");
4699 osrfHash* fields = osrfHashGet(meta, "fields");
4701 char* id = oilsFMGetString( target, pkey );
4705 "%s updating %s object with %s = %s",
4707 osrfHashGet(meta, "fieldmapper"),
4712 growing_buffer* sql = buffer_init(128);
4713 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4716 osrfHash* field_def = NULL;
4717 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4718 while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
4720 // Skip virtual fields, and the primary key
4721 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
4724 const char* field_name = osrfHashIteratorKey( field_itr );
4725 if( ! strcmp( field_name, pkey ) )
4728 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4730 int value_is_numeric = 0; // boolean
4732 if (field_object && field_object->classname) {
4733 value = oilsFMGetString(
4735 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4738 value = jsonObjectToSimpleString( field_object );
4739 if( field_object && JSON_NUMBER == field_object->type )
4740 value_is_numeric = 1;
4743 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
4744 osrfHashGet(meta, "fieldmapper"), field_name, value);
4746 if (!field_object || field_object->type == JSON_NULL) {
4747 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
4748 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
4749 if (first) first = 0;
4750 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4751 buffer_fadd( sql, " %s = NULL", field_name );
4754 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
4755 if (first) first = 0;
4756 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4758 const char* numtype = get_datatype( field_def );
4759 if ( !strncmp( numtype, "INT", 3 ) ) {
4760 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
4761 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
4762 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
4764 // Must really be intended as a string, so quote it
4765 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4766 buffer_fadd( sql, " %s = %s", field_name, value );
4768 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4769 osrfAppSessionStatus(
4771 OSRF_STATUS_INTERNALSERVERERROR,
4772 "osrfMethodException",
4774 "Error quoting string -- please see the error log for more details"
4778 osrfHashIteratorFree( field_itr );
4785 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
4788 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4789 if (first) first = 0;
4790 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4791 buffer_fadd( sql, " %s = %s", field_name, value );
4794 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4795 osrfAppSessionStatus(
4797 OSRF_STATUS_INTERNALSERVERERROR,
4798 "osrfMethodException",
4800 "Error quoting string -- please see the error log for more details"
4804 osrfHashIteratorFree( field_itr );
4815 osrfHashIteratorFree( field_itr );
4817 jsonObject* obj = jsonNewObject(id);
4819 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4820 dbi_conn_quote_string(dbhandle, &id);
4822 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
4824 char* query = buffer_release(sql);
4825 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
4827 dbi_result result = dbi_conn_query(dbhandle, query);
4831 jsonObjectFree(obj);
4832 obj = jsonNewObject(NULL);
4835 "%s ERROR updating %s object with %s = %s",
4837 osrfHashGet(meta, "fieldmapper"),
4848 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4850 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4852 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4853 osrfAppSessionStatus(
4855 OSRF_STATUS_BADREQUEST,
4856 "osrfMethodException",
4858 "No active transaction -- required for DELETE"
4864 // The following test is harmless but redundant. If a class is
4865 // readonly, we don't register a delete method for it.
4866 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4867 osrfAppSessionStatus(
4869 OSRF_STATUS_BADREQUEST,
4870 "osrfMethodException",
4872 "Cannot DELETE readonly class"
4878 dbhandle = writehandle;
4882 char* pkey = osrfHashGet(meta, "primarykey");
4890 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4891 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4896 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4899 if (!verifyObjectPCRUD( ctx, NULL )) {
4904 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4909 "%s deleting %s object with %s = %s",
4911 osrfHashGet(meta, "fieldmapper"),
4916 obj = jsonNewObject(id);
4918 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4919 dbi_conn_quote_string(writehandle, &id);
4921 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4924 jsonObjectFree(obj);
4925 obj = jsonNewObject(NULL);
4928 "%s ERROR deleting %s object with %s = %s",
4930 osrfHashGet(meta, "fieldmapper"),
4943 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4944 if(!(result && meta)) return jsonNULL;
4946 jsonObject* object = jsonNewObject(NULL);
4947 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4949 osrfHash* fields = osrfHashGet(meta, "fields");
4951 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4955 char dt_string[256];
4959 int columnIndex = 1;
4961 unsigned short type;
4962 const char* columnName;
4964 /* cycle through the column list */
4965 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
4967 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4969 fmIndex = -1; // reset the position
4971 /* determine the field type and storage attributes */
4972 type = dbi_result_get_field_type_idx(result, columnIndex);
4973 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
4975 /* fetch the fieldmapper index */
4976 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4978 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4981 const char* pos = (char*)osrfHashGet(_f, "array_position");
4982 if ( !pos ) continue;
4984 fmIndex = atoi( pos );
4985 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
4990 if (dbi_result_field_is_null_idx(result, columnIndex)) {
4991 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
4996 case DBI_TYPE_INTEGER :
4998 if( attr & DBI_INTEGER_SIZE8 )
4999 jsonObjectSetIndex( object, fmIndex,
5000 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
5002 jsonObjectSetIndex( object, fmIndex,
5003 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
5007 case DBI_TYPE_DECIMAL :
5008 jsonObjectSetIndex( object, fmIndex,
5009 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
5012 case DBI_TYPE_STRING :
5018 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
5023 case DBI_TYPE_DATETIME :
5025 memset(dt_string, '\0', sizeof(dt_string));
5026 memset(&gmdt, '\0', sizeof(gmdt));
5028 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5031 if (!(attr & DBI_DATETIME_DATE)) {
5032 gmtime_r( &_tmp_dt, &gmdt );
5033 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5034 } else if (!(attr & DBI_DATETIME_TIME)) {
5035 localtime_r( &_tmp_dt, &gmdt );
5036 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5038 localtime_r( &_tmp_dt, &gmdt );
5039 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5042 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
5046 case DBI_TYPE_BINARY :
5047 osrfLogError( OSRF_LOG_MARK,
5048 "Can't do binary at column %s : index %d", columnName, columnIndex);
5057 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5058 if(!result) return jsonNULL;
5060 jsonObject* object = jsonNewObject(NULL);
5063 char dt_string[256];
5067 int columnIndex = 1;
5069 unsigned short type;
5070 const char* columnName;
5072 /* cycle through the column list */
5073 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5075 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5077 fmIndex = -1; // reset the position
5079 /* determine the field type and storage attributes */
5080 type = dbi_result_get_field_type_idx(result, columnIndex);
5081 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5083 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5084 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
5089 case DBI_TYPE_INTEGER :
5091 if( attr & DBI_INTEGER_SIZE8 )
5092 jsonObjectSetKey( object, columnName,
5093 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)) );
5095 jsonObjectSetKey( object, columnName,
5096 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
5099 case DBI_TYPE_DECIMAL :
5100 jsonObjectSetKey( object, columnName,
5101 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
5104 case DBI_TYPE_STRING :
5105 jsonObjectSetKey( object, columnName,
5106 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
5109 case DBI_TYPE_DATETIME :
5111 memset(dt_string, '\0', sizeof(dt_string));
5112 memset(&gmdt, '\0', sizeof(gmdt));
5114 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5117 if (!(attr & DBI_DATETIME_DATE)) {
5118 gmtime_r( &_tmp_dt, &gmdt );
5119 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5120 } else if (!(attr & DBI_DATETIME_TIME)) {
5121 localtime_r( &_tmp_dt, &gmdt );
5122 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5124 localtime_r( &_tmp_dt, &gmdt );
5125 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5128 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
5131 case DBI_TYPE_BINARY :
5132 osrfLogError( OSRF_LOG_MARK,
5133 "Can't do binary at column %s : index %d", columnName, columnIndex );
5137 } // end while loop traversing result
5142 // Interpret a string as true or false
5143 static int str_is_true( const char* str ) {
5144 if( NULL == str || strcasecmp( str, "true" ) )
5150 // Interpret a jsonObject as true or false
5151 static int obj_is_true( const jsonObject* obj ) {
5154 else switch( obj->type )
5162 if( strcasecmp( obj->value.s, "true" ) )
5166 case JSON_NUMBER : // Support 1/0 for perl's sake
5167 if( jsonObjectGetNumber( obj ) == 1.0 )
5176 // Translate a numeric code into a text string identifying a type of
5177 // jsonObject. To be used for building error messages.
5178 static const char* json_type( int code ) {
5184 return "JSON_ARRAY";
5186 return "JSON_STRING";
5188 return "JSON_NUMBER";
5194 return "(unrecognized)";
5198 // Extract the "primitive" attribute from an IDL field definition.
5199 // If we haven't initialized the app, then we must be running in
5200 // some kind of testbed. In that case, default to "string".
5201 static const char* get_primitive( osrfHash* field ) {
5202 const char* s = osrfHashGet( field, "primitive" );
5204 if( child_initialized )
5207 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5209 osrfHashGet( field, "name" )
5217 // Extract the "datatype" attribute from an IDL field definition.
5218 // If we haven't initialized the app, then we must be running in
5219 // some kind of testbed. In that case, default to to NUMERIC,
5220 // since we look at the datatype only for numbers.
5221 static const char* get_datatype( osrfHash* field ) {
5222 const char* s = osrfHashGet( field, "datatype" );
5224 if( child_initialized )
5227 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5229 osrfHashGet( field, "name" )
5238 If the input string is potentially a valid SQL identifier, return 1.
5241 Purpose: to prevent certain kinds of SQL injection. To that end we
5242 don't necessarily need to follow all the rules exactly, such as requiring
5243 that the first character not be a digit.
5245 We allow leading and trailing white space. In between, we do not allow
5246 punctuation (except for underscores and dollar signs), control
5247 characters, or embedded white space.
5249 More pedantically we should allow quoted identifiers containing arbitrary
5250 characters, but for the foreseeable future such quoted identifiers are not
5251 likely to be an issue.
5253 static int is_identifier( const char* s) {
5257 // Skip leading white space
5258 while( isspace( (unsigned char) *s ) )
5262 return 0; // Nothing but white space? Not okay.
5264 // Check each character until we reach white space or
5265 // end-of-string. Letters, digits, underscores, and
5266 // dollar signs are okay. With the exception of periods
5267 // (as in schema.identifier), control characters and other
5268 // punctuation characters are not okay. Anything else
5269 // is okay -- it could for example be part of a multibyte
5270 // UTF8 character such as a letter with diacritical marks,
5271 // and those are allowed.
5273 if( isalnum( (unsigned char) *s )
5277 ; // Fine; keep going
5278 else if( ispunct( (unsigned char) *s )
5279 || iscntrl( (unsigned char) *s ) )
5282 } while( *s && ! isspace( (unsigned char) *s ) );
5284 // If we found any white space in the above loop,
5285 // the rest had better be all white space.
5287 while( isspace( (unsigned char) *s ) )
5291 return 0; // White space was embedded within non-white space
5297 Determine whether to accept a character string as a comparison operator.
5298 Return 1 if it's good, or 0 if it's bad.
5300 We don't validate it for real. We just make sure that it doesn't contain
5301 any semicolons or white space (with special exceptions for a few specific
5302 operators). The idea is to block certain kinds of SQL injection. If it
5303 has no semicolons or white space but it's still not a valid operator, then
5304 the database will complain.
5306 Another approach would be to compare the string against a short list of
5307 approved operators. We don't do that because we want to allow custom
5308 operators like ">100*", which would be difficult or impossible to
5309 express otherwise in a JSON query.
5311 static int is_good_operator( const char* op ) {
5312 if( !op ) return 0; // Sanity check
5316 if( isspace( (unsigned char) *s ) ) {
5317 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
5318 // and IS NOT DISTINCT FROM.
5319 if( !strcasecmp( op, "similar to" ) )
5321 else if( !strcasecmp( op, "is distinct from" ) )
5323 else if( !strcasecmp( op, "is not distinct from" ) )
5328 else if( ';' == *s )
5335 /* ----------------------------------------------------------------------------------
5336 The following machinery supports a stack of query frames for use by SELECT().
5338 A query frame caches information about one level of a SELECT query. When we enter
5339 a subquery, we push another query frame onto the stack, and pop it off when we leave.
5341 The query frame stores information about the core class, and about any joined classes
5344 The main purpose is to map table aliases to classes and tables, so that a query can
5345 join to the same table more than once. A secondary goal is to reduce the number of
5346 lookups in the IDL by caching the results.
5347 ----------------------------------------------------------------------------------*/
5349 #define STATIC_CLASS_INFO_COUNT 3
5351 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
5353 /* ---------------------------------------------------------------------------
5354 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5356 ---------------------------------------------------------------------------*/
5357 static ClassInfo* allocate_class_info( void ) {
5358 // In order to reduce the number of mallocs and frees, we return a static
5359 // instance of ClassInfo, if we can find one that we're not already using.
5360 // We rely on the fact that the compiler will implicitly initialize the
5361 // static instances so that in_use == 0.
5364 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5365 if( ! static_class_info[ i ].in_use ) {
5366 static_class_info[ i ].in_use = 1;
5367 return static_class_info + i;
5371 // The static ones are all in use. Malloc one.
5373 return safe_malloc( sizeof( ClassInfo ) );
5376 /* --------------------------------------------------------------------------
5377 Free any malloc'd memory owned by a ClassInfo; return it to a pristine state
5378 ---------------------------------------------------------------------------*/
5379 static void clear_class_info( ClassInfo* info ) {
5384 // Free any malloc'd strings
5386 if( info->alias != info->alias_store )
5387 free( info->alias );
5389 if( info->class_name != info->class_name_store )
5390 free( info->class_name );
5392 free( info->source_def );
5394 info->alias = info->class_name = info->source_def = NULL;
5398 /* --------------------------------------------------------------------------
5399 Deallocate a ClassInfo and everything it owns
5400 ---------------------------------------------------------------------------*/
5401 static void free_class_info( ClassInfo* info ) {
5406 clear_class_info( info );
5408 // If it's one of the static instances, just mark it as not in use
5411 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5412 if( info == static_class_info + i ) {
5413 static_class_info[ i ].in_use = 0;
5418 // Otherwise it must have been malloc'd, so free it
5423 /* --------------------------------------------------------------------------
5424 Populate an already-allocated ClassInfo. Return 0 if successful, 1 if not.
5425 ---------------------------------------------------------------------------*/
5426 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
5429 osrfLogError( OSRF_LOG_MARK,
5430 "%s ERROR: No ClassInfo available to populate", MODULENAME );
5431 info->alias = info->class_name = info->source_def = NULL;
5432 info->class_def = info->fields = info->links = NULL;
5437 osrfLogError( OSRF_LOG_MARK,
5438 "%s ERROR: No class name provided for lookup", MODULENAME );
5439 info->alias = info->class_name = info->source_def = NULL;
5440 info->class_def = info->fields = info->links = NULL;
5444 // Alias defaults to class name if not supplied
5445 if( ! alias || ! alias[ 0 ] )
5448 // Look up class info in the IDL
5449 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
5451 osrfLogError( OSRF_LOG_MARK,
5452 "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
5453 info->alias = info->class_name = info->source_def = NULL;
5454 info->class_def = info->fields = info->links = NULL;
5456 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
5457 osrfLogError( OSRF_LOG_MARK,
5458 "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
5459 info->alias = info->class_name = info->source_def = NULL;
5460 info->class_def = info->fields = info->links = NULL;
5464 osrfHash* links = osrfHashGet( class_def, "links" );
5466 osrfLogError( OSRF_LOG_MARK,
5467 "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
5468 info->alias = info->class_name = info->source_def = NULL;
5469 info->class_def = info->fields = info->links = NULL;
5473 osrfHash* fields = osrfHashGet( class_def, "fields" );
5475 osrfLogError( OSRF_LOG_MARK,
5476 "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
5477 info->alias = info->class_name = info->source_def = NULL;
5478 info->class_def = info->fields = info->links = NULL;
5482 char* source_def = getSourceDefinition( class_def );
5486 // We got everything we need, so populate the ClassInfo
5487 if( strlen( alias ) > ALIAS_STORE_SIZE )
5488 info->alias = strdup( alias );
5490 strcpy( info->alias_store, alias );
5491 info->alias = info->alias_store;
5494 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
5495 info->class_name = strdup( class );
5497 strcpy( info->class_name_store, class );
5498 info->class_name = info->class_name_store;
5501 info->source_def = source_def;
5503 info->class_def = class_def;
5504 info->links = links;
5505 info->fields = fields;
5510 #define STATIC_FRAME_COUNT 3
5512 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
5514 /* ---------------------------------------------------------------------------
5515 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5517 ---------------------------------------------------------------------------*/
5518 static QueryFrame* allocate_frame( void ) {
5519 // In order to reduce the number of mallocs and frees, we return a static
5520 // instance of QueryFrame, if we can find one that we're not already using.
5521 // We rely on the fact that the compiler will implicitly initialize the
5522 // static instances so that in_use == 0.
5525 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5526 if( ! static_frame[ i ].in_use ) {
5527 static_frame[ i ].in_use = 1;
5528 return static_frame + i;
5532 // The static ones are all in use. Malloc one.
5534 return safe_malloc( sizeof( QueryFrame ) );
5537 /* --------------------------------------------------------------------------
5538 Free a QueryFrame, and all the memory it owns.
5539 ---------------------------------------------------------------------------*/
5540 static void free_query_frame( QueryFrame* frame ) {
5545 clear_class_info( &frame->core );
5547 // Free the join list
5549 ClassInfo* info = frame->join_list;
5552 free_class_info( info );
5556 frame->join_list = NULL;
5559 // If the frame is a static instance, just mark it as unused
5561 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5562 if( frame == static_frame + i ) {
5563 static_frame[ i ].in_use = 0;
5568 // Otherwise it must have been malloc'd, so free it
5573 /* --------------------------------------------------------------------------
5574 Search a given QueryFrame for a specified alias. If you find it, return
5575 a pointer to the corresponding ClassInfo. Otherwise return NULL.
5576 ---------------------------------------------------------------------------*/
5577 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
5578 if( ! frame || ! target ) {
5582 ClassInfo* found_class = NULL;
5584 if( !strcmp( target, frame->core.alias ) )
5585 return &(frame->core);
5587 ClassInfo* curr_class = frame->join_list;
5588 while( curr_class ) {
5589 if( strcmp( target, curr_class->alias ) )
5590 curr_class = curr_class->next;
5592 found_class = curr_class;
5601 /* --------------------------------------------------------------------------
5602 Push a new (blank) QueryFrame onto the stack.
5603 ---------------------------------------------------------------------------*/
5604 static void push_query_frame( void ) {
5605 QueryFrame* frame = allocate_frame();
5606 frame->join_list = NULL;
5607 frame->next = curr_query;
5609 // Initialize the ClassInfo for the core class
5610 ClassInfo* core = &frame->core;
5611 core->alias = core->class_name = core->source_def = NULL;
5612 core->class_def = core->fields = core->links = NULL;
5617 /* --------------------------------------------------------------------------
5618 Pop a QueryFrame off the stack and destroy it
5619 ---------------------------------------------------------------------------*/
5620 static void pop_query_frame( void ) {
5625 QueryFrame* popped = curr_query;
5626 curr_query = popped->next;
5628 free_query_frame( popped );
5631 /* --------------------------------------------------------------------------
5632 Populate the ClassInfo for the core class. Return 0 if successful, 1 if not.
5633 ---------------------------------------------------------------------------*/
5634 static int add_query_core( const char* alias, const char* class_name ) {
5637 if( ! curr_query ) {
5638 osrfLogError( OSRF_LOG_MARK,
5639 "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
5641 } else if( curr_query->core.alias ) {
5642 osrfLogError( OSRF_LOG_MARK,
5643 "%s ERROR: Core class %s already populated as %s",
5644 MODULENAME, curr_query->core.class_name, curr_query->core.alias );
5648 build_class_info( &curr_query->core, alias, class_name );
5649 if( curr_query->core.alias )
5652 osrfLogError( OSRF_LOG_MARK,
5653 "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
5658 /* --------------------------------------------------------------------------
5659 Search the current QueryFrame for a specified alias. If you find it,
5660 return a pointer to the corresponding ClassInfo. Otherwise return NULL.
5661 ---------------------------------------------------------------------------*/
5662 static ClassInfo* search_alias( const char* target ) {
5663 return search_alias_in_frame( curr_query, target );
5666 /* --------------------------------------------------------------------------
5667 Search all levels of query for a specified alias, starting with the
5668 current query. If you find it, return a pointer to the corresponding
5669 ClassInfo. Otherwise return NULL.
5670 ---------------------------------------------------------------------------*/
5671 static ClassInfo* search_all_alias( const char* target ) {
5672 ClassInfo* found_class = NULL;
5673 QueryFrame* curr_frame = curr_query;
5675 while( curr_frame ) {
5676 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
5679 curr_frame = curr_frame->next;
5685 /* --------------------------------------------------------------------------
5686 Add a class to the list of classes joined to the current query.
5687 ---------------------------------------------------------------------------*/
5688 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
5690 if( ! classname || ! *classname ) { // sanity check
5691 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
5698 const ClassInfo* conflict = search_alias( alias );
5700 osrfLogError( OSRF_LOG_MARK,
5701 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
5702 MODULENAME, alias, conflict->class_name );
5706 ClassInfo* info = allocate_class_info();
5708 if( build_class_info( info, alias, classname ) ) {
5709 free_class_info( info );
5713 // Add the new ClassInfo to the join list of the current QueryFrame
5714 info->next = curr_query->join_list;
5715 curr_query->join_list = info;
5720 /* --------------------------------------------------------------------------
5721 Destroy all nodes on the query stack.
5722 ---------------------------------------------------------------------------*/
5723 static void clear_query_stack( void ) {