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)
1500 } else if( JSON_BOOL == field_object->type ) {
1501 if( jsonBoolIsTrue( field_object ) )
1502 value = strdup( "t" );
1504 value = strdup( "f" );
1506 value = jsonObjectToSimpleString( field_object );
1512 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1513 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1516 buffer_add(col_buf, field_name);
1518 if (!field_object || field_object->type == JSON_NULL) {
1519 buffer_add( val_buf, "DEFAULT" );
1521 } else if ( !strcmp(get_primitive( field ), "number") ) {
1522 const char* numtype = get_datatype( field );
1523 if ( !strcmp( numtype, "INT8") ) {
1524 buffer_fadd( val_buf, "%lld", atoll(value) );
1526 } else if ( !strcmp( numtype, "INT") ) {
1527 buffer_fadd( val_buf, "%d", atoi(value) );
1529 } else if ( !strcmp( numtype, "NUMERIC") ) {
1530 buffer_fadd( val_buf, "%f", atof(value) );
1533 if ( dbi_conn_quote_string(writehandle, &value) ) {
1534 OSRF_BUFFER_ADD( val_buf, value );
1537 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1538 osrfAppSessionStatus(
1540 OSRF_STATUS_INTERNALSERVERERROR,
1541 "osrfMethodException",
1543 "Error quoting string -- please see the error log for more details"
1546 buffer_free(table_buf);
1547 buffer_free(col_buf);
1548 buffer_free(val_buf);
1558 osrfHashIteratorFree( field_itr );
1560 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1561 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1563 char* table_str = buffer_release(table_buf);
1564 char* col_str = buffer_release(col_buf);
1565 char* val_str = buffer_release(val_buf);
1566 growing_buffer* sql = buffer_init(128);
1567 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1572 char* query = buffer_release(sql);
1574 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1577 dbi_result result = dbi_conn_query(writehandle, query);
1579 jsonObject* obj = NULL;
1582 obj = jsonNewObject(NULL);
1585 "%s ERROR inserting %s object using query [%s]",
1587 osrfHashGet(meta, "fieldmapper"),
1590 osrfAppSessionStatus(
1592 OSRF_STATUS_INTERNALSERVERERROR,
1593 "osrfMethodException",
1595 "INSERT error -- please see the error log for more details"
1600 char* id = oilsFMGetString(target, pkey);
1602 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1603 growing_buffer* _id = buffer_init(10);
1604 buffer_fadd(_id, "%lld", new_id);
1605 id = buffer_release(_id);
1608 // Find quietness specification, if present
1609 const char* quiet_str = NULL;
1611 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1613 quiet_str = jsonObjectGetString( quiet_obj );
1616 if( str_is_true( quiet_str ) ) { // if quietness is specified
1617 obj = jsonNewObject(id);
1621 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1622 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1624 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1626 jsonObjectFree( where_clause );
1631 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1634 jsonObjectFree( list );
1647 * Fetch one row from a specified table, using a specified value
1648 * for the primary key
1650 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1660 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1662 const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos); // key value
1666 "%s retrieving %s object with primary key value of %s",
1668 osrfHashGet( class_def, "fieldmapper" ),
1669 jsonObjectGetString( id_obj )
1672 // Build a WHERE clause based on the key value
1673 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1676 osrfHashGet( class_def, "primarykey" ),
1677 jsonObjectClone( id_obj )
1680 jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
1682 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
1684 jsonObjectFree( where_clause );
1688 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
1689 jsonObjectFree( list );
1692 if(!verifyObjectPCRUD(ctx, obj)) {
1693 jsonObjectFree(obj);
1696 growing_buffer* msg = buffer_init(128);
1697 OSRF_BUFFER_ADD( msg, MODULENAME );
1698 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1700 char* m = buffer_release(msg);
1701 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1712 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1713 growing_buffer* val_buf = buffer_init(32);
1714 const char* numtype = get_datatype( field );
1716 if ( !strncmp( numtype, "INT", 3 ) ) {
1717 if (value->type == JSON_NUMBER)
1718 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1719 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1721 //const char* val_str = jsonObjectGetString( value );
1722 //buffer_fadd( val_buf, "%ld", atol(val_str) );
1723 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1726 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
1727 if (value->type == JSON_NUMBER)
1728 //buffer_fadd( val_buf, "%f", jsonObjectGetNumber(value) );
1729 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1731 //const char* val_str = jsonObjectGetString( value );
1732 //buffer_fadd( val_buf, "%f", atof(val_str) );
1733 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1737 // Presumably this was really intended ot be a string, so quote it
1738 char* str = jsonObjectToSimpleString( value );
1739 if ( dbi_conn_quote_string(dbhandle, &str) ) {
1740 OSRF_BUFFER_ADD( val_buf, str );
1743 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
1745 buffer_free(val_buf);
1750 return buffer_release(val_buf);
1753 static char* searchINPredicate (const char* class_alias, osrfHash* field,
1754 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1755 growing_buffer* sql_buf = buffer_init(32);
1761 osrfHashGet(field, "name")
1765 buffer_add(sql_buf, "IN (");
1766 } else if (!(strcasecmp(op,"not in"))) {
1767 buffer_add(sql_buf, "NOT IN (");
1769 buffer_add(sql_buf, "IN (");
1772 if (node->type == JSON_HASH) {
1773 // subquery predicate
1774 char* subpred = SELECT(
1776 jsonObjectGetKey( node, "select" ),
1777 jsonObjectGetKey( node, "from" ),
1778 jsonObjectGetKey( node, "where" ),
1779 jsonObjectGetKey( node, "having" ),
1780 jsonObjectGetKey( node, "order_by" ),
1781 jsonObjectGetKey( node, "limit" ),
1782 jsonObjectGetKey( node, "offset" ),
1788 buffer_add(sql_buf, subpred);
1791 buffer_free( sql_buf );
1795 } else if (node->type == JSON_ARRAY) {
1796 // literal value list
1797 int in_item_index = 0;
1798 int in_item_first = 1;
1799 const jsonObject* in_item;
1800 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1805 buffer_add(sql_buf, ", ");
1808 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1809 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1810 MODULENAME, json_type( in_item->type ) );
1811 buffer_free(sql_buf);
1815 // Append the literal value -- quoted if not a number
1816 if ( JSON_NUMBER == in_item->type ) {
1817 char* val = jsonNumberToDBString( field, in_item );
1818 OSRF_BUFFER_ADD( sql_buf, val );
1821 } else if ( !strcmp( get_primitive( field ), "number") ) {
1822 char* val = jsonNumberToDBString( field, in_item );
1823 OSRF_BUFFER_ADD( sql_buf, val );
1827 char* key_string = jsonObjectToSimpleString(in_item);
1828 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1829 OSRF_BUFFER_ADD( sql_buf, key_string );
1832 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1834 buffer_free(sql_buf);
1840 if( in_item_first ) {
1841 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1842 buffer_free( sql_buf );
1846 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1847 MODULENAME, json_type( node->type ) );
1848 buffer_free(sql_buf);
1852 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1854 return buffer_release(sql_buf);
1857 // Receive a JSON_ARRAY representing a function call. The first
1858 // entry in the array is the function name. The rest are parameters.
1859 static char* searchValueTransform( const jsonObject* array ) {
1861 if( array->size < 1 ) {
1862 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
1866 // Get the function name
1867 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
1868 if( func_item->type != JSON_STRING ) {
1869 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
1870 MODULENAME, json_type( func_item->type ) );
1874 growing_buffer* sql_buf = buffer_init(32);
1876 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1877 OSRF_BUFFER_ADD( sql_buf, "( " );
1879 // Get the parameters
1880 int func_item_index = 1; // We already grabbed the zeroth entry
1881 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1883 // Add a separator comma, if we need one
1884 if( func_item_index > 2 )
1885 buffer_add( sql_buf, ", " );
1887 // Add the current parameter
1888 if (func_item->type == JSON_NULL) {
1889 buffer_add( sql_buf, "NULL" );
1891 char* val = jsonObjectToSimpleString(func_item);
1892 if ( dbi_conn_quote_string(dbhandle, &val) ) {
1893 OSRF_BUFFER_ADD( sql_buf, val );
1896 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1897 buffer_free(sql_buf);
1904 buffer_add( sql_buf, " )" );
1906 return buffer_release(sql_buf);
1909 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
1910 const jsonObject* node, const char* op) {
1912 if( ! is_good_operator( op ) ) {
1913 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
1917 char* val = searchValueTransform(node);
1921 growing_buffer* sql_buf = buffer_init(32);
1926 osrfHashGet(field, "name"),
1933 return buffer_release(sql_buf);
1936 // class_alias is a class name or other table alias
1937 // field is a field definition as stored in the IDL
1938 // node comes from the method parameter, and may represent an entry in the SELECT list
1939 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
1940 growing_buffer* sql_buf = buffer_init(32);
1942 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
1943 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
1945 if(transform_subcolumn) {
1946 if( ! is_identifier( transform_subcolumn ) ) {
1947 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
1948 MODULENAME, transform_subcolumn );
1949 buffer_free( sql_buf );
1952 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
1955 if (field_transform) {
1957 if( ! is_identifier( field_transform ) ) {
1958 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
1959 MODULENAME, field_transform );
1960 buffer_free( sql_buf );
1964 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class_alias, osrfHashGet(field, "name"));
1965 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
1968 if( array->type != JSON_ARRAY ) {
1969 osrfLogError( OSRF_LOG_MARK,
1970 "%s: Expected JSON_ARRAY for function params; found %s",
1971 MODULENAME, json_type( array->type ) );
1972 buffer_free( sql_buf );
1975 int func_item_index = 0;
1976 jsonObject* func_item;
1977 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1979 char* val = jsonObjectToSimpleString(func_item);
1982 buffer_add( sql_buf, ",NULL" );
1983 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
1984 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
1985 OSRF_BUFFER_ADD( sql_buf, val );
1987 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1989 buffer_free(sql_buf);
1996 buffer_add( sql_buf, " )" );
1999 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
2002 if (transform_subcolumn)
2003 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2005 return buffer_release(sql_buf);
2008 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2009 const jsonObject* node, const char* op ) {
2011 if( ! is_good_operator( op ) ) {
2012 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2016 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2017 if( ! field_transform )
2020 int extra_parens = 0; // boolean
2022 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2023 if ( ! value_obj ) {
2024 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2026 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
2027 free(field_transform);
2031 } else if ( value_obj->type == JSON_ARRAY ) {
2032 value = searchValueTransform( value_obj );
2034 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
2035 free( field_transform );
2038 } else if ( value_obj->type == JSON_HASH ) {
2039 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2041 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
2042 free(field_transform);
2046 } else if ( value_obj->type == JSON_NUMBER ) {
2047 value = jsonNumberToDBString( field, value_obj );
2048 } else if ( value_obj->type == JSON_NULL ) {
2049 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
2050 free(field_transform);
2052 } else if ( value_obj->type == JSON_BOOL ) {
2053 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
2054 free(field_transform);
2057 if ( !strcmp( get_primitive( field ), "number") ) {
2058 value = jsonNumberToDBString( field, value_obj );
2060 value = jsonObjectToSimpleString( value_obj );
2061 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2062 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
2064 free(field_transform);
2070 const char* left_parens = "";
2071 const char* right_parens = "";
2073 if( extra_parens ) {
2078 growing_buffer* sql_buf = buffer_init(32);
2082 "%s%s %s %s %s %s%s",
2093 free(field_transform);
2095 return buffer_release(sql_buf);
2098 static char* searchSimplePredicate (const char* op, const char* class_alias,
2099 osrfHash* field, const jsonObject* node) {
2101 if( ! is_good_operator( op ) ) {
2102 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2108 // Get the value to which we are comparing the specified column
2109 if (node->type != JSON_NULL) {
2110 if ( node->type == JSON_NUMBER ) {
2111 val = jsonNumberToDBString( field, node );
2112 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2113 val = jsonNumberToDBString( field, node );
2115 val = jsonObjectToSimpleString(node);
2120 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2121 // Value is not numeric; enclose it in quotes
2122 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2123 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2129 // Compare to a null value
2130 val = strdup( "NULL" );
2131 if (strcmp( op, "=" ))
2137 growing_buffer* sql_buf = buffer_init(32);
2138 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2139 char* pred = buffer_release( sql_buf );
2146 static char* searchBETWEENPredicate (const char* class_alias,
2147 osrfHash* field, const jsonObject* node) {
2149 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2150 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2152 if( NULL == y_node ) {
2153 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2156 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2157 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2164 if ( !strcmp( get_primitive( field ), "number") ) {
2165 x_string = jsonNumberToDBString(field, x_node);
2166 y_string = jsonNumberToDBString(field, y_node);
2169 x_string = jsonObjectToSimpleString(x_node);
2170 y_string = jsonObjectToSimpleString(y_node);
2171 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2172 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2173 MODULENAME, x_string, y_string);
2180 growing_buffer* sql_buf = buffer_init(32);
2181 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2182 class_alias, osrfHashGet(field, "name"), x_string, y_string );
2186 return buffer_release(sql_buf);
2189 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2190 jsonObject* node, osrfMethodContext* ctx ) {
2193 if (node->type == JSON_ARRAY) { // equality IN search
2194 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2195 } else if (node->type == JSON_HASH) { // other search
2196 jsonIterator* pred_itr = jsonNewIterator( node );
2197 if( !jsonIteratorHasNext( pred_itr ) ) {
2198 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2199 MODULENAME, osrfHashGet(field, "name") );
2201 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2203 // Verify that there are no additional predicates
2204 if( jsonIteratorHasNext( pred_itr ) ) {
2205 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2206 MODULENAME, osrfHashGet(field, "name") );
2207 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2208 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2209 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2210 pred = searchINPredicate( class_info->alias, field, pred_node, pred_itr->key, ctx );
2211 else if ( pred_node->type == JSON_ARRAY )
2212 pred = searchFunctionPredicate( class_info->alias, field, pred_node, pred_itr->key );
2213 else if ( pred_node->type == JSON_HASH )
2214 pred = searchFieldTransformPredicate( class_info, field, pred_node, pred_itr->key );
2216 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2218 jsonIteratorFree(pred_itr);
2220 } else if (node->type == JSON_NULL) { // IS NULL search
2221 growing_buffer* _p = buffer_init(64);
2224 "\"%s\".%s IS NULL",
2225 class_info->class_name,
2226 osrfHashGet(field, "name")
2228 pred = buffer_release(_p);
2229 } else { // equality search
2230 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2249 field : call_number,
2265 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2267 const jsonObject* working_hash;
2268 jsonObject* freeable_hash = NULL;
2270 if (join_hash->type == JSON_HASH) {
2271 working_hash = join_hash;
2272 } else if (join_hash->type == JSON_STRING) {
2273 // turn it into a JSON_HASH by creating a wrapper
2274 // around a copy of the original
2275 const char* _tmp = jsonObjectGetString( join_hash );
2276 freeable_hash = jsonNewObjectType(JSON_HASH);
2277 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2278 working_hash = freeable_hash;
2282 "%s: JOIN failed; expected JSON object type not found",
2288 growing_buffer* join_buf = buffer_init(128);
2289 const char* leftclass = left_info->class_name;
2291 jsonObject* snode = NULL;
2292 jsonIterator* search_itr = jsonNewIterator( working_hash );
2294 while ( (snode = jsonIteratorNext( search_itr )) ) {
2295 const char* right_alias = search_itr->key;
2297 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2299 class = right_alias;
2301 const ClassInfo* right_info = add_joined_class( right_alias, class );
2305 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2309 jsonIteratorFree( search_itr );
2310 buffer_free( join_buf );
2312 jsonObjectFree( freeable_hash );
2315 osrfHash* links = right_info->links;
2316 const char* table = right_info->source_def;
2318 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2319 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2321 if (field && !fkey) {
2322 // Look up the corresponding join column in the IDL.
2323 // The link must be defined in the child table,
2324 // and point to the right parent table.
2325 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2326 const char* reltype = NULL;
2327 const char* other_class = NULL;
2328 reltype = osrfHashGet( idl_link, "reltype" );
2329 if( reltype && strcmp( reltype, "has_many" ) )
2330 other_class = osrfHashGet( idl_link, "class" );
2331 if( other_class && !strcmp( other_class, leftclass ) )
2332 fkey = osrfHashGet( idl_link, "key" );
2336 "%s: JOIN failed. No link defined from %s.%s to %s",
2342 buffer_free(join_buf);
2344 jsonObjectFree(freeable_hash);
2345 jsonIteratorFree(search_itr);
2349 } else if (!field && fkey) {
2350 // Look up the corresponding join column in the IDL.
2351 // The link must be defined in the child table,
2352 // and point to the right parent table.
2353 osrfHash* left_links = left_info->links;
2354 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2355 const char* reltype = NULL;
2356 const char* other_class = NULL;
2357 reltype = osrfHashGet( idl_link, "reltype" );
2358 if( reltype && strcmp( reltype, "has_many" ) )
2359 other_class = osrfHashGet( idl_link, "class" );
2360 if( other_class && !strcmp( other_class, class ) )
2361 field = osrfHashGet( idl_link, "key" );
2365 "%s: JOIN failed. No link defined from %s.%s to %s",
2371 buffer_free(join_buf);
2373 jsonObjectFree(freeable_hash);
2374 jsonIteratorFree(search_itr);
2378 } else if (!field && !fkey) {
2379 osrfHash* left_links = left_info->links;
2381 // For each link defined for the left class:
2382 // see if the link references the joined class
2383 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2384 osrfHash* curr_link = NULL;
2385 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2386 const char* other_class = osrfHashGet( curr_link, "class" );
2387 if( other_class && !strcmp( other_class, class ) ) {
2389 // In the IDL, the parent class doesn't know then names of the child
2390 // columns that are pointing to it, so don't use that end of the link
2391 const char* reltype = osrfHashGet( curr_link, "reltype" );
2392 if( reltype && strcmp( reltype, "has_many" ) ) {
2393 // Found a link between the classes
2394 fkey = osrfHashIteratorKey( itr );
2395 field = osrfHashGet( curr_link, "key" );
2400 osrfHashIteratorFree( itr );
2402 if (!field || !fkey) {
2403 // Do another such search, with the classes reversed
2405 // For each link defined for the joined class:
2406 // see if the link references the left class
2407 osrfHashIterator* itr = osrfNewHashIterator( links );
2408 osrfHash* curr_link = NULL;
2409 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2410 const char* other_class = osrfHashGet( curr_link, "class" );
2411 if( other_class && !strcmp( other_class, leftclass ) ) {
2413 // In the IDL, the parent class doesn't know then names of the child
2414 // columns that are pointing to it, so don't use that end of the link
2415 const char* reltype = osrfHashGet( curr_link, "reltype" );
2416 if( reltype && strcmp( reltype, "has_many" ) ) {
2417 // Found a link between the classes
2418 field = osrfHashIteratorKey( itr );
2419 fkey = osrfHashGet( curr_link, "key" );
2424 osrfHashIteratorFree( itr );
2427 if (!field || !fkey) {
2430 "%s: JOIN failed. No link defined between %s and %s",
2435 buffer_free(join_buf);
2437 jsonObjectFree(freeable_hash);
2438 jsonIteratorFree(search_itr);
2444 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2446 if ( !strcasecmp(type,"left") ) {
2447 buffer_add(join_buf, " LEFT JOIN");
2448 } else if ( !strcasecmp(type,"right") ) {
2449 buffer_add(join_buf, " RIGHT JOIN");
2450 } else if ( !strcasecmp(type,"full") ) {
2451 buffer_add(join_buf, " FULL JOIN");
2453 buffer_add(join_buf, " INNER JOIN");
2456 buffer_add(join_buf, " INNER JOIN");
2459 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2460 table, right_alias, right_alias, field, left_info->alias, fkey);
2462 // Add any other join conditions as specified by "filter"
2463 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2465 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2466 if ( filter_op && !strcasecmp("or",filter_op) ) {
2467 buffer_add( join_buf, " OR " );
2469 buffer_add( join_buf, " AND " );
2472 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2474 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2475 OSRF_BUFFER_ADD( join_buf, jpred );
2480 "%s: JOIN failed. Invalid conditional expression.",
2483 jsonIteratorFree( search_itr );
2484 buffer_free( join_buf );
2486 jsonObjectFree( freeable_hash );
2491 buffer_add(join_buf, " ) ");
2493 // Recursively add a nested join, if one is present
2494 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2496 char* jpred = searchJOIN( join_filter, right_info );
2498 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2499 OSRF_BUFFER_ADD( join_buf, jpred );
2502 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2503 jsonIteratorFree( search_itr );
2504 buffer_free( join_buf );
2506 jsonObjectFree( freeable_hash );
2513 jsonObjectFree(freeable_hash);
2514 jsonIteratorFree(search_itr);
2516 return buffer_release(join_buf);
2521 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2522 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2523 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2525 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2527 search_hash is the JSON expression of the conditions.
2528 meta is the class definition from the IDL, for the relevant table.
2529 opjoin_type indicates whether multiple conditions, if present, should be
2530 connected by AND or OR.
2531 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2532 to pass it to other functions -- and all they do with it is to use the session
2533 and request members to send error messages back to the client.
2537 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
2538 int opjoin_type, osrfMethodContext* ctx ) {
2542 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2545 class_info->class_def,
2550 growing_buffer* sql_buf = buffer_init(128);
2552 jsonObject* node = NULL;
2555 if ( search_hash->type == JSON_ARRAY ) {
2556 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2557 if( 0 == search_hash->size ) {
2560 "%s: Invalid predicate structure: empty JSON array",
2563 buffer_free( sql_buf );
2567 unsigned long i = 0;
2568 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
2572 if (opjoin_type == OR_OP_JOIN)
2573 buffer_add(sql_buf, " OR ");
2575 buffer_add(sql_buf, " AND ");
2578 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2580 buffer_free( sql_buf );
2584 buffer_fadd(sql_buf, "( %s )", subpred);
2588 } else if ( search_hash->type == JSON_HASH ) {
2589 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2590 jsonIterator* search_itr = jsonNewIterator( search_hash );
2591 if( !jsonIteratorHasNext( search_itr ) ) {
2594 "%s: Invalid predicate structure: empty JSON object",
2597 jsonIteratorFree( search_itr );
2598 buffer_free( sql_buf );
2602 while ( (node = jsonIteratorNext( search_itr )) ) {
2607 if (opjoin_type == OR_OP_JOIN)
2608 buffer_add(sql_buf, " OR ");
2610 buffer_add(sql_buf, " AND ");
2613 if ( '+' == search_itr->key[ 0 ] ) {
2615 // This plus sign prefixes a class name or other table alias;
2616 // make sure the table alias is in scope
2617 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
2618 if( ! alias_info ) {
2621 "%s: Invalid table alias \"%s\" in WHERE clause",
2625 jsonIteratorFree( search_itr );
2626 buffer_free( sql_buf );
2630 if ( node->type == JSON_STRING ) {
2631 // It's the name of a column; make sure it belongs to the class
2632 const char* fieldname = jsonObjectGetString( node );
2633 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
2636 "%s: Invalid column name \"%s\" in WHERE clause for table alias \"%s\"",
2641 jsonIteratorFree( search_itr );
2642 buffer_free( sql_buf );
2646 buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
2648 // It's something more complicated
2649 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
2651 jsonIteratorFree( search_itr );
2652 buffer_free( sql_buf );
2656 buffer_fadd(sql_buf, "( %s )", subpred);
2659 } else if ( '-' == search_itr->key[ 0 ] ) {
2660 if ( !strcasecmp("-or",search_itr->key) ) {
2661 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
2663 jsonIteratorFree( search_itr );
2664 buffer_free( sql_buf );
2668 buffer_fadd(sql_buf, "( %s )", subpred);
2670 } else if ( !strcasecmp("-and",search_itr->key) ) {
2671 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2673 jsonIteratorFree( search_itr );
2674 buffer_free( sql_buf );
2678 buffer_fadd(sql_buf, "( %s )", subpred);
2680 } else if ( !strcasecmp("-not",search_itr->key) ) {
2681 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2683 jsonIteratorFree( search_itr );
2684 buffer_free( sql_buf );
2688 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2690 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2691 char* subpred = SELECT(
2693 jsonObjectGetKey( node, "select" ),
2694 jsonObjectGetKey( node, "from" ),
2695 jsonObjectGetKey( node, "where" ),
2696 jsonObjectGetKey( node, "having" ),
2697 jsonObjectGetKey( node, "order_by" ),
2698 jsonObjectGetKey( node, "limit" ),
2699 jsonObjectGetKey( node, "offset" ),
2705 jsonIteratorFree( search_itr );
2706 buffer_free( sql_buf );
2710 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2712 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2713 char* subpred = SELECT(
2715 jsonObjectGetKey( node, "select" ),
2716 jsonObjectGetKey( node, "from" ),
2717 jsonObjectGetKey( node, "where" ),
2718 jsonObjectGetKey( node, "having" ),
2719 jsonObjectGetKey( node, "order_by" ),
2720 jsonObjectGetKey( node, "limit" ),
2721 jsonObjectGetKey( node, "offset" ),
2727 jsonIteratorFree( search_itr );
2728 buffer_free( sql_buf );
2732 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2734 } else { // Invalid "minus" operator
2737 "%s: Invalid operator \"%s\" in WHERE clause",
2741 jsonIteratorFree( search_itr );
2742 buffer_free( sql_buf );
2748 const char* class = class_info->class_name;
2749 osrfHash* fields = class_info->fields;
2750 osrfHash* field = osrfHashGet( fields, search_itr->key );
2753 const char* table = class_info->source_def;
2756 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2759 table ? table : "?",
2762 jsonIteratorFree(search_itr);
2763 buffer_free(sql_buf);
2767 char* subpred = searchPredicate( class_info, field, node, ctx );
2769 buffer_free(sql_buf);
2770 jsonIteratorFree(search_itr);
2774 buffer_add( sql_buf, subpred );
2778 jsonIteratorFree(search_itr);
2781 // ERROR ... only hash and array allowed at this level
2782 char* predicate_string = jsonObjectToJSON( search_hash );
2785 "%s: Invalid predicate structure: %s",
2789 buffer_free(sql_buf);
2790 free(predicate_string);
2794 return buffer_release(sql_buf);
2797 /* Build a JSON_ARRAY of field names for a given table alias
2799 static jsonObject* defaultSelectList( const char* table_alias ) {
2804 ClassInfo* class_info = search_all_alias( table_alias );
2805 if( ! class_info ) {
2808 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
2815 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
2816 osrfHash* field_def = NULL;
2817 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
2818 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2819 const char* field_name = osrfHashIteratorKey( field_itr );
2820 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2821 jsonObjectPush( array, jsonNewObject( field_name ) );
2824 osrfHashIteratorFree( field_itr );
2830 /* method context */ osrfMethodContext* ctx,
2832 /* SELECT */ jsonObject* selhash,
2833 /* FROM */ jsonObject* join_hash,
2834 /* WHERE */ jsonObject* search_hash,
2835 /* HAVING */ jsonObject* having_hash,
2836 /* ORDER BY */ jsonObject* order_hash,
2837 /* LIMIT */ jsonObject* limit,
2838 /* OFFSET */ jsonObject* offset,
2839 /* flags */ int flags
2841 const char* locale = osrf_message_get_last_locale();
2843 // general tmp objects
2844 const jsonObject* tmp_const;
2845 jsonObject* selclass = NULL;
2846 jsonObject* snode = NULL;
2847 jsonObject* onode = NULL;
2849 char* string = NULL;
2850 int from_function = 0;
2855 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale);
2857 // punt if there's no FROM clause
2858 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2861 "%s: FROM clause is missing or empty",
2865 osrfAppSessionStatus(
2867 OSRF_STATUS_INTERNALSERVERERROR,
2868 "osrfMethodException",
2870 "FROM clause is missing or empty in JSON query"
2875 // Push a node onto the stack for the current query. Every level of
2876 // subquery gets its own QueryFrame on the Stack.
2879 // the core search class
2880 const char* core_class = NULL;
2882 // get the core class -- the only key of the top level FROM clause, or a string
2883 if (join_hash->type == JSON_HASH) {
2884 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2885 snode = jsonIteratorNext( tmp_itr );
2887 // Populate the current QueryFrame with information
2888 // about the core class
2889 if( add_query_core( NULL, tmp_itr->key ) ) {
2891 osrfAppSessionStatus(
2893 OSRF_STATUS_INTERNALSERVERERROR,
2894 "osrfMethodException",
2896 "Unable to look up core class"
2900 core_class = curr_query->core.class_name;
2903 jsonObject* extra = jsonIteratorNext( tmp_itr );
2905 jsonIteratorFree( tmp_itr );
2908 // There shouldn't be more than one entry in join_hash
2912 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2916 osrfAppSessionStatus(
2918 OSRF_STATUS_INTERNALSERVERERROR,
2919 "osrfMethodException",
2921 "Malformed FROM clause in JSON query"
2923 return NULL; // Malformed join_hash; extra entry
2925 } else if (join_hash->type == JSON_ARRAY) {
2926 // We're selecting from a function, not from a table
2928 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
2931 } else if (join_hash->type == JSON_STRING) {
2932 // Populate the current QueryFrame with information
2933 // about the core class
2934 core_class = jsonObjectGetString( join_hash );
2936 if( add_query_core( NULL, core_class ) ) {
2938 osrfAppSessionStatus(
2940 OSRF_STATUS_INTERNALSERVERERROR,
2941 "osrfMethodException",
2943 "Unable to look up core class"
2951 "%s: FROM clause is unexpected JSON type: %s",
2953 json_type( join_hash->type )
2956 osrfAppSessionStatus(
2958 OSRF_STATUS_INTERNALSERVERERROR,
2959 "osrfMethodException",
2961 "Ill-formed FROM clause in JSON query"
2966 // Build the join clause, if any, while filling out the list
2967 // of joined classes in the current QueryFrame.
2968 char* join_clause = NULL;
2969 if( join_hash && ! from_function ) {
2971 join_clause = searchJOIN( join_hash, &curr_query->core );
2972 if( ! join_clause ) {
2974 osrfAppSessionStatus(
2976 OSRF_STATUS_INTERNALSERVERERROR,
2977 "osrfMethodException",
2979 "Unable to construct JOIN clause(s)"
2985 // For in case we don't get a select list
2986 jsonObject* defaultselhash = NULL;
2988 // if there is no select list, build a default select list ...
2989 if (!selhash && !from_function) {
2990 jsonObject* default_list = defaultSelectList( core_class );
2991 if( ! default_list ) {
2993 osrfAppSessionStatus(
2995 OSRF_STATUS_INTERNALSERVERERROR,
2996 "osrfMethodException",
2998 "Unable to build default SELECT clause in JSON query"
3000 free( join_clause );
3005 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
3006 jsonObjectSetKey( selhash, core_class, default_list );
3009 // The SELECT clause can be encoded only by a hash
3010 if( !from_function && selhash->type != JSON_HASH ) {
3013 "%s: Expected JSON_HASH for SELECT clause; found %s",
3015 json_type( selhash->type )
3019 osrfAppSessionStatus(
3021 OSRF_STATUS_INTERNALSERVERERROR,
3022 "osrfMethodException",
3024 "Malformed SELECT clause in JSON query"
3026 free( join_clause );
3030 // If you see a null or wild card specifier for the core class, or an
3031 // empty array, replace it with a default SELECT list
3032 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3034 int default_needed = 0; // boolean
3035 if( JSON_STRING == tmp_const->type
3036 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3038 else if( JSON_NULL == tmp_const->type )
3041 if( default_needed ) {
3042 // Build a default SELECT list
3043 jsonObject* default_list = defaultSelectList( core_class );
3044 if( ! default_list ) {
3046 osrfAppSessionStatus(
3048 OSRF_STATUS_INTERNALSERVERERROR,
3049 "osrfMethodException",
3051 "Can't build default SELECT clause in JSON query"
3053 free( join_clause );
3058 jsonObjectSetKey( selhash, core_class, default_list );
3062 // temp buffers for the SELECT list and GROUP BY clause
3063 growing_buffer* select_buf = buffer_init(128);
3064 growing_buffer* group_buf = buffer_init(128);
3066 int aggregate_found = 0; // boolean
3068 // Build a select list
3069 if(from_function) // From a function we select everything
3070 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3073 // Build the SELECT list as SQL
3077 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3078 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3080 const char* cname = selclass_itr->key;
3082 // Make sure the target relation is in the FROM clause.
3084 // At this point join_hash is a step down from the join_hash we
3085 // received as a parameter. If the original was a JSON_STRING,
3086 // then json_hash is now NULL. If the original was a JSON_HASH,
3087 // then json_hash is now the first (and only) entry in it,
3088 // denoting the core class. We've already excluded the
3089 // possibility that the original was a JSON_ARRAY, because in
3090 // that case from_function would be non-NULL, and we wouldn't
3093 // If the current table alias isn't in scope, 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 unsigned long field_idx = 0;
3159 jsonObject* selfield = NULL;
3160 while((selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3162 // If we need a separator comma, add one
3166 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3169 // if the field specification is a string, add it to the list
3170 if (selfield->type == JSON_STRING) {
3172 // Look up the field in the IDL
3173 const char* col_name = jsonObjectGetString( selfield );
3174 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3176 // No such field in current class
3179 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3185 osrfAppSessionStatus(
3187 OSRF_STATUS_INTERNALSERVERERROR,
3188 "osrfMethodException",
3190 "Selected column not defined in JSON query"
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( selclass_itr );
3216 buffer_free( select_buf );
3217 buffer_free( group_buf );
3218 if( defaultselhash ) jsonObjectFree( defaultselhash );
3219 free( join_clause );
3225 if (flags & DISABLE_I18N)
3228 i18n = osrfHashGet(field_def, "i18n");
3230 if( str_is_true( i18n ) ) {
3231 buffer_fadd( select_buf,
3232 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3233 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3235 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3238 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3241 // ... but it could be an object, in which case we check for a Field Transform
3242 } else if (selfield->type == JSON_HASH) {
3244 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3246 // Get the field definition from the IDL
3247 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3249 // No such field in current class
3252 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3258 osrfAppSessionStatus(
3260 OSRF_STATUS_INTERNALSERVERERROR,
3261 "osrfMethodException",
3263 "Selected column is not defined in JSON query"
3265 jsonIteratorFree( selclass_itr );
3266 buffer_free( select_buf );
3267 buffer_free( group_buf );
3268 if( defaultselhash ) jsonObjectFree( defaultselhash );
3269 free( join_clause );
3271 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3272 // No such field in current class
3275 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3281 osrfAppSessionStatus(
3283 OSRF_STATUS_INTERNALSERVERERROR,
3284 "osrfMethodException",
3286 "Selected column is virtual in JSON query"
3288 jsonIteratorFree( selclass_itr );
3289 buffer_free( select_buf );
3290 buffer_free( group_buf );
3291 if( defaultselhash ) jsonObjectFree( defaultselhash );
3292 free( join_clause );
3296 // Decide what to use as a column alias
3298 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3299 _alias = jsonObjectGetString( tmp_const );
3300 } else { // Use field name as the alias
3304 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3305 char* transform_str = searchFieldTransform(class_info->alias, field_def, selfield);
3306 if( transform_str ) {
3307 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3308 free(transform_str);
3311 osrfAppSessionStatus(
3313 OSRF_STATUS_INTERNALSERVERERROR,
3314 "osrfMethodException",
3316 "Unable to generate transform function in JSON query"
3318 jsonIteratorFree( selclass_itr );
3319 buffer_free( select_buf );
3320 buffer_free( group_buf );
3321 if( defaultselhash ) jsonObjectFree( defaultselhash );
3322 free( join_clause );
3329 if (flags & DISABLE_I18N)
3332 i18n = osrfHashGet(field_def, "i18n");
3334 if( str_is_true( i18n ) ) {
3335 buffer_fadd( select_buf,
3336 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3337 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3339 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3342 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3349 "%s: Selected item is unexpected JSON type: %s",
3351 json_type( selfield->type )
3354 osrfAppSessionStatus(
3356 OSRF_STATUS_INTERNALSERVERERROR,
3357 "osrfMethodException",
3359 "Ill-formed SELECT item in JSON query"
3361 jsonIteratorFree( selclass_itr );
3362 buffer_free( select_buf );
3363 buffer_free( group_buf );
3364 if( defaultselhash ) jsonObjectFree( defaultselhash );
3365 free( join_clause );
3369 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3370 if( obj_is_true( agg_obj ) )
3371 aggregate_found = 1;
3373 // Append a comma (except for the first one)
3374 // and add the column to a GROUP BY clause
3378 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3380 buffer_fadd(group_buf, " %d", sel_pos);
3384 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3386 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3387 if ( ! obj_is_true( aggregate_obj ) ) {
3391 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3394 buffer_fadd(group_buf, " %d", sel_pos);
3397 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3401 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3404 _column = searchFieldTransform(class_info->alias, field, selfield);
3405 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3406 OSRF_BUFFER_ADD(group_buf, _column);
3407 _column = searchFieldTransform(class_info->alias, field, selfield);
3414 } // end while -- iterating across SELECT columns
3416 } // end while -- iterating across classes
3418 jsonIteratorFree(selclass_itr);
3422 char* col_list = buffer_release(select_buf);
3424 // Make sure the SELECT list isn't empty. This can happen, for example,
3425 // if we try to build a default SELECT clause from a non-core table.
3428 osrfLogError(OSRF_LOG_MARK, "%s: SELECT clause is empty", MODULENAME );
3430 osrfAppSessionStatus(
3432 OSRF_STATUS_INTERNALSERVERERROR,
3433 "osrfMethodException",
3435 "SELECT list is empty"
3438 buffer_free( group_buf );
3439 if( defaultselhash ) jsonObjectFree( defaultselhash );
3440 free( join_clause );
3445 if (from_function) table = searchValueTransform(join_hash);
3446 else table = strdup( curr_query->core.source_def );
3450 osrfAppSessionStatus(
3452 OSRF_STATUS_INTERNALSERVERERROR,
3453 "osrfMethodException",
3455 "Unable to identify table for core class"
3458 buffer_free( group_buf );
3459 if( defaultselhash ) jsonObjectFree( defaultselhash );
3460 free( join_clause );
3464 // Put it all together
3465 growing_buffer* sql_buf = buffer_init(128);
3466 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3470 // Append the join clause, if any
3472 buffer_add(sql_buf, join_clause);
3476 char* order_by_list = NULL;
3477 char* having_buf = NULL;
3479 if (!from_function) {
3481 // Build a WHERE clause, if there is one
3482 if ( search_hash ) {
3483 buffer_add(sql_buf, " WHERE ");
3485 // and it's on the WHERE clause
3486 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
3489 osrfAppSessionStatus(
3491 OSRF_STATUS_INTERNALSERVERERROR,
3492 "osrfMethodException",
3494 "Severe query error in WHERE predicate -- see error log for more details"
3497 buffer_free(group_buf);
3498 buffer_free(sql_buf);
3499 if (defaultselhash) jsonObjectFree(defaultselhash);
3503 buffer_add(sql_buf, pred);
3507 // Build a HAVING clause, if there is one
3508 if ( having_hash ) {
3510 // and it's on the the WHERE clause
3511 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
3513 if( ! having_buf ) {
3515 osrfAppSessionStatus(
3517 OSRF_STATUS_INTERNALSERVERERROR,
3518 "osrfMethodException",
3520 "Severe query error in HAVING predicate -- see error log for more details"
3523 buffer_free(group_buf);
3524 buffer_free(sql_buf);
3525 if (defaultselhash) jsonObjectFree(defaultselhash);
3530 growing_buffer* order_buf = NULL; // to collect ORDER BY list
3532 // Build an ORDER BY clause, if there is one
3533 if( NULL == order_hash )
3534 ; // No ORDER BY? do nothing
3535 else if( JSON_ARRAY == order_hash->type ) {
3536 // Array of field specifications, each specification being a
3537 // hash to define the class, field, and other details
3539 jsonObject* order_spec;
3540 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
3542 if( JSON_HASH != order_spec->type ) {
3543 osrfLogError(OSRF_LOG_MARK,
3544 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
3545 MODULENAME, json_type( order_spec->type ) );
3547 osrfAppSessionStatus(
3549 OSRF_STATUS_INTERNALSERVERERROR,
3550 "osrfMethodException",
3552 "Malformed ORDER BY clause -- see error log for more details"
3554 buffer_free( order_buf );
3556 buffer_free(group_buf);
3557 buffer_free(sql_buf);
3558 if (defaultselhash) jsonObjectFree(defaultselhash);
3562 const char* class_alias =
3563 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
3565 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
3568 OSRF_BUFFER_ADD(order_buf, ", ");
3570 order_buf = buffer_init(128);
3572 if( !field || !class_alias ) {
3573 osrfLogError(OSRF_LOG_MARK,
3574 "%s: Missing class or field name in field specification of ORDER BY clause",
3577 osrfAppSessionStatus(
3579 OSRF_STATUS_INTERNALSERVERERROR,
3580 "osrfMethodException",
3582 "Malformed ORDER BY clause -- see error log for more details"
3584 buffer_free( order_buf );
3586 buffer_free(group_buf);
3587 buffer_free(sql_buf);
3588 if (defaultselhash) jsonObjectFree(defaultselhash);
3592 ClassInfo* order_class_info = search_alias( class_alias );
3593 if( ! order_class_info ) {
3594 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
3595 "not in FROM clause", MODULENAME, class_alias );
3597 osrfAppSessionStatus(
3599 OSRF_STATUS_INTERNALSERVERERROR,
3600 "osrfMethodException",
3602 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3605 buffer_free(group_buf);
3606 buffer_free(sql_buf);
3607 if (defaultselhash) jsonObjectFree(defaultselhash);
3611 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
3613 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
3614 MODULENAME, class_alias, field );
3616 osrfAppSessionStatus(
3618 OSRF_STATUS_INTERNALSERVERERROR,
3619 "osrfMethodException",
3621 "Invalid field referenced in ORDER BY clause -- see error log for more details"
3624 buffer_free(group_buf);
3625 buffer_free(sql_buf);
3626 if (defaultselhash) jsonObjectFree(defaultselhash);
3628 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3629 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3630 MODULENAME, field );
3632 osrfAppSessionStatus(
3634 OSRF_STATUS_INTERNALSERVERERROR,
3635 "osrfMethodException",
3637 "Virtual field in ORDER BY clause -- see error log for more details"
3639 buffer_free( order_buf );
3641 buffer_free(group_buf);
3642 buffer_free(sql_buf);
3643 if (defaultselhash) jsonObjectFree(defaultselhash);
3647 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
3648 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
3649 if( ! transform_str ) {
3651 osrfAppSessionStatus(
3653 OSRF_STATUS_INTERNALSERVERERROR,
3654 "osrfMethodException",
3656 "Severe query error in ORDER BY clause -- see error log for more details"
3658 buffer_free( order_buf );
3660 buffer_free(group_buf);
3661 buffer_free(sql_buf);
3662 if (defaultselhash) jsonObjectFree(defaultselhash);
3666 OSRF_BUFFER_ADD( order_buf, transform_str );
3667 free( transform_str );
3670 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
3672 const char* direction =
3673 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
3675 if( direction[ 0 ] || 'D' == direction[ 0 ] )
3676 OSRF_BUFFER_ADD( order_buf, " DESC" );
3678 OSRF_BUFFER_ADD( order_buf, " ASC" );
3681 } else if( JSON_HASH == order_hash->type ) {
3682 // This hash is keyed on class alias. Each class has either
3683 // an array of field names or a hash keyed on field name.
3684 jsonIterator* class_itr = jsonNewIterator( order_hash );
3685 while ( (snode = jsonIteratorNext( class_itr )) ) {
3687 ClassInfo* order_class_info = search_alias( class_itr->key );
3688 if( ! order_class_info ) {
3689 osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
3690 MODULENAME, class_itr->key );
3692 osrfAppSessionStatus(
3694 OSRF_STATUS_INTERNALSERVERERROR,
3695 "osrfMethodException",
3697 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3699 jsonIteratorFree( class_itr );
3700 buffer_free( order_buf );
3702 buffer_free(group_buf);
3703 buffer_free(sql_buf);
3704 if (defaultselhash) jsonObjectFree(defaultselhash);
3708 osrfHash* field_list_def = order_class_info->fields;
3710 if ( snode->type == JSON_HASH ) {
3712 // Hash is keyed on field names from the current class. For each field
3713 // there is another layer of hash to define the sorting details, if any,
3714 // or a string to indicate direction of sorting.
3715 jsonIterator* order_itr = jsonNewIterator( snode );
3716 while ( (onode = jsonIteratorNext( order_itr )) ) {
3718 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
3720 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3721 MODULENAME, order_itr->key );
3723 osrfAppSessionStatus(
3725 OSRF_STATUS_INTERNALSERVERERROR,
3726 "osrfMethodException",
3728 "Invalid field in ORDER BY clause -- see error log for more details"
3730 jsonIteratorFree( order_itr );
3731 jsonIteratorFree( class_itr );
3732 buffer_free( order_buf );
3734 buffer_free(group_buf);
3735 buffer_free(sql_buf);
3736 if (defaultselhash) jsonObjectFree(defaultselhash);
3738 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3739 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3740 MODULENAME, order_itr->key );
3742 osrfAppSessionStatus(
3744 OSRF_STATUS_INTERNALSERVERERROR,
3745 "osrfMethodException",
3747 "Virtual field in ORDER BY clause -- see error log for more details"
3749 jsonIteratorFree( order_itr );
3750 jsonIteratorFree( class_itr );
3751 buffer_free( order_buf );
3753 buffer_free(group_buf);
3754 buffer_free(sql_buf);
3755 if (defaultselhash) jsonObjectFree(defaultselhash);
3759 const char* direction = NULL;
3760 if ( onode->type == JSON_HASH ) {
3761 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3762 string = searchFieldTransform(
3764 osrfHashGet( field_list_def, order_itr->key ),
3768 if( ctx ) osrfAppSessionStatus(
3770 OSRF_STATUS_INTERNALSERVERERROR,
3771 "osrfMethodException",
3773 "Severe query error in ORDER BY clause -- see error log for more details"
3775 jsonIteratorFree( order_itr );
3776 jsonIteratorFree( class_itr );
3778 buffer_free(group_buf);
3779 buffer_free(order_buf);
3780 buffer_free(sql_buf);
3781 if (defaultselhash) jsonObjectFree(defaultselhash);
3785 growing_buffer* field_buf = buffer_init(16);
3786 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3787 string = buffer_release(field_buf);
3790 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3791 const char* dir = jsonObjectGetString(tmp_const);
3792 if (!strncasecmp(dir, "d", 1)) {
3793 direction = " DESC";
3799 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
3800 osrfLogError( OSRF_LOG_MARK,
3801 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
3802 MODULENAME, json_type( onode->type ) );
3804 osrfAppSessionStatus(
3806 OSRF_STATUS_INTERNALSERVERERROR,
3807 "osrfMethodException",
3809 "Malformed ORDER BY clause -- see error log for more details"
3811 jsonIteratorFree( order_itr );
3812 jsonIteratorFree( class_itr );
3814 buffer_free(group_buf);
3815 buffer_free(order_buf);
3816 buffer_free(sql_buf);
3817 if (defaultselhash) jsonObjectFree(defaultselhash);
3821 string = strdup(order_itr->key);
3822 const char* dir = jsonObjectGetString(onode);
3823 if (!strncasecmp(dir, "d", 1)) {
3824 direction = " DESC";
3831 OSRF_BUFFER_ADD(order_buf, ", ");
3833 order_buf = buffer_init(128);
3835 OSRF_BUFFER_ADD(order_buf, string);
3839 OSRF_BUFFER_ADD(order_buf, direction);
3843 jsonIteratorFree(order_itr);
3845 } else if ( snode->type == JSON_ARRAY ) {
3847 // Array is a list of fields from the current class
3848 unsigned long order_idx = 0;
3849 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
3851 const char* _f = jsonObjectGetString( onode );
3853 osrfHash* field_def = osrfHashGet( field_list_def, _f );
3855 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3858 osrfAppSessionStatus(
3860 OSRF_STATUS_INTERNALSERVERERROR,
3861 "osrfMethodException",
3863 "Invalid field in ORDER BY clause -- see error log for more details"
3865 jsonIteratorFree( class_itr );
3866 buffer_free( order_buf );
3868 buffer_free(group_buf);
3869 buffer_free(sql_buf);
3870 if (defaultselhash) jsonObjectFree(defaultselhash);
3872 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3873 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3876 osrfAppSessionStatus(
3878 OSRF_STATUS_INTERNALSERVERERROR,
3879 "osrfMethodException",
3881 "Virtual field in ORDER BY clause -- see error log for more details"
3883 jsonIteratorFree( class_itr );
3884 buffer_free( order_buf );
3886 buffer_free(group_buf);
3887 buffer_free(sql_buf);
3888 if (defaultselhash) jsonObjectFree(defaultselhash);
3893 OSRF_BUFFER_ADD(order_buf, ", ");
3895 order_buf = buffer_init(128);
3897 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
3901 // IT'S THE OOOOOOOOOOOLD STYLE!
3903 osrfLogError(OSRF_LOG_MARK,
3904 "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3906 osrfAppSessionStatus(
3908 OSRF_STATUS_INTERNALSERVERERROR,
3909 "osrfMethodException",
3911 "Severe query error -- see error log for more details"
3916 buffer_free(group_buf);
3917 buffer_free(order_buf);
3918 buffer_free(sql_buf);
3919 if (defaultselhash) jsonObjectFree(defaultselhash);
3920 jsonIteratorFree(class_itr);
3924 jsonIteratorFree( class_itr );
3926 osrfLogError(OSRF_LOG_MARK,
3927 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
3928 MODULENAME, json_type( order_hash->type ) );
3930 osrfAppSessionStatus(
3932 OSRF_STATUS_INTERNALSERVERERROR,
3933 "osrfMethodException",
3935 "Malformed ORDER BY clause -- see error log for more details"
3937 buffer_free( order_buf );
3939 buffer_free(group_buf);
3940 buffer_free(sql_buf);
3941 if (defaultselhash) jsonObjectFree(defaultselhash);
3946 order_by_list = buffer_release( order_buf );
3950 string = buffer_release(group_buf);
3952 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
3953 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3954 OSRF_BUFFER_ADD( sql_buf, string );
3959 if( having_buf && *having_buf ) {
3960 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3961 OSRF_BUFFER_ADD( sql_buf, having_buf );
3965 if( order_by_list ) {
3967 if ( *order_by_list ) {
3968 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3969 OSRF_BUFFER_ADD( sql_buf, order_by_list );
3972 free( order_by_list );
3976 const char* str = jsonObjectGetString(limit);
3977 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
3981 const char* str = jsonObjectGetString(offset);
3982 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
3985 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3987 if (defaultselhash) jsonObjectFree(defaultselhash);
3989 return buffer_release(sql_buf);
3991 } // end of SELECT()
3993 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
3995 const char* locale = osrf_message_get_last_locale();
3997 osrfHash* fields = osrfHashGet(meta, "fields");
3998 char* core_class = osrfHashGet(meta, "classname");
4000 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4002 jsonObject* node = NULL;
4003 jsonObject* snode = NULL;
4004 jsonObject* onode = NULL;
4005 const jsonObject* _tmp = NULL;
4006 jsonObject* selhash = NULL;
4007 jsonObject* defaultselhash = NULL;
4009 growing_buffer* sql_buf = buffer_init(128);
4010 growing_buffer* select_buf = buffer_init(128);
4012 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4013 defaultselhash = jsonNewObjectType(JSON_HASH);
4014 selhash = defaultselhash;
4017 // If there's no SELECT list for the core class, build one
4018 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
4019 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4021 // Add every non-virtual field to the field list
4022 osrfHash* field_def = NULL;
4023 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4024 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4025 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4026 const char* field = osrfHashIteratorKey( field_itr );
4027 jsonObjectPush( field_list, jsonNewObject( field ) );
4030 osrfHashIteratorFree( field_itr );
4031 jsonObjectSetKey( selhash, core_class, field_list );
4035 jsonIterator* class_itr = jsonNewIterator( selhash );
4036 while ( (snode = jsonIteratorNext( class_itr )) ) {
4038 const char* cname = class_itr->key;
4039 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4040 if (!idlClass) continue;
4042 if (strcmp(core_class,class_itr->key)) {
4043 if (!join_hash) continue;
4045 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
4047 jsonObjectFree(found);
4051 jsonObjectFree(found);
4054 jsonIterator* select_itr = jsonNewIterator( snode );
4055 while ( (node = jsonIteratorNext( select_itr )) ) {
4056 const char* item_str = jsonObjectGetString( node );
4057 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4058 char* fname = osrfHashGet(field, "name");
4060 if (!field) continue;
4065 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
4070 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4071 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4074 i18n = osrfHashGet(field, "i18n");
4076 if( str_is_true( i18n ) ) {
4077 char* pkey = osrfHashGet(idlClass, "primarykey");
4078 char* tname = osrfHashGet(idlClass, "tablename");
4080 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);
4082 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4085 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4089 jsonIteratorFree(select_itr);
4092 jsonIteratorFree(class_itr);
4094 char* col_list = buffer_release(select_buf);
4095 char* table = getSourceDefinition(meta);
4097 table = strdup( "(null)" );
4099 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4103 // Clear the query stack (as a fail-safe precaution against possible
4104 // leftover garbage); then push the first query frame onto the stack.
4105 clear_query_stack();
4107 if( add_query_core( NULL, core_class ) ) {
4109 osrfAppSessionStatus(
4111 OSRF_STATUS_INTERNALSERVERERROR,
4112 "osrfMethodException",
4114 "Unable to build query frame for core class"
4120 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4121 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
4122 OSRF_BUFFER_ADD(sql_buf, join_clause);
4126 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4127 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
4129 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
4131 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4133 osrfAppSessionStatus(
4135 OSRF_STATUS_INTERNALSERVERERROR,
4136 "osrfMethodException",
4138 "Severe query error -- see error log for more details"
4140 buffer_free(sql_buf);
4141 if(defaultselhash) jsonObjectFree(defaultselhash);
4142 clear_query_stack();
4145 buffer_add(sql_buf, pred);
4150 char* string = NULL;
4151 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4153 growing_buffer* order_buf = buffer_init(128);
4156 jsonIterator* class_itr = jsonNewIterator( _tmp );
4157 while ( (snode = jsonIteratorNext( class_itr )) ) {
4159 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4162 if ( snode->type == JSON_HASH ) {
4164 jsonIterator* order_itr = jsonNewIterator( snode );
4165 while ( (onode = jsonIteratorNext( order_itr )) ) {
4167 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4168 class_itr->key, order_itr->key );
4172 char* direction = NULL;
4173 if ( onode->type == JSON_HASH ) {
4174 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4175 string = searchFieldTransform( class_itr->key, field_def, onode );
4177 osrfAppSessionStatus(
4179 OSRF_STATUS_INTERNALSERVERERROR,
4180 "osrfMethodException",
4182 "Severe query error in ORDER BY clause -- see error log for more details"
4184 jsonIteratorFree( order_itr );
4185 jsonIteratorFree( class_itr );
4186 buffer_free( order_buf );
4187 buffer_free( sql_buf );
4188 if( defaultselhash ) jsonObjectFree( defaultselhash );
4189 clear_query_stack();
4193 growing_buffer* field_buf = buffer_init(16);
4194 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4195 string = buffer_release(field_buf);
4198 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4199 const char* dir = jsonObjectGetString(_tmp);
4200 if (!strncasecmp(dir, "d", 1)) {
4201 direction = " DESC";
4208 string = strdup(order_itr->key);
4209 const char* dir = jsonObjectGetString(onode);
4210 if (!strncasecmp(dir, "d", 1)) {
4211 direction = " DESC";
4220 buffer_add(order_buf, ", ");
4223 buffer_add(order_buf, string);
4227 buffer_add(order_buf, direction);
4232 jsonIteratorFree(order_itr);
4235 const char* str = jsonObjectGetString(snode);
4236 buffer_add(order_buf, str);
4242 jsonIteratorFree(class_itr);
4244 string = buffer_release(order_buf);
4247 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4248 OSRF_BUFFER_ADD( sql_buf, string );
4254 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4255 const char* str = jsonObjectGetString(_tmp);
4263 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4265 const char* str = jsonObjectGetString(_tmp);
4274 if (defaultselhash) jsonObjectFree(defaultselhash);
4275 clear_query_stack();
4277 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4278 return buffer_release(sql_buf);
4281 int doJSONSearch ( osrfMethodContext* ctx ) {
4282 if(osrfMethodVerifyContext( ctx )) {
4283 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4287 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
4292 dbhandle = writehandle;
4294 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4298 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4299 flags |= SELECT_DISTINCT;
4301 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4302 flags |= DISABLE_I18N;
4304 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4307 jsonObjectGetKey( hash, "select" ),
4308 jsonObjectGetKey( hash, "from" ),
4309 jsonObjectGetKey( hash, "where" ),
4310 jsonObjectGetKey( hash, "having" ),
4311 jsonObjectGetKey( hash, "order_by" ),
4312 jsonObjectGetKey( hash, "limit" ),
4313 jsonObjectGetKey( hash, "offset" ),
4316 clear_query_stack();
4323 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4324 dbi_result result = dbi_conn_query(dbhandle, sql);
4327 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4329 if (dbi_result_first_row(result)) {
4330 /* JSONify the result */
4331 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4334 jsonObject* return_val = oilsMakeJSONFromResult( result );
4335 osrfAppRespond( ctx, return_val );
4336 jsonObjectFree( return_val );
4337 } while (dbi_result_next_row(result));
4340 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4343 osrfAppRespondComplete( ctx, NULL );
4345 /* clean up the query */
4346 dbi_result_free(result);
4350 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4351 osrfAppSessionStatus(
4353 OSRF_STATUS_INTERNALSERVERERROR,
4354 "osrfMethodException",
4356 "Severe query error -- see error log for more details"
4364 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4365 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4368 dbhandle = writehandle;
4370 osrfHash* links = osrfHashGet(meta, "links");
4371 osrfHash* fields = osrfHashGet(meta, "fields");
4372 char* core_class = osrfHashGet(meta, "classname");
4373 char* pkey = osrfHashGet(meta, "primarykey");
4375 const jsonObject* _tmp;
4378 char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4380 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4385 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4387 dbi_result result = dbi_conn_query(dbhandle, sql);
4388 if( NULL == result ) {
4389 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4390 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4391 osrfAppSessionStatus(
4393 OSRF_STATUS_INTERNALSERVERERROR,
4394 "osrfMethodException",
4396 "Severe query error -- see error log for more details"
4403 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4406 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
4407 osrfHash* dedup = osrfNewHash();
4409 if (dbi_result_first_row(result)) {
4410 /* JSONify the result */
4411 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4413 obj = oilsMakeFieldmapperFromResult( result, meta );
4414 char* pkey_val = oilsFMGetString( obj, pkey );
4415 if ( osrfHashGet( dedup, pkey_val ) ) {
4416 jsonObjectFree(obj);
4419 osrfHashSet( dedup, pkey_val, pkey_val );
4420 jsonObjectPush(res_list, obj);
4422 } while (dbi_result_next_row(result));
4424 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
4428 osrfHashFree(dedup);
4429 /* clean up the query */
4430 dbi_result_free(result);
4433 if (res_list->size && query_hash) {
4434 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
4436 int x = (int)jsonObjectGetNumber(_tmp);
4437 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
4439 const jsonObject* temp_blob;
4440 if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
4442 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
4443 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
4445 osrfStringArray* link_fields = NULL;
4448 if (flesh_fields->size == 1) {
4449 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
4450 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
4455 link_fields = osrfNewStringArray(1);
4456 jsonIterator* _i = jsonNewIterator( flesh_fields );
4457 while ((_f = jsonIteratorNext( _i ))) {
4458 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4460 jsonIteratorFree(_i);
4465 unsigned long res_idx = 0;
4466 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
4471 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4473 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4475 osrfHash* kid_link = osrfHashGet(links, link_field);
4476 if (!kid_link) continue;
4478 osrfHash* field = osrfHashGet(fields, link_field);
4479 if (!field) continue;
4481 osrfHash* value_field = field;
4483 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4484 if (!kid_idl) continue;
4486 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4487 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4490 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4491 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4494 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4496 if (link_map->size > 0) {
4497 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4500 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4505 osrfHashGet(kid_link, "class"),
4512 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4513 osrfHashGet(kid_link, "field"),
4514 osrfHashGet(kid_link, "class"),
4515 osrfHashGet(kid_link, "key"),
4516 osrfHashGet(kid_link, "reltype")
4519 const char* search_key = jsonObjectGetString(
4522 atoi( osrfHashGet(value_field, "array_position") )
4527 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4531 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
4533 // construct WHERE clause
4534 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
4537 osrfHashGet(kid_link, "key"),
4538 jsonNewObject( search_key )
4541 // construct the rest of the query
4542 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
4543 jsonObjectSetKey( rest_of_query, "flesh",
4544 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4548 jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
4550 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
4551 jsonObjectSetKey( rest_of_query, "order_by",
4552 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
4556 if (jsonObjectGetKeyConst(query_hash, "select")) {
4557 jsonObjectSetKey( rest_of_query, "select",
4558 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
4562 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
4563 where_clause, rest_of_query, err);
4565 jsonObjectFree( where_clause );
4566 jsonObjectFree( rest_of_query );
4569 osrfStringArrayFree(link_fields);
4570 jsonObjectFree(res_list);
4571 jsonObjectFree(flesh_blob);
4575 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4577 jsonObject* X = NULL;
4578 if ( link_map->size > 0 && kids->size > 0 ) {
4580 kids = jsonNewObjectType(JSON_ARRAY);
4582 jsonObject* _k_node;
4583 unsigned long res_idx = 0;
4584 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
4590 (unsigned long)atoi(
4596 osrfHashGet(kid_link, "class")
4600 osrfStringArrayGetString( link_map, 0 )
4608 } // end while loop traversing X
4611 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4612 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4615 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4616 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4620 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4621 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4624 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4625 jsonObjectClone( kids )
4630 jsonObjectFree(kids);
4634 jsonObjectFree( kids );
4636 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4637 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4640 } // end while loop traversing res_list
4641 jsonObjectFree( flesh_blob );
4642 osrfStringArrayFree(link_fields);
4651 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
4653 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4655 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
4657 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
4660 if (!verifyObjectClass(ctx, target)) {
4665 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4666 osrfAppSessionStatus(
4668 OSRF_STATUS_BADREQUEST,
4669 "osrfMethodException",
4671 "No active transaction -- required for UPDATE"
4677 // The following test is harmless but redundant. If a class is
4678 // readonly, we don't register an update method for it.
4679 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4680 osrfAppSessionStatus(
4682 OSRF_STATUS_BADREQUEST,
4683 "osrfMethodException",
4685 "Cannot UPDATE readonly class"
4691 dbhandle = writehandle;
4693 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4695 // Set the last_xact_id
4696 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4698 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
4699 trans_id, target->classname, index);
4700 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4703 char* pkey = osrfHashGet(meta, "primarykey");
4704 osrfHash* fields = osrfHashGet(meta, "fields");
4706 char* id = oilsFMGetString( target, pkey );
4710 "%s updating %s object with %s = %s",
4712 osrfHashGet(meta, "fieldmapper"),
4717 growing_buffer* sql = buffer_init(128);
4718 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4721 osrfHash* field_def = NULL;
4722 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4723 while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
4725 // Skip virtual fields, and the primary key
4726 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
4729 const char* field_name = osrfHashIteratorKey( field_itr );
4730 if( ! strcmp( field_name, pkey ) )
4733 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4735 int value_is_numeric = 0; // boolean
4737 if (field_object && field_object->classname) {
4738 value = oilsFMGetString(
4740 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4742 } else if( JSON_BOOL == field_object->type ) {
4743 if( jsonBoolIsTrue( field_object ) )
4744 value = strdup( "t" );
4746 value = strdup( "f" );
4748 value = jsonObjectToSimpleString( field_object );
4749 if( field_object && JSON_NUMBER == field_object->type )
4750 value_is_numeric = 1;
4753 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
4754 osrfHashGet(meta, "fieldmapper"), field_name, value);
4756 if (!field_object || field_object->type == JSON_NULL) {
4757 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
4758 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
4759 if (first) first = 0;
4760 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4761 buffer_fadd( sql, " %s = NULL", field_name );
4764 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
4765 if (first) first = 0;
4766 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4768 const char* numtype = get_datatype( field_def );
4769 if ( !strncmp( numtype, "INT", 3 ) ) {
4770 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
4771 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
4772 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
4774 // Must really be intended as a string, so quote it
4775 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4776 buffer_fadd( sql, " %s = %s", field_name, value );
4778 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4779 osrfAppSessionStatus(
4781 OSRF_STATUS_INTERNALSERVERERROR,
4782 "osrfMethodException",
4784 "Error quoting string -- please see the error log for more details"
4788 osrfHashIteratorFree( field_itr );
4795 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
4798 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4799 if (first) first = 0;
4800 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4801 buffer_fadd( sql, " %s = %s", field_name, value );
4804 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4805 osrfAppSessionStatus(
4807 OSRF_STATUS_INTERNALSERVERERROR,
4808 "osrfMethodException",
4810 "Error quoting string -- please see the error log for more details"
4814 osrfHashIteratorFree( field_itr );
4825 osrfHashIteratorFree( field_itr );
4827 jsonObject* obj = jsonNewObject(id);
4829 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4830 dbi_conn_quote_string(dbhandle, &id);
4832 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
4834 char* query = buffer_release(sql);
4835 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
4837 dbi_result result = dbi_conn_query(dbhandle, query);
4841 jsonObjectFree(obj);
4842 obj = jsonNewObject(NULL);
4845 "%s ERROR updating %s object with %s = %s",
4847 osrfHashGet(meta, "fieldmapper"),
4858 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4860 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4862 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4863 osrfAppSessionStatus(
4865 OSRF_STATUS_BADREQUEST,
4866 "osrfMethodException",
4868 "No active transaction -- required for DELETE"
4874 // The following test is harmless but redundant. If a class is
4875 // readonly, we don't register a delete method for it.
4876 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4877 osrfAppSessionStatus(
4879 OSRF_STATUS_BADREQUEST,
4880 "osrfMethodException",
4882 "Cannot DELETE readonly class"
4888 dbhandle = writehandle;
4892 char* pkey = osrfHashGet(meta, "primarykey");
4900 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4901 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4906 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4909 if (!verifyObjectPCRUD( ctx, NULL )) {
4914 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4919 "%s deleting %s object with %s = %s",
4921 osrfHashGet(meta, "fieldmapper"),
4926 obj = jsonNewObject(id);
4928 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4929 dbi_conn_quote_string(writehandle, &id);
4931 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4934 jsonObjectFree(obj);
4935 obj = jsonNewObject(NULL);
4938 "%s ERROR deleting %s object with %s = %s",
4940 osrfHashGet(meta, "fieldmapper"),
4953 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4954 if(!(result && meta)) return jsonNULL;
4956 jsonObject* object = jsonNewObject(NULL);
4957 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4959 osrfHash* fields = osrfHashGet(meta, "fields");
4961 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4965 char dt_string[256];
4969 int columnIndex = 1;
4971 unsigned short type;
4972 const char* columnName;
4974 /* cycle through the column list */
4975 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
4977 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4979 fmIndex = -1; // reset the position
4981 /* determine the field type and storage attributes */
4982 type = dbi_result_get_field_type_idx(result, columnIndex);
4983 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
4985 /* fetch the fieldmapper index */
4986 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4988 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4991 const char* pos = (char*)osrfHashGet(_f, "array_position");
4992 if ( !pos ) continue;
4994 fmIndex = atoi( pos );
4995 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
5000 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5001 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
5006 case DBI_TYPE_INTEGER :
5008 if( attr & DBI_INTEGER_SIZE8 )
5009 jsonObjectSetIndex( object, fmIndex,
5010 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
5012 jsonObjectSetIndex( object, fmIndex,
5013 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
5017 case DBI_TYPE_DECIMAL :
5018 jsonObjectSetIndex( object, fmIndex,
5019 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
5022 case DBI_TYPE_STRING :
5028 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
5033 case DBI_TYPE_DATETIME :
5035 memset(dt_string, '\0', sizeof(dt_string));
5036 memset(&gmdt, '\0', sizeof(gmdt));
5038 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5041 if (!(attr & DBI_DATETIME_DATE)) {
5042 gmtime_r( &_tmp_dt, &gmdt );
5043 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5044 } else if (!(attr & DBI_DATETIME_TIME)) {
5045 localtime_r( &_tmp_dt, &gmdt );
5046 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5048 localtime_r( &_tmp_dt, &gmdt );
5049 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5052 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
5056 case DBI_TYPE_BINARY :
5057 osrfLogError( OSRF_LOG_MARK,
5058 "Can't do binary at column %s : index %d", columnName, columnIndex);
5067 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5068 if(!result) return jsonNULL;
5070 jsonObject* object = jsonNewObject(NULL);
5073 char dt_string[256];
5077 int columnIndex = 1;
5079 unsigned short type;
5080 const char* columnName;
5082 /* cycle through the column list */
5083 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5085 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5087 fmIndex = -1; // reset the position
5089 /* determine the field type and storage attributes */
5090 type = dbi_result_get_field_type_idx(result, columnIndex);
5091 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5093 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5094 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
5099 case DBI_TYPE_INTEGER :
5101 if( attr & DBI_INTEGER_SIZE8 )
5102 jsonObjectSetKey( object, columnName,
5103 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)) );
5105 jsonObjectSetKey( object, columnName,
5106 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
5109 case DBI_TYPE_DECIMAL :
5110 jsonObjectSetKey( object, columnName,
5111 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
5114 case DBI_TYPE_STRING :
5115 jsonObjectSetKey( object, columnName,
5116 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
5119 case DBI_TYPE_DATETIME :
5121 memset(dt_string, '\0', sizeof(dt_string));
5122 memset(&gmdt, '\0', sizeof(gmdt));
5124 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5127 if (!(attr & DBI_DATETIME_DATE)) {
5128 gmtime_r( &_tmp_dt, &gmdt );
5129 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5130 } else if (!(attr & DBI_DATETIME_TIME)) {
5131 localtime_r( &_tmp_dt, &gmdt );
5132 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5134 localtime_r( &_tmp_dt, &gmdt );
5135 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5138 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
5141 case DBI_TYPE_BINARY :
5142 osrfLogError( OSRF_LOG_MARK,
5143 "Can't do binary at column %s : index %d", columnName, columnIndex );
5147 } // end while loop traversing result
5152 // Interpret a string as true or false
5153 static int str_is_true( const char* str ) {
5154 if( NULL == str || strcasecmp( str, "true" ) )
5160 // Interpret a jsonObject as true or false
5161 static int obj_is_true( const jsonObject* obj ) {
5164 else switch( obj->type )
5172 if( strcasecmp( obj->value.s, "true" ) )
5176 case JSON_NUMBER : // Support 1/0 for perl's sake
5177 if( jsonObjectGetNumber( obj ) == 1.0 )
5186 // Translate a numeric code into a text string identifying a type of
5187 // jsonObject. To be used for building error messages.
5188 static const char* json_type( int code ) {
5194 return "JSON_ARRAY";
5196 return "JSON_STRING";
5198 return "JSON_NUMBER";
5204 return "(unrecognized)";
5208 // Extract the "primitive" attribute from an IDL field definition.
5209 // If we haven't initialized the app, then we must be running in
5210 // some kind of testbed. In that case, default to "string".
5211 static const char* get_primitive( osrfHash* field ) {
5212 const char* s = osrfHashGet( field, "primitive" );
5214 if( child_initialized )
5217 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5219 osrfHashGet( field, "name" )
5227 // Extract the "datatype" attribute from an IDL field definition.
5228 // If we haven't initialized the app, then we must be running in
5229 // some kind of testbed. In that case, default to to NUMERIC,
5230 // since we look at the datatype only for numbers.
5231 static const char* get_datatype( osrfHash* field ) {
5232 const char* s = osrfHashGet( field, "datatype" );
5234 if( child_initialized )
5237 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5239 osrfHashGet( field, "name" )
5248 If the input string is potentially a valid SQL identifier, return 1.
5251 Purpose: to prevent certain kinds of SQL injection. To that end we
5252 don't necessarily need to follow all the rules exactly, such as requiring
5253 that the first character not be a digit.
5255 We allow leading and trailing white space. In between, we do not allow
5256 punctuation (except for underscores and dollar signs), control
5257 characters, or embedded white space.
5259 More pedantically we should allow quoted identifiers containing arbitrary
5260 characters, but for the foreseeable future such quoted identifiers are not
5261 likely to be an issue.
5263 static int is_identifier( const char* s) {
5267 // Skip leading white space
5268 while( isspace( (unsigned char) *s ) )
5272 return 0; // Nothing but white space? Not okay.
5274 // Check each character until we reach white space or
5275 // end-of-string. Letters, digits, underscores, and
5276 // dollar signs are okay. With the exception of periods
5277 // (as in schema.identifier), control characters and other
5278 // punctuation characters are not okay. Anything else
5279 // is okay -- it could for example be part of a multibyte
5280 // UTF8 character such as a letter with diacritical marks,
5281 // and those are allowed.
5283 if( isalnum( (unsigned char) *s )
5287 ; // Fine; keep going
5288 else if( ispunct( (unsigned char) *s )
5289 || iscntrl( (unsigned char) *s ) )
5292 } while( *s && ! isspace( (unsigned char) *s ) );
5294 // If we found any white space in the above loop,
5295 // the rest had better be all white space.
5297 while( isspace( (unsigned char) *s ) )
5301 return 0; // White space was embedded within non-white space
5307 Determine whether to accept a character string as a comparison operator.
5308 Return 1 if it's good, or 0 if it's bad.
5310 We don't validate it for real. We just make sure that it doesn't contain
5311 any semicolons or white space (with special exceptions for a few specific
5312 operators). The idea is to block certain kinds of SQL injection. If it
5313 has no semicolons or white space but it's still not a valid operator, then
5314 the database will complain.
5316 Another approach would be to compare the string against a short list of
5317 approved operators. We don't do that because we want to allow custom
5318 operators like ">100*", which would be difficult or impossible to
5319 express otherwise in a JSON query.
5321 static int is_good_operator( const char* op ) {
5322 if( !op ) return 0; // Sanity check
5326 if( isspace( (unsigned char) *s ) ) {
5327 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
5328 // and IS NOT DISTINCT FROM.
5329 if( !strcasecmp( op, "similar to" ) )
5331 else if( !strcasecmp( op, "is distinct from" ) )
5333 else if( !strcasecmp( op, "is not distinct from" ) )
5338 else if( ';' == *s )
5345 /* ----------------------------------------------------------------------------------
5346 The following machinery supports a stack of query frames for use by SELECT().
5348 A query frame caches information about one level of a SELECT query. When we enter
5349 a subquery, we push another query frame onto the stack, and pop it off when we leave.
5351 The query frame stores information about the core class, and about any joined classes
5354 The main purpose is to map table aliases to classes and tables, so that a query can
5355 join to the same table more than once. A secondary goal is to reduce the number of
5356 lookups in the IDL by caching the results.
5357 ----------------------------------------------------------------------------------*/
5359 #define STATIC_CLASS_INFO_COUNT 3
5361 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
5363 /* ---------------------------------------------------------------------------
5364 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5366 ---------------------------------------------------------------------------*/
5367 static ClassInfo* allocate_class_info( void ) {
5368 // In order to reduce the number of mallocs and frees, we return a static
5369 // instance of ClassInfo, if we can find one that we're not already using.
5370 // We rely on the fact that the compiler will implicitly initialize the
5371 // static instances so that in_use == 0.
5374 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5375 if( ! static_class_info[ i ].in_use ) {
5376 static_class_info[ i ].in_use = 1;
5377 return static_class_info + i;
5381 // The static ones are all in use. Malloc one.
5383 return safe_malloc( sizeof( ClassInfo ) );
5386 /* --------------------------------------------------------------------------
5387 Free any malloc'd memory owned by a ClassInfo; return it to a pristine state
5388 ---------------------------------------------------------------------------*/
5389 static void clear_class_info( ClassInfo* info ) {
5394 // Free any malloc'd strings
5396 if( info->alias != info->alias_store )
5397 free( info->alias );
5399 if( info->class_name != info->class_name_store )
5400 free( info->class_name );
5402 free( info->source_def );
5404 info->alias = info->class_name = info->source_def = NULL;
5408 /* --------------------------------------------------------------------------
5409 Deallocate a ClassInfo and everything it owns
5410 ---------------------------------------------------------------------------*/
5411 static void free_class_info( ClassInfo* info ) {
5416 clear_class_info( info );
5418 // If it's one of the static instances, just mark it as not in use
5421 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5422 if( info == static_class_info + i ) {
5423 static_class_info[ i ].in_use = 0;
5428 // Otherwise it must have been malloc'd, so free it
5433 /* --------------------------------------------------------------------------
5434 Populate an already-allocated ClassInfo. Return 0 if successful, 1 if not.
5435 ---------------------------------------------------------------------------*/
5436 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
5439 osrfLogError( OSRF_LOG_MARK,
5440 "%s ERROR: No ClassInfo available to populate", MODULENAME );
5441 info->alias = info->class_name = info->source_def = NULL;
5442 info->class_def = info->fields = info->links = NULL;
5447 osrfLogError( OSRF_LOG_MARK,
5448 "%s ERROR: No class name provided for lookup", MODULENAME );
5449 info->alias = info->class_name = info->source_def = NULL;
5450 info->class_def = info->fields = info->links = NULL;
5454 // Alias defaults to class name if not supplied
5455 if( ! alias || ! alias[ 0 ] )
5458 // Look up class info in the IDL
5459 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
5461 osrfLogError( OSRF_LOG_MARK,
5462 "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
5463 info->alias = info->class_name = info->source_def = NULL;
5464 info->class_def = info->fields = info->links = NULL;
5466 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
5467 osrfLogError( OSRF_LOG_MARK,
5468 "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
5469 info->alias = info->class_name = info->source_def = NULL;
5470 info->class_def = info->fields = info->links = NULL;
5474 osrfHash* links = osrfHashGet( class_def, "links" );
5476 osrfLogError( OSRF_LOG_MARK,
5477 "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
5478 info->alias = info->class_name = info->source_def = NULL;
5479 info->class_def = info->fields = info->links = NULL;
5483 osrfHash* fields = osrfHashGet( class_def, "fields" );
5485 osrfLogError( OSRF_LOG_MARK,
5486 "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
5487 info->alias = info->class_name = info->source_def = NULL;
5488 info->class_def = info->fields = info->links = NULL;
5492 char* source_def = getSourceDefinition( class_def );
5496 // We got everything we need, so populate the ClassInfo
5497 if( strlen( alias ) > ALIAS_STORE_SIZE )
5498 info->alias = strdup( alias );
5500 strcpy( info->alias_store, alias );
5501 info->alias = info->alias_store;
5504 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
5505 info->class_name = strdup( class );
5507 strcpy( info->class_name_store, class );
5508 info->class_name = info->class_name_store;
5511 info->source_def = source_def;
5513 info->class_def = class_def;
5514 info->links = links;
5515 info->fields = fields;
5520 #define STATIC_FRAME_COUNT 3
5522 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
5524 /* ---------------------------------------------------------------------------
5525 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5527 ---------------------------------------------------------------------------*/
5528 static QueryFrame* allocate_frame( void ) {
5529 // In order to reduce the number of mallocs and frees, we return a static
5530 // instance of QueryFrame, if we can find one that we're not already using.
5531 // We rely on the fact that the compiler will implicitly initialize the
5532 // static instances so that in_use == 0.
5535 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5536 if( ! static_frame[ i ].in_use ) {
5537 static_frame[ i ].in_use = 1;
5538 return static_frame + i;
5542 // The static ones are all in use. Malloc one.
5544 return safe_malloc( sizeof( QueryFrame ) );
5547 /* --------------------------------------------------------------------------
5548 Free a QueryFrame, and all the memory it owns.
5549 ---------------------------------------------------------------------------*/
5550 static void free_query_frame( QueryFrame* frame ) {
5555 clear_class_info( &frame->core );
5557 // Free the join list
5559 ClassInfo* info = frame->join_list;
5562 free_class_info( info );
5566 frame->join_list = NULL;
5569 // If the frame is a static instance, just mark it as unused
5571 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5572 if( frame == static_frame + i ) {
5573 static_frame[ i ].in_use = 0;
5578 // Otherwise it must have been malloc'd, so free it
5583 /* --------------------------------------------------------------------------
5584 Search a given QueryFrame for a specified alias. If you find it, return
5585 a pointer to the corresponding ClassInfo. Otherwise return NULL.
5586 ---------------------------------------------------------------------------*/
5587 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
5588 if( ! frame || ! target ) {
5592 ClassInfo* found_class = NULL;
5594 if( !strcmp( target, frame->core.alias ) )
5595 return &(frame->core);
5597 ClassInfo* curr_class = frame->join_list;
5598 while( curr_class ) {
5599 if( strcmp( target, curr_class->alias ) )
5600 curr_class = curr_class->next;
5602 found_class = curr_class;
5611 /* --------------------------------------------------------------------------
5612 Push a new (blank) QueryFrame onto the stack.
5613 ---------------------------------------------------------------------------*/
5614 static void push_query_frame( void ) {
5615 QueryFrame* frame = allocate_frame();
5616 frame->join_list = NULL;
5617 frame->next = curr_query;
5619 // Initialize the ClassInfo for the core class
5620 ClassInfo* core = &frame->core;
5621 core->alias = core->class_name = core->source_def = NULL;
5622 core->class_def = core->fields = core->links = NULL;
5627 /* --------------------------------------------------------------------------
5628 Pop a QueryFrame off the stack and destroy it
5629 ---------------------------------------------------------------------------*/
5630 static void pop_query_frame( void ) {
5635 QueryFrame* popped = curr_query;
5636 curr_query = popped->next;
5638 free_query_frame( popped );
5641 /* --------------------------------------------------------------------------
5642 Populate the ClassInfo for the core class. Return 0 if successful, 1 if not.
5643 ---------------------------------------------------------------------------*/
5644 static int add_query_core( const char* alias, const char* class_name ) {
5647 if( ! curr_query ) {
5648 osrfLogError( OSRF_LOG_MARK,
5649 "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
5651 } else if( curr_query->core.alias ) {
5652 osrfLogError( OSRF_LOG_MARK,
5653 "%s ERROR: Core class %s already populated as %s",
5654 MODULENAME, curr_query->core.class_name, curr_query->core.alias );
5658 build_class_info( &curr_query->core, alias, class_name );
5659 if( curr_query->core.alias )
5662 osrfLogError( OSRF_LOG_MARK,
5663 "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
5668 /* --------------------------------------------------------------------------
5669 Search the current QueryFrame for a specified alias. If you find it,
5670 return a pointer to the corresponding ClassInfo. Otherwise return NULL.
5671 ---------------------------------------------------------------------------*/
5672 static ClassInfo* search_alias( const char* target ) {
5673 return search_alias_in_frame( curr_query, target );
5676 /* --------------------------------------------------------------------------
5677 Search all levels of query for a specified alias, starting with the
5678 current query. If you find it, return a pointer to the corresponding
5679 ClassInfo. Otherwise return NULL.
5680 ---------------------------------------------------------------------------*/
5681 static ClassInfo* search_all_alias( const char* target ) {
5682 ClassInfo* found_class = NULL;
5683 QueryFrame* curr_frame = curr_query;
5685 while( curr_frame ) {
5686 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
5689 curr_frame = curr_frame->next;
5695 /* --------------------------------------------------------------------------
5696 Add a class to the list of classes joined to the current query.
5697 ---------------------------------------------------------------------------*/
5698 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
5700 if( ! classname || ! *classname ) { // sanity check
5701 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
5708 const ClassInfo* conflict = search_alias( alias );
5710 osrfLogError( OSRF_LOG_MARK,
5711 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
5712 MODULENAME, alias, conflict->class_name );
5716 ClassInfo* info = allocate_class_info();
5718 if( build_class_info( info, alias, classname ) ) {
5719 free_class_info( info );
5723 // Add the new ClassInfo to the join list of the current QueryFrame
5724 info->next = curr_query->join_list;
5725 curr_query->join_list = info;
5730 /* --------------------------------------------------------------------------
5731 Destroy all nodes on the query stack.
5732 ---------------------------------------------------------------------------*/
5733 static void clear_query_stack( void ) {