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 );
1508 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1509 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1512 buffer_add(col_buf, field_name);
1514 if (!field_object || field_object->type == JSON_NULL) {
1515 buffer_add( val_buf, "DEFAULT" );
1517 } else if ( !strcmp(get_primitive( field ), "number") ) {
1518 const char* numtype = get_datatype( field );
1519 if ( !strcmp( numtype, "INT8") ) {
1520 buffer_fadd( val_buf, "%lld", atoll(value) );
1522 } else if ( !strcmp( numtype, "INT") ) {
1523 buffer_fadd( val_buf, "%d", atoi(value) );
1525 } else if ( !strcmp( numtype, "NUMERIC") ) {
1526 buffer_fadd( val_buf, "%f", atof(value) );
1529 if ( dbi_conn_quote_string(writehandle, &value) ) {
1530 OSRF_BUFFER_ADD( val_buf, value );
1533 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1534 osrfAppSessionStatus(
1536 OSRF_STATUS_INTERNALSERVERERROR,
1537 "osrfMethodException",
1539 "Error quoting string -- please see the error log for more details"
1542 buffer_free(table_buf);
1543 buffer_free(col_buf);
1544 buffer_free(val_buf);
1554 osrfHashIteratorFree( field_itr );
1556 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1557 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1559 char* table_str = buffer_release(table_buf);
1560 char* col_str = buffer_release(col_buf);
1561 char* val_str = buffer_release(val_buf);
1562 growing_buffer* sql = buffer_init(128);
1563 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1568 char* query = buffer_release(sql);
1570 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1573 dbi_result result = dbi_conn_query(writehandle, query);
1575 jsonObject* obj = NULL;
1578 obj = jsonNewObject(NULL);
1581 "%s ERROR inserting %s object using query [%s]",
1583 osrfHashGet(meta, "fieldmapper"),
1586 osrfAppSessionStatus(
1588 OSRF_STATUS_INTERNALSERVERERROR,
1589 "osrfMethodException",
1591 "INSERT error -- please see the error log for more details"
1596 char* id = oilsFMGetString(target, pkey);
1598 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1599 growing_buffer* _id = buffer_init(10);
1600 buffer_fadd(_id, "%lld", new_id);
1601 id = buffer_release(_id);
1604 // Find quietness specification, if present
1605 const char* quiet_str = NULL;
1607 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1609 quiet_str = jsonObjectGetString( quiet_obj );
1612 if( str_is_true( quiet_str ) ) { // if quietness is specified
1613 obj = jsonNewObject(id);
1617 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1618 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1620 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1622 jsonObjectFree( where_clause );
1627 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1630 jsonObjectFree( list );
1643 * Fetch one row from a specified table, using a specified value
1644 * for the primary key
1646 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1656 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1658 const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos); // key value
1662 "%s retrieving %s object with primary key value of %s",
1664 osrfHashGet( class_def, "fieldmapper" ),
1665 jsonObjectGetString( id_obj )
1668 // Build a WHERE clause based on the key value
1669 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1672 osrfHashGet( class_def, "primarykey" ),
1673 jsonObjectClone( id_obj )
1676 jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
1678 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
1680 jsonObjectFree( where_clause );
1684 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
1685 jsonObjectFree( list );
1688 if(!verifyObjectPCRUD(ctx, obj)) {
1689 jsonObjectFree(obj);
1692 growing_buffer* msg = buffer_init(128);
1693 OSRF_BUFFER_ADD( msg, MODULENAME );
1694 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1696 char* m = buffer_release(msg);
1697 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1708 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1709 growing_buffer* val_buf = buffer_init(32);
1710 const char* numtype = get_datatype( field );
1712 if ( !strncmp( numtype, "INT", 3 ) ) {
1713 if (value->type == JSON_NUMBER)
1714 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1715 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1717 //const char* val_str = jsonObjectGetString( value );
1718 //buffer_fadd( val_buf, "%ld", atol(val_str) );
1719 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1722 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
1723 if (value->type == JSON_NUMBER)
1724 //buffer_fadd( val_buf, "%f", jsonObjectGetNumber(value) );
1725 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1727 //const char* val_str = jsonObjectGetString( value );
1728 //buffer_fadd( val_buf, "%f", atof(val_str) );
1729 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1733 // Presumably this was really intended ot be a string, so quote it
1734 char* str = jsonObjectToSimpleString( value );
1735 if ( dbi_conn_quote_string(dbhandle, &str) ) {
1736 OSRF_BUFFER_ADD( val_buf, str );
1739 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
1741 buffer_free(val_buf);
1746 return buffer_release(val_buf);
1749 static char* searchINPredicate (const char* class_alias, osrfHash* field,
1750 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1751 growing_buffer* sql_buf = buffer_init(32);
1757 osrfHashGet(field, "name")
1761 buffer_add(sql_buf, "IN (");
1762 } else if (!(strcasecmp(op,"not in"))) {
1763 buffer_add(sql_buf, "NOT IN (");
1765 buffer_add(sql_buf, "IN (");
1768 if (node->type == JSON_HASH) {
1769 // subquery predicate
1770 char* subpred = SELECT(
1772 jsonObjectGetKey( node, "select" ),
1773 jsonObjectGetKey( node, "from" ),
1774 jsonObjectGetKey( node, "where" ),
1775 jsonObjectGetKey( node, "having" ),
1776 jsonObjectGetKey( node, "order_by" ),
1777 jsonObjectGetKey( node, "limit" ),
1778 jsonObjectGetKey( node, "offset" ),
1784 buffer_add(sql_buf, subpred);
1787 buffer_free( sql_buf );
1791 } else if (node->type == JSON_ARRAY) {
1792 // literal value list
1793 int in_item_index = 0;
1794 int in_item_first = 1;
1795 const jsonObject* in_item;
1796 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1801 buffer_add(sql_buf, ", ");
1804 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1805 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1806 MODULENAME, json_type( in_item->type ) );
1807 buffer_free(sql_buf);
1811 // Append the literal value -- quoted if not a number
1812 if ( JSON_NUMBER == in_item->type ) {
1813 char* val = jsonNumberToDBString( field, in_item );
1814 OSRF_BUFFER_ADD( sql_buf, val );
1817 } else if ( !strcmp( get_primitive( field ), "number") ) {
1818 char* val = jsonNumberToDBString( field, in_item );
1819 OSRF_BUFFER_ADD( sql_buf, val );
1823 char* key_string = jsonObjectToSimpleString(in_item);
1824 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1825 OSRF_BUFFER_ADD( sql_buf, key_string );
1828 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1830 buffer_free(sql_buf);
1836 if( in_item_first ) {
1837 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1838 buffer_free( sql_buf );
1842 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1843 MODULENAME, json_type( node->type ) );
1844 buffer_free(sql_buf);
1848 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1850 return buffer_release(sql_buf);
1853 // Receive a JSON_ARRAY representing a function call. The first
1854 // entry in the array is the function name. The rest are parameters.
1855 static char* searchValueTransform( const jsonObject* array ) {
1857 if( array->size < 1 ) {
1858 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
1862 // Get the function name
1863 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
1864 if( func_item->type != JSON_STRING ) {
1865 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
1866 MODULENAME, json_type( func_item->type ) );
1870 growing_buffer* sql_buf = buffer_init(32);
1872 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1873 OSRF_BUFFER_ADD( sql_buf, "( " );
1875 // Get the parameters
1876 int func_item_index = 1; // We already grabbed the zeroth entry
1877 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1879 // Add a separator comma, if we need one
1880 if( func_item_index > 2 )
1881 buffer_add( sql_buf, ", " );
1883 // Add the current parameter
1884 if (func_item->type == JSON_NULL) {
1885 buffer_add( sql_buf, "NULL" );
1887 char* val = jsonObjectToSimpleString(func_item);
1888 if ( dbi_conn_quote_string(dbhandle, &val) ) {
1889 OSRF_BUFFER_ADD( sql_buf, val );
1892 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1893 buffer_free(sql_buf);
1900 buffer_add( sql_buf, " )" );
1902 return buffer_release(sql_buf);
1905 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
1906 const jsonObject* node, const char* op) {
1908 if( ! is_good_operator( op ) ) {
1909 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
1913 char* val = searchValueTransform(node);
1917 growing_buffer* sql_buf = buffer_init(32);
1922 osrfHashGet(field, "name"),
1929 return buffer_release(sql_buf);
1932 // class_alias is a class name or other table alias
1933 // field is a field definition as stored in the IDL
1934 // node comes from the method parameter, and may represent an entry in the SELECT list
1935 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
1936 growing_buffer* sql_buf = buffer_init(32);
1938 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
1939 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
1941 if(transform_subcolumn) {
1942 if( ! is_identifier( transform_subcolumn ) ) {
1943 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
1944 MODULENAME, transform_subcolumn );
1945 buffer_free( sql_buf );
1948 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
1951 if (field_transform) {
1953 if( ! is_identifier( field_transform ) ) {
1954 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
1955 MODULENAME, field_transform );
1956 buffer_free( sql_buf );
1960 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class_alias, osrfHashGet(field, "name"));
1961 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
1964 if( array->type != JSON_ARRAY ) {
1965 osrfLogError( OSRF_LOG_MARK,
1966 "%s: Expected JSON_ARRAY for function params; found %s",
1967 MODULENAME, json_type( array->type ) );
1968 buffer_free( sql_buf );
1971 int func_item_index = 0;
1972 jsonObject* func_item;
1973 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1975 char* val = jsonObjectToSimpleString(func_item);
1978 buffer_add( sql_buf, ",NULL" );
1979 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
1980 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
1981 OSRF_BUFFER_ADD( sql_buf, val );
1983 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1985 buffer_free(sql_buf);
1992 buffer_add( sql_buf, " )" );
1995 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
1998 if (transform_subcolumn)
1999 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2001 return buffer_release(sql_buf);
2004 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2005 const jsonObject* node, const char* op ) {
2007 if( ! is_good_operator( op ) ) {
2008 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2012 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2013 if( ! field_transform )
2016 int extra_parens = 0; // boolean
2018 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2019 if ( ! value_obj ) {
2020 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2022 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
2023 free(field_transform);
2027 } else if ( value_obj->type == JSON_ARRAY ) {
2028 value = searchValueTransform( value_obj );
2030 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
2031 free( field_transform );
2034 } else if ( value_obj->type == JSON_HASH ) {
2035 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2037 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
2038 free(field_transform);
2042 } else if ( value_obj->type == JSON_NUMBER ) {
2043 value = jsonNumberToDBString( field, value_obj );
2044 } else if ( value_obj->type == JSON_NULL ) {
2045 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
2046 free(field_transform);
2048 } else if ( value_obj->type == JSON_BOOL ) {
2049 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
2050 free(field_transform);
2053 if ( !strcmp( get_primitive( field ), "number") ) {
2054 value = jsonNumberToDBString( field, value_obj );
2056 value = jsonObjectToSimpleString( value_obj );
2057 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2058 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
2060 free(field_transform);
2066 const char* left_parens = "";
2067 const char* right_parens = "";
2069 if( extra_parens ) {
2074 growing_buffer* sql_buf = buffer_init(32);
2078 "%s%s %s %s %s %s%s",
2089 free(field_transform);
2091 return buffer_release(sql_buf);
2094 static char* searchSimplePredicate (const char* op, const char* class_alias,
2095 osrfHash* field, const jsonObject* node) {
2097 if( ! is_good_operator( op ) ) {
2098 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2104 // Get the value to which we are comparing the specified column
2105 if (node->type != JSON_NULL) {
2106 if ( node->type == JSON_NUMBER ) {
2107 val = jsonNumberToDBString( field, node );
2108 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2109 val = jsonNumberToDBString( field, node );
2111 val = jsonObjectToSimpleString(node);
2116 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2117 // Value is not numeric; enclose it in quotes
2118 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2119 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2125 // Compare to a null value
2126 val = strdup( "NULL" );
2127 if (strcmp( op, "=" ))
2133 growing_buffer* sql_buf = buffer_init(32);
2134 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2135 char* pred = buffer_release( sql_buf );
2142 static char* searchBETWEENPredicate (const char* class_alias,
2143 osrfHash* field, const jsonObject* node) {
2145 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2146 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2148 if( NULL == y_node ) {
2149 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2152 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2153 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2160 if ( !strcmp( get_primitive( field ), "number") ) {
2161 x_string = jsonNumberToDBString(field, x_node);
2162 y_string = jsonNumberToDBString(field, y_node);
2165 x_string = jsonObjectToSimpleString(x_node);
2166 y_string = jsonObjectToSimpleString(y_node);
2167 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2168 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2169 MODULENAME, x_string, y_string);
2176 growing_buffer* sql_buf = buffer_init(32);
2177 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2178 class_alias, osrfHashGet(field, "name"), x_string, y_string );
2182 return buffer_release(sql_buf);
2185 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2186 jsonObject* node, osrfMethodContext* ctx ) {
2189 if (node->type == JSON_ARRAY) { // equality IN search
2190 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2191 } else if (node->type == JSON_HASH) { // other search
2192 jsonIterator* pred_itr = jsonNewIterator( node );
2193 if( !jsonIteratorHasNext( pred_itr ) ) {
2194 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2195 MODULENAME, osrfHashGet(field, "name") );
2197 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2199 // Verify that there are no additional predicates
2200 if( jsonIteratorHasNext( pred_itr ) ) {
2201 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2202 MODULENAME, osrfHashGet(field, "name") );
2203 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2204 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2205 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2206 pred = searchINPredicate( class_info->alias, field, pred_node, pred_itr->key, ctx );
2207 else if ( pred_node->type == JSON_ARRAY )
2208 pred = searchFunctionPredicate( class_info->alias, field, pred_node, pred_itr->key );
2209 else if ( pred_node->type == JSON_HASH )
2210 pred = searchFieldTransformPredicate( class_info, field, pred_node, pred_itr->key );
2212 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2214 jsonIteratorFree(pred_itr);
2216 } else if (node->type == JSON_NULL) { // IS NULL search
2217 growing_buffer* _p = buffer_init(64);
2220 "\"%s\".%s IS NULL",
2221 class_info->class_name,
2222 osrfHashGet(field, "name")
2224 pred = buffer_release(_p);
2225 } else { // equality search
2226 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2245 field : call_number,
2261 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2263 const jsonObject* working_hash;
2264 jsonObject* freeable_hash = NULL;
2266 if (join_hash->type == JSON_HASH) {
2267 working_hash = join_hash;
2268 } else if (join_hash->type == JSON_STRING) {
2269 // turn it into a JSON_HASH by creating a wrapper
2270 // around a copy of the original
2271 const char* _tmp = jsonObjectGetString( join_hash );
2272 freeable_hash = jsonNewObjectType(JSON_HASH);
2273 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2274 working_hash = freeable_hash;
2278 "%s: JOIN failed; expected JSON object type not found",
2284 growing_buffer* join_buf = buffer_init(128);
2285 const char* leftclass = left_info->class_name;
2287 jsonObject* snode = NULL;
2288 jsonIterator* search_itr = jsonNewIterator( working_hash );
2290 while ( (snode = jsonIteratorNext( search_itr )) ) {
2291 const char* right_alias = search_itr->key;
2293 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2295 class = right_alias;
2297 const ClassInfo* right_info = add_joined_class( right_alias, class );
2301 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2305 jsonIteratorFree( search_itr );
2306 buffer_free( join_buf );
2308 jsonObjectFree( freeable_hash );
2311 osrfHash* links = right_info->links;
2312 const char* table = right_info->source_def;
2314 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2315 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2317 if (field && !fkey) {
2318 // Look up the corresponding join column in the IDL.
2319 // The link must be defined in the child table,
2320 // and point to the right parent table.
2321 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2322 const char* reltype = NULL;
2323 const char* other_class = NULL;
2324 reltype = osrfHashGet( idl_link, "reltype" );
2325 if( reltype && strcmp( reltype, "has_many" ) )
2326 other_class = osrfHashGet( idl_link, "class" );
2327 if( other_class && !strcmp( other_class, leftclass ) )
2328 fkey = osrfHashGet( idl_link, "key" );
2332 "%s: JOIN failed. No link defined from %s.%s to %s",
2338 buffer_free(join_buf);
2340 jsonObjectFree(freeable_hash);
2341 jsonIteratorFree(search_itr);
2345 } else if (!field && fkey) {
2346 // Look up the corresponding join column in the IDL.
2347 // The link must be defined in the child table,
2348 // and point to the right parent table.
2349 osrfHash* left_links = left_info->links;
2350 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2351 const char* reltype = NULL;
2352 const char* other_class = NULL;
2353 reltype = osrfHashGet( idl_link, "reltype" );
2354 if( reltype && strcmp( reltype, "has_many" ) )
2355 other_class = osrfHashGet( idl_link, "class" );
2356 if( other_class && !strcmp( other_class, class ) )
2357 field = osrfHashGet( idl_link, "key" );
2361 "%s: JOIN failed. No link defined from %s.%s to %s",
2367 buffer_free(join_buf);
2369 jsonObjectFree(freeable_hash);
2370 jsonIteratorFree(search_itr);
2374 } else if (!field && !fkey) {
2375 osrfHash* left_links = left_info->links;
2377 // For each link defined for the left class:
2378 // see if the link references the joined class
2379 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2380 osrfHash* curr_link = NULL;
2381 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2382 const char* other_class = osrfHashGet( curr_link, "class" );
2383 if( other_class && !strcmp( other_class, class ) ) {
2385 // In the IDL, the parent class doesn't know then names of the child
2386 // columns that are pointing to it, so don't use that end of the link
2387 const char* reltype = osrfHashGet( curr_link, "reltype" );
2388 if( reltype && strcmp( reltype, "has_many" ) ) {
2389 // Found a link between the classes
2390 fkey = osrfHashIteratorKey( itr );
2391 field = osrfHashGet( curr_link, "key" );
2396 osrfHashIteratorFree( itr );
2398 if (!field || !fkey) {
2399 // Do another such search, with the classes reversed
2401 // For each link defined for the joined class:
2402 // see if the link references the left class
2403 osrfHashIterator* itr = osrfNewHashIterator( links );
2404 osrfHash* curr_link = NULL;
2405 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2406 const char* other_class = osrfHashGet( curr_link, "class" );
2407 if( other_class && !strcmp( other_class, leftclass ) ) {
2409 // In the IDL, the parent class doesn't know then names of the child
2410 // columns that are pointing to it, so don't use that end of the link
2411 const char* reltype = osrfHashGet( curr_link, "reltype" );
2412 if( reltype && strcmp( reltype, "has_many" ) ) {
2413 // Found a link between the classes
2414 field = osrfHashIteratorKey( itr );
2415 fkey = osrfHashGet( curr_link, "key" );
2420 osrfHashIteratorFree( itr );
2423 if (!field || !fkey) {
2426 "%s: JOIN failed. No link defined between %s and %s",
2431 buffer_free(join_buf);
2433 jsonObjectFree(freeable_hash);
2434 jsonIteratorFree(search_itr);
2440 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2442 if ( !strcasecmp(type,"left") ) {
2443 buffer_add(join_buf, " LEFT JOIN");
2444 } else if ( !strcasecmp(type,"right") ) {
2445 buffer_add(join_buf, " RIGHT JOIN");
2446 } else if ( !strcasecmp(type,"full") ) {
2447 buffer_add(join_buf, " FULL JOIN");
2449 buffer_add(join_buf, " INNER JOIN");
2452 buffer_add(join_buf, " INNER JOIN");
2455 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2456 table, right_alias, right_alias, field, left_info->alias, fkey);
2458 // Add any other join conditions as specified by "filter"
2459 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2461 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2462 if ( filter_op && !strcasecmp("or",filter_op) ) {
2463 buffer_add( join_buf, " OR " );
2465 buffer_add( join_buf, " AND " );
2468 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2470 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2471 OSRF_BUFFER_ADD( join_buf, jpred );
2476 "%s: JOIN failed. Invalid conditional expression.",
2479 jsonIteratorFree( search_itr );
2480 buffer_free( join_buf );
2482 jsonObjectFree( freeable_hash );
2487 buffer_add(join_buf, " ) ");
2489 // Recursively add a nested join, if one is present
2490 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2492 char* jpred = searchJOIN( join_filter, right_info );
2494 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2495 OSRF_BUFFER_ADD( join_buf, jpred );
2498 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2499 jsonIteratorFree( search_itr );
2500 buffer_free( join_buf );
2502 jsonObjectFree( freeable_hash );
2509 jsonObjectFree(freeable_hash);
2510 jsonIteratorFree(search_itr);
2512 return buffer_release(join_buf);
2517 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2518 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2519 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2521 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2523 search_hash is the JSON expression of the conditions.
2524 meta is the class definition from the IDL, for the relevant table.
2525 opjoin_type indicates whether multiple conditions, if present, should be
2526 connected by AND or OR.
2527 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2528 to pass it to other functions -- and all they do with it is to use the session
2529 and request members to send error messages back to the client.
2533 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
2534 int opjoin_type, osrfMethodContext* ctx ) {
2538 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2541 class_info->class_def,
2546 growing_buffer* sql_buf = buffer_init(128);
2548 jsonObject* node = NULL;
2551 if ( search_hash->type == JSON_ARRAY ) {
2552 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2553 if( 0 == search_hash->size ) {
2556 "%s: Invalid predicate structure: empty JSON array",
2559 buffer_free( sql_buf );
2563 unsigned long i = 0;
2564 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
2568 if (opjoin_type == OR_OP_JOIN)
2569 buffer_add(sql_buf, " OR ");
2571 buffer_add(sql_buf, " AND ");
2574 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2576 buffer_free( sql_buf );
2580 buffer_fadd(sql_buf, "( %s )", subpred);
2584 } else if ( search_hash->type == JSON_HASH ) {
2585 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2586 jsonIterator* search_itr = jsonNewIterator( search_hash );
2587 if( !jsonIteratorHasNext( search_itr ) ) {
2590 "%s: Invalid predicate structure: empty JSON object",
2593 jsonIteratorFree( search_itr );
2594 buffer_free( sql_buf );
2598 while ( (node = jsonIteratorNext( search_itr )) ) {
2603 if (opjoin_type == OR_OP_JOIN)
2604 buffer_add(sql_buf, " OR ");
2606 buffer_add(sql_buf, " AND ");
2609 if ( '+' == search_itr->key[ 0 ] ) {
2611 // This plus sign prefixes a class name or other table alias;
2612 // make sure the table alias is in scope
2613 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
2614 if( ! alias_info ) {
2617 "%s: Invalid table alias \"%s\" in WHERE clause",
2621 jsonIteratorFree( search_itr );
2622 buffer_free( sql_buf );
2626 if ( node->type == JSON_STRING ) {
2627 // It's the name of a column; make sure it belongs to the class
2628 const char* fieldname = jsonObjectGetString( node );
2629 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
2632 "%s: Invalid column name \"%s\" in WHERE clause for table alias \"%s\"",
2637 jsonIteratorFree( search_itr );
2638 buffer_free( sql_buf );
2642 buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
2644 // It's something more complicated
2645 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
2647 jsonIteratorFree( search_itr );
2648 buffer_free( sql_buf );
2652 buffer_fadd(sql_buf, "( %s )", subpred);
2655 } else if ( '-' == search_itr->key[ 0 ] ) {
2656 if ( !strcasecmp("-or",search_itr->key) ) {
2657 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
2659 jsonIteratorFree( search_itr );
2660 buffer_free( sql_buf );
2664 buffer_fadd(sql_buf, "( %s )", subpred);
2666 } else if ( !strcasecmp("-and",search_itr->key) ) {
2667 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2669 jsonIteratorFree( search_itr );
2670 buffer_free( sql_buf );
2674 buffer_fadd(sql_buf, "( %s )", subpred);
2676 } else if ( !strcasecmp("-not",search_itr->key) ) {
2677 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2679 jsonIteratorFree( search_itr );
2680 buffer_free( sql_buf );
2684 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2686 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2687 char* subpred = SELECT(
2689 jsonObjectGetKey( node, "select" ),
2690 jsonObjectGetKey( node, "from" ),
2691 jsonObjectGetKey( node, "where" ),
2692 jsonObjectGetKey( node, "having" ),
2693 jsonObjectGetKey( node, "order_by" ),
2694 jsonObjectGetKey( node, "limit" ),
2695 jsonObjectGetKey( node, "offset" ),
2701 jsonIteratorFree( search_itr );
2702 buffer_free( sql_buf );
2706 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2708 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2709 char* subpred = SELECT(
2711 jsonObjectGetKey( node, "select" ),
2712 jsonObjectGetKey( node, "from" ),
2713 jsonObjectGetKey( node, "where" ),
2714 jsonObjectGetKey( node, "having" ),
2715 jsonObjectGetKey( node, "order_by" ),
2716 jsonObjectGetKey( node, "limit" ),
2717 jsonObjectGetKey( node, "offset" ),
2723 jsonIteratorFree( search_itr );
2724 buffer_free( sql_buf );
2728 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2730 } else { // Invalid "minus" operator
2733 "%s: Invalid operator \"%s\" in WHERE clause",
2737 jsonIteratorFree( search_itr );
2738 buffer_free( sql_buf );
2744 const char* class = class_info->class_name;
2745 osrfHash* fields = class_info->fields;
2746 osrfHash* field = osrfHashGet( fields, search_itr->key );
2749 const char* table = class_info->source_def;
2752 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2755 table ? table : "?",
2758 jsonIteratorFree(search_itr);
2759 buffer_free(sql_buf);
2763 char* subpred = searchPredicate( class_info, field, node, ctx );
2765 buffer_free(sql_buf);
2766 jsonIteratorFree(search_itr);
2770 buffer_add( sql_buf, subpred );
2774 jsonIteratorFree(search_itr);
2777 // ERROR ... only hash and array allowed at this level
2778 char* predicate_string = jsonObjectToJSON( search_hash );
2781 "%s: Invalid predicate structure: %s",
2785 buffer_free(sql_buf);
2786 free(predicate_string);
2790 return buffer_release(sql_buf);
2793 /* Build a JSON_ARRAY of field names for a given table alias
2795 static jsonObject* defaultSelectList( const char* table_alias ) {
2800 ClassInfo* class_info = search_all_alias( table_alias );
2801 if( ! class_info ) {
2804 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
2811 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
2812 osrfHash* field_def = NULL;
2813 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
2814 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2815 const char* field_name = osrfHashIteratorKey( field_itr );
2816 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2817 jsonObjectPush( array, jsonNewObject( field_name ) );
2820 osrfHashIteratorFree( field_itr );
2826 /* method context */ osrfMethodContext* ctx,
2828 /* SELECT */ jsonObject* selhash,
2829 /* FROM */ jsonObject* join_hash,
2830 /* WHERE */ jsonObject* search_hash,
2831 /* HAVING */ jsonObject* having_hash,
2832 /* ORDER BY */ jsonObject* order_hash,
2833 /* LIMIT */ jsonObject* limit,
2834 /* OFFSET */ jsonObject* offset,
2835 /* flags */ int flags
2837 const char* locale = osrf_message_get_last_locale();
2839 // general tmp objects
2840 const jsonObject* tmp_const;
2841 jsonObject* selclass = NULL;
2842 jsonObject* selfield = NULL;
2843 jsonObject* snode = NULL;
2844 jsonObject* onode = NULL;
2846 char* string = NULL;
2847 int from_function = 0;
2852 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale);
2854 // punt if there's no FROM clause
2855 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2858 "%s: FROM clause is missing or empty",
2862 osrfAppSessionStatus(
2864 OSRF_STATUS_INTERNALSERVERERROR,
2865 "osrfMethodException",
2867 "FROM clause is missing or empty in JSON query"
2872 // Push a node onto the stack for the current query. Every level of
2873 // subquery gets its own QueryFrame on the Stack.
2876 // the core search class
2877 const char* core_class = NULL;
2879 // get the core class -- the only key of the top level FROM clause, or a string
2880 if (join_hash->type == JSON_HASH) {
2881 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2882 snode = jsonIteratorNext( tmp_itr );
2884 // Populate the current QueryFrame with information
2885 // about the core class
2886 if( add_query_core( NULL, tmp_itr->key ) ) {
2888 osrfAppSessionStatus(
2890 OSRF_STATUS_INTERNALSERVERERROR,
2891 "osrfMethodException",
2893 "Unable to look up core class"
2897 core_class = curr_query->core.class_name;
2900 jsonObject* extra = jsonIteratorNext( tmp_itr );
2902 jsonIteratorFree( tmp_itr );
2905 // There shouldn't be more than one entry in join_hash
2909 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2913 osrfAppSessionStatus(
2915 OSRF_STATUS_INTERNALSERVERERROR,
2916 "osrfMethodException",
2918 "Malformed FROM clause in JSON query"
2920 return NULL; // Malformed join_hash; extra entry
2922 } else if (join_hash->type == JSON_ARRAY) {
2923 // We're selecting from a function, not from a table
2925 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
2928 } else if (join_hash->type == JSON_STRING) {
2929 // Populate the current QueryFrame with information
2930 // about the core class
2931 core_class = jsonObjectGetString( join_hash );
2933 if( add_query_core( NULL, core_class ) ) {
2935 osrfAppSessionStatus(
2937 OSRF_STATUS_INTERNALSERVERERROR,
2938 "osrfMethodException",
2940 "Unable to look up core class"
2948 "%s: FROM clause is unexpected JSON type: %s",
2950 json_type( join_hash->type )
2953 osrfAppSessionStatus(
2955 OSRF_STATUS_INTERNALSERVERERROR,
2956 "osrfMethodException",
2958 "Ill-formed FROM clause in JSON query"
2963 // Build the join clause, if any, while filling out the list
2964 // of joined classes in the current QueryFrame.
2965 char* join_clause = NULL;
2966 if( join_hash && ! from_function ) {
2968 join_clause = searchJOIN( join_hash, &curr_query->core );
2969 if( ! join_clause ) {
2971 osrfAppSessionStatus(
2973 OSRF_STATUS_INTERNALSERVERERROR,
2974 "osrfMethodException",
2976 "Unable to construct JOIN clause(s)"
2982 // For in case we don't get a select list
2983 jsonObject* defaultselhash = NULL;
2985 // if there is no select list, build a default select list ...
2987 jsonObject* default_list = defaultSelectList( core_class );
2988 if( ! default_list ) {
2990 osrfAppSessionStatus(
2992 OSRF_STATUS_INTERNALSERVERERROR,
2993 "osrfMethodException",
2995 "Unable to build default SELECT clause in JSON query"
2997 free( join_clause );
3002 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
3003 jsonObjectSetKey( selhash, core_class, default_list );
3006 // The SELECT clause can be encoded only by a hash
3007 if( selhash->type != JSON_HASH ) {
3010 "%s: Expected JSON_HASH for SELECT clause; found %s",
3012 json_type( selhash->type )
3016 osrfAppSessionStatus(
3018 OSRF_STATUS_INTERNALSERVERERROR,
3019 "osrfMethodException",
3021 "Malformed SELECT clause in JSON query"
3023 free( join_clause );
3027 // If you see a null or wild card specifier for the core class, or an
3028 // empty array, replace it with a default SELECT list
3029 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3031 int default_needed = 0; // boolean
3032 if( JSON_STRING == tmp_const->type
3033 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3035 else if( JSON_NULL == tmp_const->type )
3037 else if( JSON_ARRAY == tmp_const->type && 0 == tmp_const->size )
3040 if( default_needed ) {
3041 // Build a default SELECT list
3042 jsonObject* default_list = defaultSelectList( core_class );
3043 if( ! default_list ) {
3045 osrfAppSessionStatus(
3047 OSRF_STATUS_INTERNALSERVERERROR,
3048 "osrfMethodException",
3050 "Can't build default SELECT clause in JSON query"
3052 free( join_clause );
3057 jsonObjectSetKey( selhash, core_class, default_list );
3061 // temp buffers for the SELECT list and GROUP BY clause
3062 growing_buffer* select_buf = buffer_init(128);
3063 growing_buffer* group_buf = buffer_init(128);
3065 int aggregate_found = 0; // boolean
3067 // Build a select list
3068 if(from_function) // From a function we select everything
3069 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3072 // Build the SELECT list as SQL
3076 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3077 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3079 const char* cname = selclass_itr->key;
3081 // Make sure the target relation is in the FROM clause.
3083 // At this point join_hash is a step down from the join_hash we
3084 // received as a parameter. If the original was a JSON_STRING,
3085 // then json_hash is now NULL. If the original was a JSON_HASH,
3086 // then json_hash is now the first (and only) entry in it,
3087 // denoting the core class. We've already excluded the
3088 // possibility that the original was a JSON_ARRAY, because in
3089 // that case from_function would be non-NULL, and we wouldn't
3092 // If the current class isn't the core class
3093 // and it isn't in the join tree, bail out
3094 ClassInfo* class_info = search_alias( cname );
3095 if( ! class_info ) {
3098 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3103 osrfAppSessionStatus(
3105 OSRF_STATUS_INTERNALSERVERERROR,
3106 "osrfMethodException",
3108 "Selected class not in FROM clause in JSON query"
3110 jsonIteratorFree( selclass_itr );
3111 buffer_free( select_buf );
3112 buffer_free( group_buf );
3113 if( defaultselhash ) jsonObjectFree( defaultselhash );
3114 free( join_clause );
3118 if( selclass->type != JSON_ARRAY ) {
3121 "%s: Malformed SELECT list for class \"%s\"; not an array",
3126 osrfAppSessionStatus(
3128 OSRF_STATUS_INTERNALSERVERERROR,
3129 "osrfMethodException",
3131 "Selected class not in FROM clause in JSON query"
3134 jsonIteratorFree( selclass_itr );
3135 buffer_free( select_buf );
3136 buffer_free( group_buf );
3137 if( defaultselhash ) jsonObjectFree( defaultselhash );
3138 free( join_clause );
3142 // Look up some attributes of the current class
3143 osrfHash* idlClass = class_info->class_def;
3144 osrfHash* class_field_set = class_info->fields;
3145 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3146 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3148 if( 0 == selclass->size ) {
3151 "%s: No columns selected from \"%s\"",
3157 // stitch together the column list for the current table alias...
3158 jsonIterator* select_itr = jsonNewIterator( selclass );
3159 while ( (selfield = jsonIteratorNext( select_itr )) ) { // for each SELECT column
3161 // If we need a separator comma, add one
3165 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3168 // ... if it's a string, just toss it on the pile
3169 if (selfield->type == JSON_STRING) {
3171 // Look up the field in the IDL
3172 const char* col_name = jsonObjectGetString( selfield );
3173 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3175 // No such field in current class
3178 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3184 osrfAppSessionStatus(
3186 OSRF_STATUS_INTERNALSERVERERROR,
3187 "osrfMethodException",
3189 "Selected column not defined in JSON query"
3191 jsonIteratorFree( select_itr );
3192 jsonIteratorFree( selclass_itr );
3193 buffer_free( select_buf );
3194 buffer_free( group_buf );
3195 if( defaultselhash ) jsonObjectFree( defaultselhash );
3196 free( join_clause );
3198 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3199 // Virtual field not allowed
3202 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3208 osrfAppSessionStatus(
3210 OSRF_STATUS_INTERNALSERVERERROR,
3211 "osrfMethodException",
3213 "Selected column may not be virtual in JSON query"
3215 jsonIteratorFree( select_itr );
3216 jsonIteratorFree( selclass_itr );
3217 buffer_free( select_buf );
3218 buffer_free( group_buf );
3219 if( defaultselhash ) jsonObjectFree( defaultselhash );
3220 free( join_clause );
3226 if (flags & DISABLE_I18N)
3229 i18n = osrfHashGet(field_def, "i18n");
3231 if( str_is_true( i18n ) ) {
3232 buffer_fadd( select_buf,
3233 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3234 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3236 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3239 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3242 // ... but it could be an object, in which case we check for a Field Transform
3243 } else if (selfield->type == JSON_HASH) {
3245 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3247 // Get the field definition from the IDL
3248 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3250 // No such field in current class
3253 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3259 osrfAppSessionStatus(
3261 OSRF_STATUS_INTERNALSERVERERROR,
3262 "osrfMethodException",
3264 "Selected column is not defined in JSON query"
3266 jsonIteratorFree( select_itr );
3267 jsonIteratorFree( selclass_itr );
3268 buffer_free( select_buf );
3269 buffer_free( group_buf );
3270 if( defaultselhash ) jsonObjectFree( defaultselhash );
3271 free( join_clause );
3273 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3274 // No such field in current class
3277 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3283 osrfAppSessionStatus(
3285 OSRF_STATUS_INTERNALSERVERERROR,
3286 "osrfMethodException",
3288 "Selected column is virtual in JSON query"
3290 jsonIteratorFree( select_itr );
3291 jsonIteratorFree( selclass_itr );
3292 buffer_free( select_buf );
3293 buffer_free( group_buf );
3294 if( defaultselhash ) jsonObjectFree( defaultselhash );
3295 free( join_clause );
3299 // Decide what to use as a column alias
3301 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3302 _alias = jsonObjectGetString( tmp_const );
3303 } else { // Use field name as the alias
3307 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3308 char* transform_str = searchFieldTransform(class_info->alias, field_def, selfield);
3309 if( transform_str ) {
3310 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3311 free(transform_str);
3314 osrfAppSessionStatus(
3316 OSRF_STATUS_INTERNALSERVERERROR,
3317 "osrfMethodException",
3319 "Unable to generate transform function in JSON query"
3321 jsonIteratorFree( select_itr );
3322 jsonIteratorFree( selclass_itr );
3323 buffer_free( select_buf );
3324 buffer_free( group_buf );
3325 if( defaultselhash ) jsonObjectFree( defaultselhash );
3326 free( join_clause );
3333 if (flags & DISABLE_I18N)
3336 i18n = osrfHashGet(field_def, "i18n");
3338 if( str_is_true( i18n ) ) {
3339 buffer_fadd( select_buf,
3340 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3341 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3343 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3346 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3353 "%s: Selected item is unexpected JSON type: %s",
3355 json_type( selfield->type )
3358 osrfAppSessionStatus(
3360 OSRF_STATUS_INTERNALSERVERERROR,
3361 "osrfMethodException",
3363 "Ill-formed SELECT item in JSON query"
3365 jsonIteratorFree( select_itr );
3366 jsonIteratorFree( selclass_itr );
3367 buffer_free( select_buf );
3368 buffer_free( group_buf );
3369 if( defaultselhash ) jsonObjectFree( defaultselhash );
3370 free( join_clause );
3374 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3375 if( obj_is_true( agg_obj ) )
3376 aggregate_found = 1;
3378 // Append a comma (except for the first one)
3379 // and add the column to a GROUP BY clause
3383 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3385 buffer_fadd(group_buf, " %d", sel_pos);
3389 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3391 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3392 if ( ! obj_is_true( aggregate_obj ) ) {
3396 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3399 buffer_fadd(group_buf, " %d", sel_pos);
3402 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3406 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3409 _column = searchFieldTransform(class_info->alias, field, selfield);
3410 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3411 OSRF_BUFFER_ADD(group_buf, _column);
3412 _column = searchFieldTransform(class_info->alias, field, selfield);
3419 } // end while -- iterating across SELECT columns
3421 jsonIteratorFree(select_itr);
3422 } // end while -- iterating across classes
3424 jsonIteratorFree(selclass_itr);
3428 char* col_list = buffer_release(select_buf);
3430 // Make sure the SELECT list isn't empty. This can happen, for example,
3431 // if we try to build a default SELECT clause from a non-core table.
3434 osrfLogError(OSRF_LOG_MARK, "%s: SELECT clause is empty", MODULENAME );
3436 osrfAppSessionStatus(
3438 OSRF_STATUS_INTERNALSERVERERROR,
3439 "osrfMethodException",
3441 "SELECT list is empty"
3444 buffer_free( group_buf );
3445 if( defaultselhash ) jsonObjectFree( defaultselhash );
3446 free( join_clause );
3451 if (from_function) table = searchValueTransform(join_hash);
3452 else table = strdup( curr_query->core.source_def );
3456 osrfAppSessionStatus(
3458 OSRF_STATUS_INTERNALSERVERERROR,
3459 "osrfMethodException",
3461 "Unable to identify table for core class"
3464 buffer_free( group_buf );
3465 if( defaultselhash ) jsonObjectFree( defaultselhash );
3466 free( join_clause );
3470 // Put it all together
3471 growing_buffer* sql_buf = buffer_init(128);
3472 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3476 // Append the join clause, if any
3478 buffer_add(sql_buf, join_clause);
3482 char* order_by_list = NULL;
3483 char* having_buf = NULL;
3485 if (!from_function) {
3487 // Build a WHERE clause, if there is one
3488 if ( search_hash ) {
3489 buffer_add(sql_buf, " WHERE ");
3491 // and it's on the WHERE clause
3492 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
3495 osrfAppSessionStatus(
3497 OSRF_STATUS_INTERNALSERVERERROR,
3498 "osrfMethodException",
3500 "Severe query error in WHERE predicate -- see error log for more details"
3503 buffer_free(group_buf);
3504 buffer_free(sql_buf);
3505 if (defaultselhash) jsonObjectFree(defaultselhash);
3509 buffer_add(sql_buf, pred);
3513 // Build a HAVING clause, if there is one
3514 if ( having_hash ) {
3516 // and it's on the the WHERE clause
3517 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
3519 if( ! having_buf ) {
3521 osrfAppSessionStatus(
3523 OSRF_STATUS_INTERNALSERVERERROR,
3524 "osrfMethodException",
3526 "Severe query error in HAVING predicate -- see error log for more details"
3529 buffer_free(group_buf);
3530 buffer_free(sql_buf);
3531 if (defaultselhash) jsonObjectFree(defaultselhash);
3536 growing_buffer* order_buf = NULL; // to collect ORDER BY list
3538 // Build an ORDER BY clause, if there is one
3539 if( NULL == order_hash )
3540 ; // No ORDER BY? do nothing
3541 else if( JSON_ARRAY == order_hash->type ) {
3542 // Array of field specifications, each specification being a
3543 // hash to define the class, field, and other details
3545 jsonObject* order_spec;
3546 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
3548 if( JSON_HASH != order_spec->type ) {
3549 osrfLogError(OSRF_LOG_MARK,
3550 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
3551 MODULENAME, json_type( order_spec->type ) );
3553 osrfAppSessionStatus(
3555 OSRF_STATUS_INTERNALSERVERERROR,
3556 "osrfMethodException",
3558 "Malformed ORDER BY clause -- see error log for more details"
3560 buffer_free( order_buf );
3562 buffer_free(group_buf);
3563 buffer_free(sql_buf);
3564 if (defaultselhash) jsonObjectFree(defaultselhash);
3568 const char* class_alias =
3569 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
3571 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
3574 OSRF_BUFFER_ADD(order_buf, ", ");
3576 order_buf = buffer_init(128);
3578 if( !field || !class_alias ) {
3579 osrfLogError(OSRF_LOG_MARK,
3580 "%s: Missing class or field name in field specification of ORDER BY clause",
3583 osrfAppSessionStatus(
3585 OSRF_STATUS_INTERNALSERVERERROR,
3586 "osrfMethodException",
3588 "Malformed ORDER BY clause -- see error log for more details"
3590 buffer_free( order_buf );
3592 buffer_free(group_buf);
3593 buffer_free(sql_buf);
3594 if (defaultselhash) jsonObjectFree(defaultselhash);
3598 ClassInfo* order_class_info = search_alias( class_alias );
3599 if( ! order_class_info ) {
3600 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
3601 "not in FROM clause", MODULENAME, class_alias );
3603 osrfAppSessionStatus(
3605 OSRF_STATUS_INTERNALSERVERERROR,
3606 "osrfMethodException",
3608 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3611 buffer_free(group_buf);
3612 buffer_free(sql_buf);
3613 if (defaultselhash) jsonObjectFree(defaultselhash);
3617 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
3619 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
3620 MODULENAME, class_alias, field );
3622 osrfAppSessionStatus(
3624 OSRF_STATUS_INTERNALSERVERERROR,
3625 "osrfMethodException",
3627 "Invalid field referenced in ORDER BY clause -- see error log for more details"
3630 buffer_free(group_buf);
3631 buffer_free(sql_buf);
3632 if (defaultselhash) jsonObjectFree(defaultselhash);
3634 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3635 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3636 MODULENAME, field );
3638 osrfAppSessionStatus(
3640 OSRF_STATUS_INTERNALSERVERERROR,
3641 "osrfMethodException",
3643 "Virtual field in ORDER BY clause -- see error log for more details"
3645 buffer_free( order_buf );
3647 buffer_free(group_buf);
3648 buffer_free(sql_buf);
3649 if (defaultselhash) jsonObjectFree(defaultselhash);
3653 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
3654 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
3655 if( ! transform_str ) {
3657 osrfAppSessionStatus(
3659 OSRF_STATUS_INTERNALSERVERERROR,
3660 "osrfMethodException",
3662 "Severe query error in ORDER BY clause -- see error log for more details"
3664 buffer_free( order_buf );
3666 buffer_free(group_buf);
3667 buffer_free(sql_buf);
3668 if (defaultselhash) jsonObjectFree(defaultselhash);
3672 OSRF_BUFFER_ADD( order_buf, transform_str );
3673 free( transform_str );
3676 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
3678 const char* direction =
3679 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
3681 if( direction[ 0 ] || 'D' == direction[ 0 ] )
3682 OSRF_BUFFER_ADD( order_buf, " DESC" );
3684 OSRF_BUFFER_ADD( order_buf, " ASC" );
3687 } else if( JSON_HASH == order_hash->type ) {
3688 // This hash is keyed on class alias. Each class has either
3689 // an array of field names or a hash keyed on field name.
3690 jsonIterator* class_itr = jsonNewIterator( order_hash );
3691 while ( (snode = jsonIteratorNext( class_itr )) ) {
3693 ClassInfo* order_class_info = search_alias( class_itr->key );
3694 if( ! order_class_info ) {
3695 osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
3696 MODULENAME, class_itr->key );
3698 osrfAppSessionStatus(
3700 OSRF_STATUS_INTERNALSERVERERROR,
3701 "osrfMethodException",
3703 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3705 jsonIteratorFree( class_itr );
3706 buffer_free( order_buf );
3708 buffer_free(group_buf);
3709 buffer_free(sql_buf);
3710 if (defaultselhash) jsonObjectFree(defaultselhash);
3714 osrfHash* field_list_def = order_class_info->fields;
3716 if ( snode->type == JSON_HASH ) {
3718 // Hash is keyed on field names from the current class. For each field
3719 // there is another layer of hash to define the sorting details, if any,
3720 // or a string to indicate direction of sorting.
3721 jsonIterator* order_itr = jsonNewIterator( snode );
3722 while ( (onode = jsonIteratorNext( order_itr )) ) {
3724 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
3726 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3727 MODULENAME, order_itr->key );
3729 osrfAppSessionStatus(
3731 OSRF_STATUS_INTERNALSERVERERROR,
3732 "osrfMethodException",
3734 "Invalid field in ORDER BY clause -- see error log for more details"
3736 jsonIteratorFree( order_itr );
3737 jsonIteratorFree( class_itr );
3738 buffer_free( order_buf );
3740 buffer_free(group_buf);
3741 buffer_free(sql_buf);
3742 if (defaultselhash) jsonObjectFree(defaultselhash);
3744 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3745 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3746 MODULENAME, order_itr->key );
3748 osrfAppSessionStatus(
3750 OSRF_STATUS_INTERNALSERVERERROR,
3751 "osrfMethodException",
3753 "Virtual field in ORDER BY clause -- see error log for more details"
3755 jsonIteratorFree( order_itr );
3756 jsonIteratorFree( class_itr );
3757 buffer_free( order_buf );
3759 buffer_free(group_buf);
3760 buffer_free(sql_buf);
3761 if (defaultselhash) jsonObjectFree(defaultselhash);
3765 const char* direction = NULL;
3766 if ( onode->type == JSON_HASH ) {
3767 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3768 string = searchFieldTransform(
3770 osrfHashGet( field_list_def, order_itr->key ),
3774 if( ctx ) osrfAppSessionStatus(
3776 OSRF_STATUS_INTERNALSERVERERROR,
3777 "osrfMethodException",
3779 "Severe query error in ORDER BY clause -- see error log for more details"
3781 jsonIteratorFree( order_itr );
3782 jsonIteratorFree( class_itr );
3784 buffer_free(group_buf);
3785 buffer_free(order_buf);
3786 buffer_free(sql_buf);
3787 if (defaultselhash) jsonObjectFree(defaultselhash);
3791 growing_buffer* field_buf = buffer_init(16);
3792 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3793 string = buffer_release(field_buf);
3796 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3797 const char* dir = jsonObjectGetString(tmp_const);
3798 if (!strncasecmp(dir, "d", 1)) {
3799 direction = " DESC";
3805 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
3806 osrfLogError( OSRF_LOG_MARK,
3807 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
3808 MODULENAME, json_type( onode->type ) );
3810 osrfAppSessionStatus(
3812 OSRF_STATUS_INTERNALSERVERERROR,
3813 "osrfMethodException",
3815 "Malformed ORDER BY clause -- see error log for more details"
3817 jsonIteratorFree( order_itr );
3818 jsonIteratorFree( class_itr );
3820 buffer_free(group_buf);
3821 buffer_free(order_buf);
3822 buffer_free(sql_buf);
3823 if (defaultselhash) jsonObjectFree(defaultselhash);
3827 string = strdup(order_itr->key);
3828 const char* dir = jsonObjectGetString(onode);
3829 if (!strncasecmp(dir, "d", 1)) {
3830 direction = " DESC";
3837 OSRF_BUFFER_ADD(order_buf, ", ");
3839 order_buf = buffer_init(128);
3841 OSRF_BUFFER_ADD(order_buf, string);
3845 OSRF_BUFFER_ADD(order_buf, direction);
3849 jsonIteratorFree(order_itr);
3851 } else if ( snode->type == JSON_ARRAY ) {
3853 // Array is a list of fields from the current class
3854 unsigned long order_idx = 0;
3855 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
3857 const char* _f = jsonObjectGetString( onode );
3859 osrfHash* field_def = osrfHashGet( field_list_def, _f );
3861 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3864 osrfAppSessionStatus(
3866 OSRF_STATUS_INTERNALSERVERERROR,
3867 "osrfMethodException",
3869 "Invalid field in ORDER BY clause -- see error log for more details"
3871 jsonIteratorFree( class_itr );
3872 buffer_free( order_buf );
3874 buffer_free(group_buf);
3875 buffer_free(sql_buf);
3876 if (defaultselhash) jsonObjectFree(defaultselhash);
3878 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3879 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3882 osrfAppSessionStatus(
3884 OSRF_STATUS_INTERNALSERVERERROR,
3885 "osrfMethodException",
3887 "Virtual field in ORDER BY clause -- see error log for more details"
3889 jsonIteratorFree( class_itr );
3890 buffer_free( order_buf );
3892 buffer_free(group_buf);
3893 buffer_free(sql_buf);
3894 if (defaultselhash) jsonObjectFree(defaultselhash);
3899 OSRF_BUFFER_ADD(order_buf, ", ");
3901 order_buf = buffer_init(128);
3903 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
3907 // IT'S THE OOOOOOOOOOOLD STYLE!
3909 osrfLogError(OSRF_LOG_MARK,
3910 "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3912 osrfAppSessionStatus(
3914 OSRF_STATUS_INTERNALSERVERERROR,
3915 "osrfMethodException",
3917 "Severe query error -- see error log for more details"
3922 buffer_free(group_buf);
3923 buffer_free(order_buf);
3924 buffer_free(sql_buf);
3925 if (defaultselhash) jsonObjectFree(defaultselhash);
3926 jsonIteratorFree(class_itr);
3930 jsonIteratorFree( class_itr );
3932 osrfLogError(OSRF_LOG_MARK,
3933 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
3934 MODULENAME, json_type( order_hash->type ) );
3936 osrfAppSessionStatus(
3938 OSRF_STATUS_INTERNALSERVERERROR,
3939 "osrfMethodException",
3941 "Malformed ORDER BY clause -- see error log for more details"
3943 buffer_free( order_buf );
3945 buffer_free(group_buf);
3946 buffer_free(sql_buf);
3947 if (defaultselhash) jsonObjectFree(defaultselhash);
3952 order_by_list = buffer_release( order_buf );
3956 string = buffer_release(group_buf);
3958 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
3959 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3960 OSRF_BUFFER_ADD( sql_buf, string );
3965 if( having_buf && *having_buf ) {
3966 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3967 OSRF_BUFFER_ADD( sql_buf, having_buf );
3971 if( order_by_list ) {
3973 if ( *order_by_list ) {
3974 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3975 OSRF_BUFFER_ADD( sql_buf, order_by_list );
3978 free( order_by_list );
3982 const char* str = jsonObjectGetString(limit);
3983 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
3987 const char* str = jsonObjectGetString(offset);
3988 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
3991 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3993 if (defaultselhash) jsonObjectFree(defaultselhash);
3995 return buffer_release(sql_buf);
3997 } // end of SELECT()
3999 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4001 const char* locale = osrf_message_get_last_locale();
4003 osrfHash* fields = osrfHashGet(meta, "fields");
4004 char* core_class = osrfHashGet(meta, "classname");
4006 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4008 jsonObject* node = NULL;
4009 jsonObject* snode = NULL;
4010 jsonObject* onode = NULL;
4011 const jsonObject* _tmp = NULL;
4012 jsonObject* selhash = NULL;
4013 jsonObject* defaultselhash = NULL;
4015 growing_buffer* sql_buf = buffer_init(128);
4016 growing_buffer* select_buf = buffer_init(128);
4018 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4019 defaultselhash = jsonNewObjectType(JSON_HASH);
4020 selhash = defaultselhash;
4023 // If there's no SELECT list for the core class, build one
4024 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
4025 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4027 // Add every non-virtual field to the field list
4028 osrfHash* field_def = NULL;
4029 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4030 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4031 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4032 const char* field = osrfHashIteratorKey( field_itr );
4033 jsonObjectPush( field_list, jsonNewObject( field ) );
4036 osrfHashIteratorFree( field_itr );
4037 jsonObjectSetKey( selhash, core_class, field_list );
4041 jsonIterator* class_itr = jsonNewIterator( selhash );
4042 while ( (snode = jsonIteratorNext( class_itr )) ) {
4044 const char* cname = class_itr->key;
4045 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4046 if (!idlClass) continue;
4048 if (strcmp(core_class,class_itr->key)) {
4049 if (!join_hash) continue;
4051 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
4053 jsonObjectFree(found);
4057 jsonObjectFree(found);
4060 jsonIterator* select_itr = jsonNewIterator( snode );
4061 while ( (node = jsonIteratorNext( select_itr )) ) {
4062 const char* item_str = jsonObjectGetString( node );
4063 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4064 char* fname = osrfHashGet(field, "name");
4066 if (!field) continue;
4071 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
4076 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4077 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4080 i18n = osrfHashGet(field, "i18n");
4082 if( str_is_true( i18n ) ) {
4083 char* pkey = osrfHashGet(idlClass, "primarykey");
4084 char* tname = osrfHashGet(idlClass, "tablename");
4086 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);
4088 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4091 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4095 jsonIteratorFree(select_itr);
4098 jsonIteratorFree(class_itr);
4100 char* col_list = buffer_release(select_buf);
4101 char* table = getSourceDefinition(meta);
4103 table = strdup( "(null)" );
4105 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4109 // Clear the query stack (as a fail-safe precaution against possible
4110 // leftover garbage); then push the first query frame onto the stack.
4111 clear_query_stack();
4113 if( add_query_core( NULL, core_class ) ) {
4115 osrfAppSessionStatus(
4117 OSRF_STATUS_INTERNALSERVERERROR,
4118 "osrfMethodException",
4120 "Unable to build query frame for core class"
4126 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4127 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
4128 OSRF_BUFFER_ADD(sql_buf, join_clause);
4132 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4133 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
4135 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
4137 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4139 osrfAppSessionStatus(
4141 OSRF_STATUS_INTERNALSERVERERROR,
4142 "osrfMethodException",
4144 "Severe query error -- see error log for more details"
4146 buffer_free(sql_buf);
4147 if(defaultselhash) jsonObjectFree(defaultselhash);
4148 clear_query_stack();
4151 buffer_add(sql_buf, pred);
4156 char* string = NULL;
4157 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4159 growing_buffer* order_buf = buffer_init(128);
4162 jsonIterator* class_itr = jsonNewIterator( _tmp );
4163 while ( (snode = jsonIteratorNext( class_itr )) ) {
4165 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4168 if ( snode->type == JSON_HASH ) {
4170 jsonIterator* order_itr = jsonNewIterator( snode );
4171 while ( (onode = jsonIteratorNext( order_itr )) ) {
4173 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4174 class_itr->key, order_itr->key );
4178 char* direction = NULL;
4179 if ( onode->type == JSON_HASH ) {
4180 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4181 string = searchFieldTransform( class_itr->key, field_def, onode );
4183 osrfAppSessionStatus(
4185 OSRF_STATUS_INTERNALSERVERERROR,
4186 "osrfMethodException",
4188 "Severe query error in ORDER BY clause -- see error log for more details"
4190 jsonIteratorFree( order_itr );
4191 jsonIteratorFree( class_itr );
4192 buffer_free( order_buf );
4193 buffer_free( sql_buf );
4194 if( defaultselhash ) jsonObjectFree( defaultselhash );
4195 clear_query_stack();
4199 growing_buffer* field_buf = buffer_init(16);
4200 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4201 string = buffer_release(field_buf);
4204 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4205 const char* dir = jsonObjectGetString(_tmp);
4206 if (!strncasecmp(dir, "d", 1)) {
4207 direction = " DESC";
4214 string = strdup(order_itr->key);
4215 const char* dir = jsonObjectGetString(onode);
4216 if (!strncasecmp(dir, "d", 1)) {
4217 direction = " DESC";
4226 buffer_add(order_buf, ", ");
4229 buffer_add(order_buf, string);
4233 buffer_add(order_buf, direction);
4238 jsonIteratorFree(order_itr);
4241 const char* str = jsonObjectGetString(snode);
4242 buffer_add(order_buf, str);
4248 jsonIteratorFree(class_itr);
4250 string = buffer_release(order_buf);
4253 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4254 OSRF_BUFFER_ADD( sql_buf, string );
4260 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4261 const char* str = jsonObjectGetString(_tmp);
4269 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4271 const char* str = jsonObjectGetString(_tmp);
4280 if (defaultselhash) jsonObjectFree(defaultselhash);
4281 clear_query_stack();
4283 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4284 return buffer_release(sql_buf);
4287 int doJSONSearch ( osrfMethodContext* ctx ) {
4288 if(osrfMethodVerifyContext( ctx )) {
4289 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4293 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
4298 dbhandle = writehandle;
4300 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4304 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4305 flags |= SELECT_DISTINCT;
4307 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4308 flags |= DISABLE_I18N;
4310 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4313 jsonObjectGetKey( hash, "select" ),
4314 jsonObjectGetKey( hash, "from" ),
4315 jsonObjectGetKey( hash, "where" ),
4316 jsonObjectGetKey( hash, "having" ),
4317 jsonObjectGetKey( hash, "order_by" ),
4318 jsonObjectGetKey( hash, "limit" ),
4319 jsonObjectGetKey( hash, "offset" ),
4322 clear_query_stack();
4329 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4330 dbi_result result = dbi_conn_query(dbhandle, sql);
4333 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4335 if (dbi_result_first_row(result)) {
4336 /* JSONify the result */
4337 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4340 jsonObject* return_val = oilsMakeJSONFromResult( result );
4341 osrfAppRespond( ctx, return_val );
4342 jsonObjectFree( return_val );
4343 } while (dbi_result_next_row(result));
4346 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4349 osrfAppRespondComplete( ctx, NULL );
4351 /* clean up the query */
4352 dbi_result_free(result);
4356 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4357 osrfAppSessionStatus(
4359 OSRF_STATUS_INTERNALSERVERERROR,
4360 "osrfMethodException",
4362 "Severe query error -- see error log for more details"
4370 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4371 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4374 dbhandle = writehandle;
4376 osrfHash* links = osrfHashGet(meta, "links");
4377 osrfHash* fields = osrfHashGet(meta, "fields");
4378 char* core_class = osrfHashGet(meta, "classname");
4379 char* pkey = osrfHashGet(meta, "primarykey");
4381 const jsonObject* _tmp;
4384 char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4386 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4391 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4393 dbi_result result = dbi_conn_query(dbhandle, sql);
4394 if( NULL == result ) {
4395 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4396 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4397 osrfAppSessionStatus(
4399 OSRF_STATUS_INTERNALSERVERERROR,
4400 "osrfMethodException",
4402 "Severe query error -- see error log for more details"
4409 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4412 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
4413 osrfHash* dedup = osrfNewHash();
4415 if (dbi_result_first_row(result)) {
4416 /* JSONify the result */
4417 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4419 obj = oilsMakeFieldmapperFromResult( result, meta );
4420 char* pkey_val = oilsFMGetString( obj, pkey );
4421 if ( osrfHashGet( dedup, pkey_val ) ) {
4422 jsonObjectFree(obj);
4425 osrfHashSet( dedup, pkey_val, pkey_val );
4426 jsonObjectPush(res_list, obj);
4428 } while (dbi_result_next_row(result));
4430 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
4434 osrfHashFree(dedup);
4435 /* clean up the query */
4436 dbi_result_free(result);
4439 if (res_list->size && query_hash) {
4440 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
4442 int x = (int)jsonObjectGetNumber(_tmp);
4443 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
4445 const jsonObject* temp_blob;
4446 if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
4448 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
4449 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
4451 osrfStringArray* link_fields = NULL;
4454 if (flesh_fields->size == 1) {
4455 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
4456 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
4461 link_fields = osrfNewStringArray(1);
4462 jsonIterator* _i = jsonNewIterator( flesh_fields );
4463 while ((_f = jsonIteratorNext( _i ))) {
4464 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4466 jsonIteratorFree(_i);
4471 unsigned long res_idx = 0;
4472 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
4477 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4479 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4481 osrfHash* kid_link = osrfHashGet(links, link_field);
4482 if (!kid_link) continue;
4484 osrfHash* field = osrfHashGet(fields, link_field);
4485 if (!field) continue;
4487 osrfHash* value_field = field;
4489 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4490 if (!kid_idl) continue;
4492 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4493 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4496 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4497 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4500 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4502 if (link_map->size > 0) {
4503 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4506 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4511 osrfHashGet(kid_link, "class"),
4518 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4519 osrfHashGet(kid_link, "field"),
4520 osrfHashGet(kid_link, "class"),
4521 osrfHashGet(kid_link, "key"),
4522 osrfHashGet(kid_link, "reltype")
4525 const char* search_key = jsonObjectGetString(
4528 atoi( osrfHashGet(value_field, "array_position") )
4533 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4537 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
4539 // construct WHERE clause
4540 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
4543 osrfHashGet(kid_link, "key"),
4544 jsonNewObject( search_key )
4547 // construct the rest of the query
4548 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
4549 jsonObjectSetKey( rest_of_query, "flesh",
4550 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4554 jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
4556 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
4557 jsonObjectSetKey( rest_of_query, "order_by",
4558 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
4562 if (jsonObjectGetKeyConst(query_hash, "select")) {
4563 jsonObjectSetKey( rest_of_query, "select",
4564 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
4568 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
4569 where_clause, rest_of_query, err);
4571 jsonObjectFree( where_clause );
4572 jsonObjectFree( rest_of_query );
4575 osrfStringArrayFree(link_fields);
4576 jsonObjectFree(res_list);
4577 jsonObjectFree(flesh_blob);
4581 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4583 jsonObject* X = NULL;
4584 if ( link_map->size > 0 && kids->size > 0 ) {
4586 kids = jsonNewObjectType(JSON_ARRAY);
4588 jsonObject* _k_node;
4589 unsigned long res_idx = 0;
4590 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
4596 (unsigned long)atoi(
4602 osrfHashGet(kid_link, "class")
4606 osrfStringArrayGetString( link_map, 0 )
4614 } // end while loop traversing X
4617 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4618 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4621 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4622 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4626 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4627 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4630 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4631 jsonObjectClone( kids )
4636 jsonObjectFree(kids);
4640 jsonObjectFree( kids );
4642 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4643 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4646 } // end while loop traversing res_list
4647 jsonObjectFree( flesh_blob );
4648 osrfStringArrayFree(link_fields);
4657 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
4659 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4661 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
4663 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
4666 if (!verifyObjectClass(ctx, target)) {
4671 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4672 osrfAppSessionStatus(
4674 OSRF_STATUS_BADREQUEST,
4675 "osrfMethodException",
4677 "No active transaction -- required for UPDATE"
4683 // The following test is harmless but redundant. If a class is
4684 // readonly, we don't register an update method for it.
4685 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4686 osrfAppSessionStatus(
4688 OSRF_STATUS_BADREQUEST,
4689 "osrfMethodException",
4691 "Cannot UPDATE readonly class"
4697 dbhandle = writehandle;
4699 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4701 // Set the last_xact_id
4702 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4704 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
4705 trans_id, target->classname, index);
4706 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4709 char* pkey = osrfHashGet(meta, "primarykey");
4710 osrfHash* fields = osrfHashGet(meta, "fields");
4712 char* id = oilsFMGetString( target, pkey );
4716 "%s updating %s object with %s = %s",
4718 osrfHashGet(meta, "fieldmapper"),
4723 growing_buffer* sql = buffer_init(128);
4724 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4727 osrfHash* field_def = NULL;
4728 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4729 while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
4731 // Skip virtual fields, and the primary key
4732 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
4735 const char* field_name = osrfHashIteratorKey( field_itr );
4736 if( ! strcmp( field_name, pkey ) )
4739 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4741 int value_is_numeric = 0; // boolean
4743 if (field_object && field_object->classname) {
4744 value = oilsFMGetString(
4746 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4749 value = jsonObjectToSimpleString( field_object );
4750 if( field_object && JSON_NUMBER == field_object->type )
4751 value_is_numeric = 1;
4754 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
4755 osrfHashGet(meta, "fieldmapper"), field_name, value);
4757 if (!field_object || field_object->type == JSON_NULL) {
4758 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
4759 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
4760 if (first) first = 0;
4761 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4762 buffer_fadd( sql, " %s = NULL", field_name );
4765 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
4766 if (first) first = 0;
4767 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4769 const char* numtype = get_datatype( field_def );
4770 if ( !strncmp( numtype, "INT", 3 ) ) {
4771 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
4772 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
4773 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
4775 // Must really be intended as a string, so quote it
4776 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4777 buffer_fadd( sql, " %s = %s", field_name, value );
4779 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4780 osrfAppSessionStatus(
4782 OSRF_STATUS_INTERNALSERVERERROR,
4783 "osrfMethodException",
4785 "Error quoting string -- please see the error log for more details"
4789 osrfHashIteratorFree( field_itr );
4796 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
4799 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4800 if (first) first = 0;
4801 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4802 buffer_fadd( sql, " %s = %s", field_name, value );
4805 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4806 osrfAppSessionStatus(
4808 OSRF_STATUS_INTERNALSERVERERROR,
4809 "osrfMethodException",
4811 "Error quoting string -- please see the error log for more details"
4815 osrfHashIteratorFree( field_itr );
4826 osrfHashIteratorFree( field_itr );
4828 jsonObject* obj = jsonNewObject(id);
4830 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4831 dbi_conn_quote_string(dbhandle, &id);
4833 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
4835 char* query = buffer_release(sql);
4836 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
4838 dbi_result result = dbi_conn_query(dbhandle, query);
4842 jsonObjectFree(obj);
4843 obj = jsonNewObject(NULL);
4846 "%s ERROR updating %s object with %s = %s",
4848 osrfHashGet(meta, "fieldmapper"),
4859 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4861 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4863 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4864 osrfAppSessionStatus(
4866 OSRF_STATUS_BADREQUEST,
4867 "osrfMethodException",
4869 "No active transaction -- required for DELETE"
4875 // The following test is harmless but redundant. If a class is
4876 // readonly, we don't register a delete method for it.
4877 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4878 osrfAppSessionStatus(
4880 OSRF_STATUS_BADREQUEST,
4881 "osrfMethodException",
4883 "Cannot DELETE readonly class"
4889 dbhandle = writehandle;
4893 char* pkey = osrfHashGet(meta, "primarykey");
4901 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4902 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4907 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4910 if (!verifyObjectPCRUD( ctx, NULL )) {
4915 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4920 "%s deleting %s object with %s = %s",
4922 osrfHashGet(meta, "fieldmapper"),
4927 obj = jsonNewObject(id);
4929 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4930 dbi_conn_quote_string(writehandle, &id);
4932 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4935 jsonObjectFree(obj);
4936 obj = jsonNewObject(NULL);
4939 "%s ERROR deleting %s object with %s = %s",
4941 osrfHashGet(meta, "fieldmapper"),
4954 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4955 if(!(result && meta)) return jsonNULL;
4957 jsonObject* object = jsonNewObject(NULL);
4958 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4960 osrfHash* fields = osrfHashGet(meta, "fields");
4962 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4966 char dt_string[256];
4970 int columnIndex = 1;
4972 unsigned short type;
4973 const char* columnName;
4975 /* cycle through the column list */
4976 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
4978 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4980 fmIndex = -1; // reset the position
4982 /* determine the field type and storage attributes */
4983 type = dbi_result_get_field_type_idx(result, columnIndex);
4984 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
4986 /* fetch the fieldmapper index */
4987 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4989 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4992 const char* pos = (char*)osrfHashGet(_f, "array_position");
4993 if ( !pos ) continue;
4995 fmIndex = atoi( pos );
4996 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
5001 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5002 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
5007 case DBI_TYPE_INTEGER :
5009 if( attr & DBI_INTEGER_SIZE8 )
5010 jsonObjectSetIndex( object, fmIndex,
5011 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
5013 jsonObjectSetIndex( object, fmIndex,
5014 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
5018 case DBI_TYPE_DECIMAL :
5019 jsonObjectSetIndex( object, fmIndex,
5020 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
5023 case DBI_TYPE_STRING :
5029 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
5034 case DBI_TYPE_DATETIME :
5036 memset(dt_string, '\0', sizeof(dt_string));
5037 memset(&gmdt, '\0', sizeof(gmdt));
5039 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5042 if (!(attr & DBI_DATETIME_DATE)) {
5043 gmtime_r( &_tmp_dt, &gmdt );
5044 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5045 } else if (!(attr & DBI_DATETIME_TIME)) {
5046 localtime_r( &_tmp_dt, &gmdt );
5047 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5049 localtime_r( &_tmp_dt, &gmdt );
5050 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5053 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
5057 case DBI_TYPE_BINARY :
5058 osrfLogError( OSRF_LOG_MARK,
5059 "Can't do binary at column %s : index %d", columnName, columnIndex);
5068 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5069 if(!result) return jsonNULL;
5071 jsonObject* object = jsonNewObject(NULL);
5074 char dt_string[256];
5078 int columnIndex = 1;
5080 unsigned short type;
5081 const char* columnName;
5083 /* cycle through the column list */
5084 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5086 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5088 fmIndex = -1; // reset the position
5090 /* determine the field type and storage attributes */
5091 type = dbi_result_get_field_type_idx(result, columnIndex);
5092 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5094 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5095 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
5100 case DBI_TYPE_INTEGER :
5102 if( attr & DBI_INTEGER_SIZE8 )
5103 jsonObjectSetKey( object, columnName,
5104 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)) );
5106 jsonObjectSetKey( object, columnName,
5107 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
5110 case DBI_TYPE_DECIMAL :
5111 jsonObjectSetKey( object, columnName,
5112 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
5115 case DBI_TYPE_STRING :
5116 jsonObjectSetKey( object, columnName,
5117 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
5120 case DBI_TYPE_DATETIME :
5122 memset(dt_string, '\0', sizeof(dt_string));
5123 memset(&gmdt, '\0', sizeof(gmdt));
5125 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5128 if (!(attr & DBI_DATETIME_DATE)) {
5129 gmtime_r( &_tmp_dt, &gmdt );
5130 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5131 } else if (!(attr & DBI_DATETIME_TIME)) {
5132 localtime_r( &_tmp_dt, &gmdt );
5133 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5135 localtime_r( &_tmp_dt, &gmdt );
5136 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5139 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
5142 case DBI_TYPE_BINARY :
5143 osrfLogError( OSRF_LOG_MARK,
5144 "Can't do binary at column %s : index %d", columnName, columnIndex );
5148 } // end while loop traversing result
5153 // Interpret a string as true or false
5154 static int str_is_true( const char* str ) {
5155 if( NULL == str || strcasecmp( str, "true" ) )
5161 // Interpret a jsonObject as true or false
5162 static int obj_is_true( const jsonObject* obj ) {
5165 else switch( obj->type )
5173 if( strcasecmp( obj->value.s, "true" ) )
5177 case JSON_NUMBER : // Support 1/0 for perl's sake
5178 if( jsonObjectGetNumber( obj ) == 1.0 )
5187 // Translate a numeric code into a text string identifying a type of
5188 // jsonObject. To be used for building error messages.
5189 static const char* json_type( int code ) {
5195 return "JSON_ARRAY";
5197 return "JSON_STRING";
5199 return "JSON_NUMBER";
5205 return "(unrecognized)";
5209 // Extract the "primitive" attribute from an IDL field definition.
5210 // If we haven't initialized the app, then we must be running in
5211 // some kind of testbed. In that case, default to "string".
5212 static const char* get_primitive( osrfHash* field ) {
5213 const char* s = osrfHashGet( field, "primitive" );
5215 if( child_initialized )
5218 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5220 osrfHashGet( field, "name" )
5228 // Extract the "datatype" attribute from an IDL field definition.
5229 // If we haven't initialized the app, then we must be running in
5230 // some kind of testbed. In that case, default to to NUMERIC,
5231 // since we look at the datatype only for numbers.
5232 static const char* get_datatype( osrfHash* field ) {
5233 const char* s = osrfHashGet( field, "datatype" );
5235 if( child_initialized )
5238 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5240 osrfHashGet( field, "name" )
5249 If the input string is potentially a valid SQL identifier, return 1.
5252 Purpose: to prevent certain kinds of SQL injection. To that end we
5253 don't necessarily need to follow all the rules exactly, such as requiring
5254 that the first character not be a digit.
5256 We allow leading and trailing white space. In between, we do not allow
5257 punctuation (except for underscores and dollar signs), control
5258 characters, or embedded white space.
5260 More pedantically we should allow quoted identifiers containing arbitrary
5261 characters, but for the foreseeable future such quoted identifiers are not
5262 likely to be an issue.
5264 static int is_identifier( const char* s) {
5268 // Skip leading white space
5269 while( isspace( (unsigned char) *s ) )
5273 return 0; // Nothing but white space? Not okay.
5275 // Check each character until we reach white space or
5276 // end-of-string. Letters, digits, underscores, and
5277 // dollar signs are okay. With the exception of periods
5278 // (as in schema.identifier), control characters and other
5279 // punctuation characters are not okay. Anything else
5280 // is okay -- it could for example be part of a multibyte
5281 // UTF8 character such as a letter with diacritical marks,
5282 // and those are allowed.
5284 if( isalnum( (unsigned char) *s )
5288 ; // Fine; keep going
5289 else if( ispunct( (unsigned char) *s )
5290 || iscntrl( (unsigned char) *s ) )
5293 } while( *s && ! isspace( (unsigned char) *s ) );
5295 // If we found any white space in the above loop,
5296 // the rest had better be all white space.
5298 while( isspace( (unsigned char) *s ) )
5302 return 0; // White space was embedded within non-white space
5308 Determine whether to accept a character string as a comparison operator.
5309 Return 1 if it's good, or 0 if it's bad.
5311 We don't validate it for real. We just make sure that it doesn't contain
5312 any semicolons or white space (with special exceptions for a few specific
5313 operators). The idea is to block certain kinds of SQL injection. If it
5314 has no semicolons or white space but it's still not a valid operator, then
5315 the database will complain.
5317 Another approach would be to compare the string against a short list of
5318 approved operators. We don't do that because we want to allow custom
5319 operators like ">100*", which would be difficult or impossible to
5320 express otherwise in a JSON query.
5322 static int is_good_operator( const char* op ) {
5323 if( !op ) return 0; // Sanity check
5327 if( isspace( (unsigned char) *s ) ) {
5328 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
5329 // and IS NOT DISTINCT FROM.
5330 if( !strcasecmp( op, "similar to" ) )
5332 else if( !strcasecmp( op, "is distinct from" ) )
5334 else if( !strcasecmp( op, "is not distinct from" ) )
5339 else if( ';' == *s )
5346 /* ----------------------------------------------------------------------------------
5347 The following machinery supports a stack of query frames for use by SELECT().
5349 A query frame caches information about one level of a SELECT query. When we enter
5350 a subquery, we push another query frame onto the stack, and pop it off when we leave.
5352 The query frame stores information about the core class, and about any joined classes
5355 The main purpose is to map table aliases to classes and tables, so that a query can
5356 join to the same table more than once. A secondary goal is to reduce the number of
5357 lookups in the IDL by caching the results.
5358 ----------------------------------------------------------------------------------*/
5360 #define STATIC_CLASS_INFO_COUNT 3
5362 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
5364 /* ---------------------------------------------------------------------------
5365 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5367 ---------------------------------------------------------------------------*/
5368 static ClassInfo* allocate_class_info( void ) {
5369 // In order to reduce the number of mallocs and frees, we return a static
5370 // instance of ClassInfo, if we can find one that we're not already using.
5371 // We rely on the fact that the compiler will implicitly initialize the
5372 // static instances so that in_use == 0.
5375 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5376 if( ! static_class_info[ i ].in_use ) {
5377 static_class_info[ i ].in_use = 1;
5378 return static_class_info + i;
5382 // The static ones are all in use. Malloc one.
5384 return safe_malloc( sizeof( ClassInfo ) );
5387 /* --------------------------------------------------------------------------
5388 Free any malloc'd memory owned by a ClassInfo; return it to a pristine state
5389 ---------------------------------------------------------------------------*/
5390 static void clear_class_info( ClassInfo* info ) {
5395 // Free any malloc'd strings
5397 if( info->alias != info->alias_store )
5398 free( info->alias );
5400 if( info->class_name != info->class_name_store )
5401 free( info->class_name );
5403 free( info->source_def );
5405 info->alias = info->class_name = info->source_def = NULL;
5409 /* --------------------------------------------------------------------------
5410 Deallocate a ClassInfo and everything it owns
5411 ---------------------------------------------------------------------------*/
5412 static void free_class_info( ClassInfo* info ) {
5417 clear_class_info( info );
5419 // If it's one of the static instances, just mark it as not in use
5422 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5423 if( info == static_class_info + i ) {
5424 static_class_info[ i ].in_use = 0;
5429 // Otherwise it must have been malloc'd, so free it
5434 /* --------------------------------------------------------------------------
5435 Populate an already-allocated ClassInfo. Return 0 if successful, 1 if not.
5436 ---------------------------------------------------------------------------*/
5437 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
5440 osrfLogError( OSRF_LOG_MARK,
5441 "%s ERROR: No ClassInfo available to populate", MODULENAME );
5442 info->alias = info->class_name = info->source_def = NULL;
5443 info->class_def = info->fields = info->links = NULL;
5448 osrfLogError( OSRF_LOG_MARK,
5449 "%s ERROR: No class name provided for lookup", MODULENAME );
5450 info->alias = info->class_name = info->source_def = NULL;
5451 info->class_def = info->fields = info->links = NULL;
5455 // Alias defaults to class name if not supplied
5456 if( ! alias || ! alias[ 0 ] )
5459 // Look up class info in the IDL
5460 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
5462 osrfLogError( OSRF_LOG_MARK,
5463 "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
5464 info->alias = info->class_name = info->source_def = NULL;
5465 info->class_def = info->fields = info->links = NULL;
5467 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
5468 osrfLogError( OSRF_LOG_MARK,
5469 "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
5470 info->alias = info->class_name = info->source_def = NULL;
5471 info->class_def = info->fields = info->links = NULL;
5475 osrfHash* links = osrfHashGet( class_def, "links" );
5477 osrfLogError( OSRF_LOG_MARK,
5478 "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
5479 info->alias = info->class_name = info->source_def = NULL;
5480 info->class_def = info->fields = info->links = NULL;
5484 osrfHash* fields = osrfHashGet( class_def, "fields" );
5486 osrfLogError( OSRF_LOG_MARK,
5487 "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
5488 info->alias = info->class_name = info->source_def = NULL;
5489 info->class_def = info->fields = info->links = NULL;
5493 char* source_def = getSourceDefinition( class_def );
5497 // We got everything we need, so populate the ClassInfo
5498 if( strlen( alias ) > ALIAS_STORE_SIZE )
5499 info->alias = strdup( alias );
5501 strcpy( info->alias_store, alias );
5502 info->alias = info->alias_store;
5505 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
5506 info->class_name = strdup( class );
5508 strcpy( info->class_name_store, class );
5509 info->class_name = info->class_name_store;
5512 info->source_def = source_def;
5514 info->class_def = class_def;
5515 info->links = links;
5516 info->fields = fields;
5521 #define STATIC_FRAME_COUNT 3
5523 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
5525 /* ---------------------------------------------------------------------------
5526 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5528 ---------------------------------------------------------------------------*/
5529 static QueryFrame* allocate_frame( void ) {
5530 // In order to reduce the number of mallocs and frees, we return a static
5531 // instance of QueryFrame, if we can find one that we're not already using.
5532 // We rely on the fact that the compiler will implicitly initialize the
5533 // static instances so that in_use == 0.
5536 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5537 if( ! static_frame[ i ].in_use ) {
5538 static_frame[ i ].in_use = 1;
5539 return static_frame + i;
5543 // The static ones are all in use. Malloc one.
5545 return safe_malloc( sizeof( QueryFrame ) );
5548 /* --------------------------------------------------------------------------
5549 Free a QueryFrame, and all the memory it owns.
5550 ---------------------------------------------------------------------------*/
5551 static void free_query_frame( QueryFrame* frame ) {
5556 clear_class_info( &frame->core );
5558 // Free the join list
5560 ClassInfo* info = frame->join_list;
5563 free_class_info( info );
5567 frame->join_list = NULL;
5570 // If the frame is a static instance, just mark it as unused
5572 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5573 if( frame == static_frame + i ) {
5574 static_frame[ i ].in_use = 0;
5579 // Otherwise it must have been malloc'd, so free it
5584 /* --------------------------------------------------------------------------
5585 Search a given QueryFrame for a specified alias. If you find it, return
5586 a pointer to the corresponding ClassInfo. Otherwise return NULL.
5587 ---------------------------------------------------------------------------*/
5588 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
5589 if( ! frame || ! target ) {
5593 ClassInfo* found_class = NULL;
5595 if( !strcmp( target, frame->core.alias ) )
5596 return &(frame->core);
5598 ClassInfo* curr_class = frame->join_list;
5599 while( curr_class ) {
5600 if( strcmp( target, curr_class->alias ) )
5601 curr_class = curr_class->next;
5603 found_class = curr_class;
5612 /* --------------------------------------------------------------------------
5613 Push a new (blank) QueryFrame onto the stack.
5614 ---------------------------------------------------------------------------*/
5615 static void push_query_frame( void ) {
5616 QueryFrame* frame = allocate_frame();
5617 frame->join_list = NULL;
5618 frame->next = curr_query;
5620 // Initialize the ClassInfo for the core class
5621 ClassInfo* core = &frame->core;
5622 core->alias = core->class_name = core->source_def = NULL;
5623 core->class_def = core->fields = core->links = NULL;
5628 /* --------------------------------------------------------------------------
5629 Pop a QueryFrame off the stack and destroy it
5630 ---------------------------------------------------------------------------*/
5631 static void pop_query_frame( void ) {
5636 QueryFrame* popped = curr_query;
5637 curr_query = popped->next;
5639 free_query_frame( popped );
5642 /* --------------------------------------------------------------------------
5643 Populate the ClassInfo for the core class. Return 0 if successful, 1 if not.
5644 ---------------------------------------------------------------------------*/
5645 static int add_query_core( const char* alias, const char* class_name ) {
5648 if( ! curr_query ) {
5649 osrfLogError( OSRF_LOG_MARK,
5650 "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
5652 } else if( curr_query->core.alias ) {
5653 osrfLogError( OSRF_LOG_MARK,
5654 "%s ERROR: Core class %s already populated as %s",
5655 MODULENAME, curr_query->core.class_name, curr_query->core.alias );
5659 build_class_info( &curr_query->core, alias, class_name );
5660 if( curr_query->core.alias )
5663 osrfLogError( OSRF_LOG_MARK,
5664 "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
5669 /* --------------------------------------------------------------------------
5670 Search the current QueryFrame for a specified alias. If you find it,
5671 return a pointer to the corresponding ClassInfo. Otherwise return NULL.
5672 ---------------------------------------------------------------------------*/
5673 static ClassInfo* search_alias( const char* target ) {
5674 return search_alias_in_frame( curr_query, target );
5677 /* --------------------------------------------------------------------------
5678 Search all levels of query for a specified alias, starting with the
5679 current query. If you find it, return a pointer to the corresponding
5680 ClassInfo. Otherwise return NULL.
5681 ---------------------------------------------------------------------------*/
5682 static ClassInfo* search_all_alias( const char* target ) {
5683 ClassInfo* found_class = NULL;
5684 QueryFrame* curr_frame = curr_query;
5686 while( curr_frame ) {
5687 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
5690 curr_frame = curr_frame->next;
5696 /* --------------------------------------------------------------------------
5697 Add a class to the list of classes joined to the current query.
5698 ---------------------------------------------------------------------------*/
5699 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
5701 if( ! classname || ! *classname ) { // sanity check
5702 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
5709 const ClassInfo* conflict = search_alias( alias );
5711 osrfLogError( OSRF_LOG_MARK,
5712 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
5713 MODULENAME, alias, conflict->class_name );
5717 ClassInfo* info = allocate_class_info();
5719 if( build_class_info( info, alias, classname ) ) {
5720 free_class_info( info );
5724 // Add the new ClassInfo to the join list of the current QueryFrame
5725 info->next = curr_query->join_list;
5726 curr_query->join_list = info;
5731 /* --------------------------------------------------------------------------
5732 Destroy all nodes on the query stack.
5733 ---------------------------------------------------------------------------*/
5734 static void clear_query_stack( void ) {