2 #include "opensrf/osrf_application.h"
3 #include "opensrf/osrf_settings.h"
4 #include "opensrf/osrf_message.h"
5 #include "opensrf/utils.h"
6 #include "opensrf/osrf_json.h"
7 #include "opensrf/log.h"
8 #include "openils/oils_utils.h"
17 # define MODULENAME "open-ils.reporter-store"
20 # define MODULENAME "open-ils.pcrud"
22 # define MODULENAME "open-ils.cstore"
27 #define DISABLE_I18N 2
28 #define SELECT_DISTINCT 1
32 struct ClassInfoStruct;
33 typedef struct ClassInfoStruct ClassInfo;
35 #define ALIAS_STORE_SIZE 16
36 #define CLASS_NAME_STORE_SIZE 16
38 struct ClassInfoStruct {
42 osrfHash* class_def; // Points into IDL
43 osrfHash* fields; // Points into IDL
44 osrfHash* links; // Points into IDL
46 // The remaining members are private and internal. Client code should not
47 // access them directly.
49 ClassInfo* next; // Supports linked list of joined classes
50 int in_use; // boolean
52 // We usually store the alias and class name in the following arrays, and
53 // point the corresponding pointers at them. When the string is too big
54 // for the array (which will probably never happen in practice), we strdup it.
56 char alias_store[ ALIAS_STORE_SIZE + 1 ];
57 char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
60 struct QueryFrameStruct;
61 typedef struct QueryFrameStruct QueryFrame;
63 struct QueryFrameStruct {
65 ClassInfo* join_list; // linked list of classes joined to the core class
66 QueryFrame* next; // implements stack as linked list
67 int in_use; // boolean
70 int osrfAppChildInit();
71 int osrfAppInitialize();
72 void osrfAppChildExit();
74 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
76 int beginTransaction ( osrfMethodContext* );
77 int commitTransaction ( osrfMethodContext* );
78 int rollbackTransaction ( osrfMethodContext* );
80 int setSavepoint ( osrfMethodContext* );
81 int releaseSavepoint ( osrfMethodContext* );
82 int rollbackSavepoint ( osrfMethodContext* );
84 int doJSONSearch ( osrfMethodContext* );
86 int dispatchCRUDMethod ( osrfMethodContext* );
87 static jsonObject* doCreate ( osrfMethodContext*, int* );
88 static jsonObject* doRetrieve ( osrfMethodContext*, int* );
89 static jsonObject* doUpdate ( osrfMethodContext*, int* );
90 static jsonObject* doDelete ( osrfMethodContext*, int* );
91 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
92 jsonObject* where_hash, jsonObject* query_hash, int* err );
93 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
94 static jsonObject* oilsMakeJSONFromResult( dbi_result );
96 static char* searchSimplePredicate ( const char* op, const char* class_alias,
97 osrfHash* field, const jsonObject* node );
98 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
99 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject*);
100 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*, const char* );
101 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
102 static char* searchINPredicate ( const char*, osrfHash*,
103 jsonObject*, const char*, osrfMethodContext* );
104 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
105 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
106 static char* searchWHERE ( const jsonObject*, const ClassInfo*, int, osrfMethodContext* );
107 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
109 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
111 void userDataFree( void* );
112 static void sessionDataFree( char*, void* );
113 static char* getSourceDefinition( osrfHash* );
114 static int str_is_true( const char* str );
115 static int obj_is_true( const jsonObject* obj );
116 static const char* json_type( int code );
117 static const char* get_primitive( osrfHash* field );
118 static const char* get_datatype( osrfHash* field );
119 static int is_identifier( const char* s);
120 static int is_good_operator( const char* op );
121 static void pop_query_frame( void );
122 static void push_query_frame( void );
123 static int add_query_core( const char* alias, const char* class_name );
124 static ClassInfo* search_alias( const char* target );
125 static ClassInfo* search_all_alias( const char* target );
126 static ClassInfo* add_joined_class( const char* alias, const char* classname );
127 static void clear_query_stack( void );
130 static jsonObject* verifyUserPCRUD( osrfMethodContext* );
131 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
132 static char* org_tree_root( osrfMethodContext* ctx );
133 static jsonObject* single_hash( const char* key, const char* value );
136 static int child_initialized = 0; /* boolean */
138 static dbi_conn writehandle; /* our MASTER db connection */
139 static dbi_conn dbhandle; /* our CURRENT db connection */
140 //static osrfHash * readHandles;
141 static jsonObject* const jsonNULL = NULL; //
142 static int max_flesh_depth = 100;
144 // The following points the top of a stack of QueryFrames. It's a little
145 // confusing because the top level of the query is at the bottom of the stack.
146 static QueryFrame* curr_query = NULL;
148 /* called when this process is about to exit */
149 void osrfAppChildExit() {
150 osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
153 if (writehandle == dbhandle) same = 1;
155 dbi_conn_query(writehandle, "ROLLBACK;");
156 dbi_conn_close(writehandle);
159 if (dbhandle && !same)
160 dbi_conn_close(dbhandle);
162 // XXX add cleanup of readHandles whenever that gets used
167 int osrfAppInitialize() {
169 osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
170 osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
172 if (!oilsIDLInit( osrf_settings_host_value("/IDL") )) return 1; /* return non-zero to indicate error */
174 growing_buffer* method_name = buffer_init(64);
176 // Generic search thingy
177 buffer_add(method_name, MODULENAME);
178 buffer_add(method_name, ".json_query");
179 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
180 "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
183 // first we register all the transaction and savepoint methods
184 buffer_reset(method_name);
185 OSRF_BUFFER_ADD(method_name, MODULENAME);
186 OSRF_BUFFER_ADD(method_name, ".transaction.begin");
187 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
188 "beginTransaction", "", 0, 0 );
190 buffer_reset(method_name);
191 OSRF_BUFFER_ADD(method_name, MODULENAME);
192 OSRF_BUFFER_ADD(method_name, ".transaction.commit");
193 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
194 "commitTransaction", "", 0, 0 );
196 buffer_reset(method_name);
197 OSRF_BUFFER_ADD(method_name, MODULENAME);
198 OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
199 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
200 "rollbackTransaction", "", 0, 0 );
202 buffer_reset(method_name);
203 OSRF_BUFFER_ADD(method_name, MODULENAME);
204 OSRF_BUFFER_ADD(method_name, ".savepoint.set");
205 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
206 "setSavepoint", "", 1, 0 );
208 buffer_reset(method_name);
209 OSRF_BUFFER_ADD(method_name, MODULENAME);
210 OSRF_BUFFER_ADD(method_name, ".savepoint.release");
211 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
212 "releaseSavepoint", "", 1, 0 );
214 buffer_reset(method_name);
215 OSRF_BUFFER_ADD(method_name, MODULENAME);
216 OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
217 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
218 "rollbackSavepoint", "", 1, 0 );
220 static const char* global_method[] = {
228 const int global_method_count
229 = sizeof( global_method ) / sizeof ( global_method[0] );
231 unsigned long class_count = osrfHashGetCount( oilsIDL() );
232 osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
233 osrfLogDebug(OSRF_LOG_MARK,
234 "At most %lu methods will be generated",
235 (unsigned long) (class_count * global_method_count) );
237 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
238 osrfHash* idlClass = NULL;
240 // For each class in IDL...
241 while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
243 const char* classname = osrfHashIteratorKey( class_itr );
244 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
246 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), MODULENAME )) {
247 osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on", MODULENAME, classname);
251 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
252 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
256 // Look up some other attributes of the current class
257 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
258 if( !idlClass_fieldmapper ) {
259 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL", classname );
264 osrfHash* idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
265 if (!idlClass_permacrud) {
266 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no permacrud in IDL", classname );
270 const char* readonly = osrfHashGet(idlClass, "readonly");
273 for( i = 0; i < global_method_count; ++i ) { // for each global method
274 const char* method_type = global_method[ i ];
275 osrfLogDebug(OSRF_LOG_MARK,
276 "Using files to build %s class methods for %s", method_type, classname);
279 const char* tmp_method = method_type;
280 if ( *tmp_method == 'i' || *tmp_method == 's') {
281 tmp_method = "retrieve";
283 if (!osrfHashGet( idlClass_permacrud, tmp_method )) continue;
286 if ( str_is_true( readonly ) &&
287 ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd')
290 buffer_reset( method_name );
292 buffer_fadd(method_name, "%s.%s.%s", MODULENAME, method_type, classname);
296 char* _fm = strdup( idlClass_fieldmapper );
297 part = strtok_r(_fm, ":", &st_tmp);
299 buffer_fadd(method_name, "%s.direct.%s", MODULENAME, part);
301 while ((part = strtok_r(NULL, ":", &st_tmp))) {
302 OSRF_BUFFER_ADD_CHAR(method_name, '.');
303 OSRF_BUFFER_ADD(method_name, part);
305 OSRF_BUFFER_ADD_CHAR(method_name, '.');
306 OSRF_BUFFER_ADD(method_name, method_type);
310 char* method = buffer_data(method_name);
313 if (*method_type == 'i' || *method_type == 's') {
314 flags = flags | OSRF_METHOD_STREAMING;
317 osrfHash* method_meta = osrfNewHash();
318 osrfHashSet( method_meta, idlClass, "class");
319 osrfHashSet( method_meta, method, "methodname" );
320 osrfHashSet( method_meta, strdup(method_type), "methodtype" );
322 osrfAppRegisterExtendedMethod(
325 "dispatchCRUDMethod",
333 } // end for each global method
334 } // end for each class in IDL
336 buffer_free( method_name );
337 osrfHashIteratorFree( class_itr );
342 static char* getSourceDefinition( osrfHash* class ) {
344 char* tabledef = osrfHashGet(class, "tablename");
347 tabledef = strdup(tabledef);
349 tabledef = osrfHashGet(class, "source_definition");
351 growing_buffer* tablebuf = buffer_init(128);
352 buffer_fadd( tablebuf, "(%s)", tabledef );
353 tabledef = buffer_release(tablebuf);
355 const char* classname = osrfHashGet( class, "classname" );
360 "%s ERROR No tablename or source_definition for class \"%s\"",
371 * Connects to the database
373 int osrfAppChildInit() {
375 osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
376 dbi_initialize(NULL);
377 osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
379 char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
380 char* user = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
381 char* host = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
382 char* port = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
383 char* db = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
384 char* pw = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
385 char* md = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion", MODULENAME);
387 osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
388 writehandle = dbi_conn_new(driver);
391 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
394 osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
396 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
397 "port=%s, user=%s, pw=%s, db=%s", MODULENAME, host, port, user, pw, db );
399 if(host) dbi_conn_set_option(writehandle, "host", host );
400 if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
401 if(user) dbi_conn_set_option(writehandle, "username", user);
402 if(pw) dbi_conn_set_option(writehandle, "password", pw );
403 if(db) dbi_conn_set_option(writehandle, "dbname", db );
405 if(md) max_flesh_depth = atoi(md);
406 if(max_flesh_depth < 0) max_flesh_depth = 1;
407 if(max_flesh_depth > 1000) max_flesh_depth = 1000;
416 if (dbi_conn_connect(writehandle) < 0) {
418 if (dbi_conn_connect(writehandle) < 0) {
419 dbi_conn_error(writehandle, &err);
420 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
425 osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
427 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
428 osrfHash* class = NULL;
430 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
431 const char* classname = osrfHashIteratorKey( class_itr );
432 osrfHash* fields = osrfHashGet( class, "fields" );
434 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
435 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
439 char* tabledef = getSourceDefinition(class);
441 tabledef = strdup( "(null)" );
443 growing_buffer* sql_buf = buffer_init(32);
444 buffer_fadd( sql_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
448 char* sql = buffer_release(sql_buf);
449 osrfLogDebug(OSRF_LOG_MARK, "%s Investigatory SQL = %s", MODULENAME, sql);
451 dbi_result result = dbi_conn_query(writehandle, sql);
457 const char* columnName;
459 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
461 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
463 /* fetch the fieldmapper index */
464 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
466 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", (char*)columnName);
468 /* determine the field type and storage attributes */
470 switch( dbi_result_get_field_type_idx(result, columnIndex) ) {
472 case DBI_TYPE_INTEGER : {
474 if ( !osrfHashGet(_f, "primitive") )
475 osrfHashSet(_f,"number", "primitive");
477 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
478 if( attr & DBI_INTEGER_SIZE8 )
479 osrfHashSet(_f,"INT8", "datatype");
481 osrfHashSet(_f,"INT", "datatype");
484 case DBI_TYPE_DECIMAL :
485 if ( !osrfHashGet(_f, "primitive") )
486 osrfHashSet(_f,"number", "primitive");
488 osrfHashSet(_f,"NUMERIC", "datatype");
491 case DBI_TYPE_STRING :
492 if ( !osrfHashGet(_f, "primitive") )
493 osrfHashSet(_f,"string", "primitive");
494 osrfHashSet(_f,"TEXT", "datatype");
497 case DBI_TYPE_DATETIME :
498 if ( !osrfHashGet(_f, "primitive") )
499 osrfHashSet(_f,"string", "primitive");
501 osrfHashSet(_f,"TIMESTAMP", "datatype");
504 case DBI_TYPE_BINARY :
505 if ( !osrfHashGet(_f, "primitive") )
506 osrfHashSet(_f,"string", "primitive");
508 osrfHashSet(_f,"BYTEA", "datatype");
513 "Setting [%s] to primitive [%s] and datatype [%s]...",
515 osrfHashGet(_f, "primitive"),
516 osrfHashGet(_f, "datatype")
520 } // end while loop for traversing result
521 dbi_result_free(result);
523 osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", (char*)classname);
525 } // end for each class in IDL
527 osrfHashIteratorFree( class_itr );
528 child_initialized = 1;
533 This function is a sleazy hack intended *only* for testing and
534 debugging. Any real server process should initialize the
535 database connection by calling osrfAppChildInit().
537 void set_cstore_dbi_conn( dbi_conn conn ) {
538 dbhandle = writehandle = conn;
541 void userDataFree( void* blob ) {
542 osrfHashFree( (osrfHash*)blob );
546 static void sessionDataFree( char* key, void* item ) {
547 if (!(strcmp(key,"xact_id"))) {
549 dbi_conn_query(writehandle, "ROLLBACK;");
556 int beginTransaction ( osrfMethodContext* ctx ) {
557 if(osrfMethodVerifyContext( ctx )) {
558 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
563 jsonObject* user = verifyUserPCRUD( ctx );
564 if (!user) return -1;
565 jsonObjectFree(user);
568 dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
570 osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
571 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error starting transaction" );
574 jsonObject* ret = jsonNewObject(ctx->session->session_id);
575 osrfAppRespondComplete( ctx, ret );
578 if (!ctx->session->userData) {
579 ctx->session->userData = osrfNewHash();
580 osrfHashSetCallback((osrfHash*)ctx->session->userData, &sessionDataFree);
583 osrfHashSet( (osrfHash*)ctx->session->userData, strdup( ctx->session->session_id ), "xact_id" );
584 ctx->session->userDataFree = &userDataFree;
590 int setSavepoint ( osrfMethodContext* ctx ) {
591 if(osrfMethodVerifyContext( ctx )) {
592 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
599 jsonObject* user = verifyUserPCRUD( ctx );
600 if (!user) return -1;
601 jsonObjectFree(user);
604 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
605 osrfAppSessionStatus(
607 OSRF_STATUS_INTERNALSERVERERROR,
608 "osrfMethodException",
610 "No active transaction -- required for savepoints"
615 const char* spName = jsonObjectGetString(jsonObjectGetIndex(ctx->params, spNamePos));
617 dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
621 "%s: Error creating savepoint %s in transaction %s",
624 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
626 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
627 "osrfMethodException", ctx->request, "Error creating savepoint" );
630 jsonObject* ret = jsonNewObject(spName);
631 osrfAppRespondComplete( ctx, ret );
637 int releaseSavepoint ( osrfMethodContext* ctx ) {
638 if(osrfMethodVerifyContext( ctx )) {
639 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
646 jsonObject* user = verifyUserPCRUD( ctx );
647 if (!user) return -1;
648 jsonObjectFree(user);
651 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
652 osrfAppSessionStatus(
654 OSRF_STATUS_INTERNALSERVERERROR,
655 "osrfMethodException",
657 "No active transaction -- required for savepoints"
662 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
664 dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
668 "%s: Error releasing savepoint %s in transaction %s",
671 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
673 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
674 "osrfMethodException", ctx->request, "Error releasing savepoint" );
677 jsonObject* ret = jsonNewObject(spName);
678 osrfAppRespondComplete( ctx, ret );
684 int rollbackSavepoint ( osrfMethodContext* ctx ) {
685 if(osrfMethodVerifyContext( ctx )) {
686 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
693 jsonObject* user = verifyUserPCRUD( ctx );
694 if (!user) return -1;
695 jsonObjectFree(user);
698 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
699 osrfAppSessionStatus(
701 OSRF_STATUS_INTERNALSERVERERROR,
702 "osrfMethodException",
704 "No active transaction -- required for savepoints"
709 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
711 dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
715 "%s: Error rolling back savepoint %s in transaction %s",
718 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
720 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
721 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
724 jsonObject* ret = jsonNewObject(spName);
725 osrfAppRespondComplete( ctx, ret );
731 int commitTransaction ( osrfMethodContext* ctx ) {
732 if(osrfMethodVerifyContext( ctx )) {
733 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
738 jsonObject* user = verifyUserPCRUD( ctx );
739 if (!user) return -1;
740 jsonObjectFree(user);
743 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
744 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to commit" );
748 dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
750 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
751 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error committing transaction" );
754 osrfHashRemove(ctx->session->userData, "xact_id");
755 jsonObject* ret = jsonNewObject(ctx->session->session_id);
756 osrfAppRespondComplete( ctx, ret );
762 int rollbackTransaction ( osrfMethodContext* ctx ) {
763 if(osrfMethodVerifyContext( ctx )) {
764 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
769 jsonObject* user = verifyUserPCRUD( ctx );
770 if (!user) return -1;
771 jsonObjectFree(user);
774 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
775 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to roll back" );
779 dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
781 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
782 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error rolling back transaction" );
785 osrfHashRemove(ctx->session->userData, "xact_id");
786 jsonObject* ret = jsonNewObject(ctx->session->session_id);
787 osrfAppRespondComplete( ctx, ret );
793 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
794 if(osrfMethodVerifyContext( ctx )) {
795 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
799 osrfHash* meta = (osrfHash*) ctx->method->userData;
800 osrfHash* class_obj = osrfHashGet( meta, "class" );
804 const char* methodtype = osrfHashGet(meta, "methodtype");
805 jsonObject * obj = NULL;
807 if (!strcmp(methodtype, "create")) {
808 obj = doCreate(ctx, &err);
809 osrfAppRespondComplete( ctx, obj );
811 else if (!strcmp(methodtype, "retrieve")) {
812 obj = doRetrieve(ctx, &err);
813 osrfAppRespondComplete( ctx, obj );
815 else if (!strcmp(methodtype, "update")) {
816 obj = doUpdate(ctx, &err);
817 osrfAppRespondComplete( ctx, obj );
819 else if (!strcmp(methodtype, "delete")) {
820 obj = doDelete(ctx, &err);
821 osrfAppRespondComplete( ctx, obj );
823 else if (!strcmp(methodtype, "search")) {
825 jsonObject* where_clause;
826 jsonObject* rest_of_query;
829 where_clause = jsonObjectGetIndex( ctx->params, 1 );
830 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
832 where_clause = jsonObjectGetIndex( ctx->params, 0 );
833 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
836 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
841 unsigned long res_idx = 0;
842 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
844 if(!verifyObjectPCRUD(ctx, cur)) continue;
846 osrfAppRespond( ctx, cur );
848 osrfAppRespondComplete( ctx, NULL );
850 } else if (!strcmp(methodtype, "id_list")) {
852 jsonObject* where_clause;
853 jsonObject* rest_of_query;
855 // We use the where clause without change. But we need
856 // to massage the rest of the query, so we work with a copy
857 // of it instead of modifying the original.
859 where_clause = jsonObjectGetIndex( ctx->params, 1 );
860 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
862 where_clause = jsonObjectGetIndex( ctx->params, 0 );
863 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
866 if ( rest_of_query ) {
867 jsonObjectRemoveKey( rest_of_query, "select" );
868 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
869 jsonObjectRemoveKey( rest_of_query, "flesh" );
870 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
872 rest_of_query = jsonNewObjectType( JSON_HASH );
875 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
877 // Build a SELECT list containing just the primary key,
878 // i.e. like { "classname":["keyname"] }
879 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
880 jsonObjectPush( col_list_obj, // Load array with name of primary key
881 jsonNewObject( osrfHashGet( class_obj, "primarykey" ) ) );
882 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
883 jsonObjectSetKey( select_clause, osrfHashGet( class_obj, "classname" ), col_list_obj );
885 jsonObjectSetKey( rest_of_query, "select", select_clause );
887 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
889 jsonObjectFree( rest_of_query );
893 unsigned long res_idx = 0;
894 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
896 if(!verifyObjectPCRUD(ctx, cur)) continue;
900 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
903 osrfAppRespondComplete( ctx, NULL );
906 osrfAppRespondComplete( ctx, obj );
914 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
917 osrfHash* meta = (osrfHash*) ctx->method->userData;
918 osrfHash* class = osrfHashGet( meta, "class" );
920 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
922 const char* temp_classname = param->classname;
923 if( ! temp_classname )
924 temp_classname = "(null)";
926 growing_buffer* msg = buffer_init(128);
929 "%s: %s method for type %s was passed a %s",
931 osrfHashGet(meta, "methodtype"),
932 osrfHashGet(class, "classname"),
936 char* m = buffer_release(msg);
937 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException", ctx->request, m );
945 ret = verifyObjectPCRUD( ctx, param );
953 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
954 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
955 jsonObject* auth_object = jsonNewObject(auth);
956 jsonObject* user = oilsUtilsQuickReq("open-ils.auth","open-ils.auth.session.retrieve", auth_object);
957 jsonObjectFree(auth_object);
959 if (!user->classname || strcmp(user->classname, "au")) {
961 growing_buffer* msg = buffer_init(128);
964 "%s: permacrud received a bad auth token: %s",
969 char* m = buffer_release(msg);
970 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException", ctx->request, m );
973 jsonObjectFree(user);
981 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
983 dbhandle = writehandle;
985 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
986 osrfHash* class = osrfHashGet( method_metadata, "class" );
987 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
990 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
991 method_type = "retrieve"; // search and id_list are equivalant to retrieve for this
992 } else if ( *method_type == 'u' || *method_type == 'd' ) {
993 fetch = 1; // MUST go to the db for the object for update and delete
996 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
999 // No permacrud for this method type on this class
1001 growing_buffer* msg = buffer_init(128);
1004 "%s: %s on class %s has no permacrud IDL entry",
1006 osrfHashGet(method_metadata, "methodtype"),
1007 osrfHashGet(class, "classname")
1010 char* m = buffer_release(msg);
1011 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN, "osrfMethodException", ctx->request, m );
1018 jsonObject* user = verifyUserPCRUD( ctx );
1019 if (!user) return 0;
1021 int userid = atoi( oilsFMGetString( user, "id" ) );
1022 jsonObjectFree(user);
1024 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
1025 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
1026 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
1028 osrfStringArray* context_org_array = osrfNewStringArray(1);
1031 char* pkey_value = NULL;
1032 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1033 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions required, fetching top of the org tree" );
1035 // check for perm at top of org tree
1036 char* org_tree_root_id = org_tree_root( ctx );
1037 if( org_tree_root_id ) {
1038 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1039 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1041 osrfStringArrayFree( context_org_array );
1046 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, fetching context org ids" );
1047 const char* pkey = osrfHashGet(class, "primarykey");
1048 jsonObject *param = NULL;
1050 if (obj->classname) {
1051 pkey_value = oilsFMGetString( obj, pkey );
1052 if (!fetch) param = jsonObjectClone(obj);
1053 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s", pkey_value );
1055 pkey_value = jsonObjectToSimpleString( obj );
1057 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value of %s and retrieving from the database", pkey_value );
1061 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1062 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1063 jsonObjectFree(_tmp_params);
1065 param = jsonObjectExtractIndex(_list, 0);
1066 jsonObjectFree(_list);
1070 osrfLogDebug( OSRF_LOG_MARK, "Object not found in the database with primary key %s of %s", pkey, pkey_value );
1072 growing_buffer* msg = buffer_init(128);
1075 "%s: no object found with primary key %s of %s",
1081 char* m = buffer_release(msg);
1082 osrfAppSessionStatus(
1084 OSRF_STATUS_INTERNALSERVERERROR,
1085 "osrfMethodException",
1091 if (pkey_value) free(pkey_value);
1096 if (local_context->size > 0) {
1097 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified", local_context->size);
1099 char* lcontext = NULL;
1100 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1101 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1104 "adding class-local field %s (value: %s) to the context org list",
1106 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1112 if (foreign_context) {
1113 unsigned long class_count = osrfHashGetCount( foreign_context );
1114 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count);
1116 if (class_count > 0) {
1118 osrfHash* fcontext = NULL;
1119 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1120 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1121 const char* class_name = osrfHashIteratorKey( class_itr );
1122 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1126 "%d foreign context fields(s) specified for class %s",
1127 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1131 char* foreign_pkey = osrfHashGet(fcontext, "field");
1132 char* foreign_pkey_value = oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1134 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1136 jsonObject* _list = doFieldmapperSearch(
1137 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1139 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1140 jsonObjectFree(_tmp_params);
1141 jsonObjectFree(_list);
1143 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1145 if (_fparam && jump_list) {
1148 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1149 free(foreign_pkey_value);
1151 osrfHash* foreign_link_hash = oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1153 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1154 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1156 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1158 _list = doFieldmapperSearch(
1160 osrfHashGet( oilsIDL(), osrfHashGet( foreign_link_hash, "class" ) ),
1166 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1167 jsonObjectFree(_tmp_params);
1168 jsonObjectFree(_list);
1175 growing_buffer* msg = buffer_init(128);
1178 "%s: no object found with primary key %s of %s",
1184 char* m = buffer_release(msg);
1185 osrfAppSessionStatus(
1187 OSRF_STATUS_INTERNALSERVERERROR,
1188 "osrfMethodException",
1194 osrfHashIteratorFree(class_itr);
1195 free(foreign_pkey_value);
1196 jsonObjectFree(param);
1201 free(foreign_pkey_value);
1204 char* foreign_field = NULL;
1205 while ( (foreign_field = osrfStringArrayGetString(osrfHashGet(fcontext,"context"), j++)) ) {
1206 osrfStringArrayAdd( context_org_array, oilsFMGetString( _fparam, foreign_field ) );
1209 "adding foreign class %s field %s (value: %s) to the context org list",
1212 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1216 jsonObjectFree(_fparam);
1219 osrfHashIteratorFree( class_itr );
1223 jsonObjectFree(param);
1226 char* context_org = NULL;
1230 if (permission->size == 0) {
1231 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1236 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1238 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1244 "Checking object permission [%s] for user %d on object %s (class %s) at org %d",
1248 osrfHashGet(class, "classname"),
1252 result = dbi_conn_queryf(
1254 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1257 osrfHashGet(class, "classname"),
1265 "Received a result for object permission [%s] for user %d on object %s (class %s) at org %d",
1269 osrfHashGet(class, "classname"),
1273 if (dbi_result_first_row(result)) {
1274 jsonObject* return_val = oilsMakeJSONFromResult( result );
1275 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1279 "Status of object permission [%s] for user %d on object %s (class %s) at org %d is %s",
1283 osrfHashGet(class, "classname"),
1288 if ( *has_perm == 't' ) OK = 1;
1289 jsonObjectFree(return_val);
1292 dbi_result_free(result);
1297 osrfLogDebug( OSRF_LOG_MARK, "Checking non-object permission [%s] for user %d at org %d", perm, userid, atoi(context_org) );
1298 result = dbi_conn_queryf(
1300 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1307 osrfLogDebug( OSRF_LOG_MARK, "Received a result for permission [%s] for user %d at org %d",
1308 perm, userid, atoi(context_org) );
1309 if ( dbi_result_first_row(result) ) {
1310 jsonObject* return_val = oilsMakeJSONFromResult( result );
1311 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1312 osrfLogDebug( OSRF_LOG_MARK, "Status of permission [%s] for user %d at org %d is [%s]",
1313 perm, userid, atoi(context_org), has_perm );
1314 if ( *has_perm == 't' ) OK = 1;
1315 jsonObjectFree(return_val);
1318 dbi_result_free(result);
1326 if (pkey_value) free(pkey_value);
1327 osrfStringArrayFree(context_org_array);
1333 * Look up the root of the org_unit tree. If you find it, return
1334 * a string containing the id, which the caller is responsible for freeing.
1335 * Otherwise return NULL.
1337 static char* org_tree_root( osrfMethodContext* ctx ) {
1339 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1340 static time_t last_lookup_time = 0;
1341 time_t current_time = time( NULL );
1343 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1344 // We successfully looked this up less than an hour ago.
1345 // It's not likely to have changed since then.
1346 return strdup( cached_root_id );
1348 last_lookup_time = current_time;
1351 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1352 jsonObject* result = doFieldmapperSearch(
1353 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1354 jsonObjectFree( where_clause );
1356 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1359 jsonObjectFree( result );
1361 growing_buffer* msg = buffer_init(128);
1362 OSRF_BUFFER_ADD( msg, MODULENAME );
1363 OSRF_BUFFER_ADD( msg,
1364 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1366 char* m = buffer_release(msg);
1367 osrfAppSessionStatus( ctx->session,
1368 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1371 cached_root_id[ 0 ] = '\0';
1375 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1376 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1378 jsonObjectFree( result );
1380 strcpy( cached_root_id, root_org_unit_id );
1381 return root_org_unit_id;
1385 Utility function: create a JSON_HASH with a single key/value pair.
1386 This function is equivalent to:
1388 jsonParseStringFmt( "{\"%s\":\"%s\"}", key, value )
1390 or, if value is NULL:
1392 jsonParseStringFmt( "{\"%s\":null}", key )
1394 ...but faster because it doesn't create and parse a JSON string.
1396 static jsonObject* single_hash( const char* key, const char* value ) {
1398 if( ! key ) key = "";
1400 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1401 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1407 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1409 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1411 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1412 jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1414 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1415 jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1418 if (!verifyObjectClass(ctx, target)) {
1423 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1425 char* trans_id = NULL;
1426 if( ctx->session && ctx->session->userData )
1427 trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
1430 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1432 osrfAppSessionStatus(
1434 OSRF_STATUS_BADREQUEST,
1435 "osrfMethodException",
1437 "No active transaction -- required for CREATE"
1443 // The following test is harmless but redundant. If a class is
1444 // readonly, we don't register a create method for it.
1445 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1446 osrfAppSessionStatus(
1448 OSRF_STATUS_BADREQUEST,
1449 "osrfMethodException",
1451 "Cannot INSERT readonly class"
1457 // Set the last_xact_id
1458 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1460 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
1461 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1464 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1466 dbhandle = writehandle;
1468 osrfHash* fields = osrfHashGet(meta, "fields");
1469 char* pkey = osrfHashGet(meta, "primarykey");
1470 char* seq = osrfHashGet(meta, "sequence");
1472 growing_buffer* table_buf = buffer_init(128);
1473 growing_buffer* col_buf = buffer_init(128);
1474 growing_buffer* val_buf = buffer_init(128);
1476 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1477 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1478 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1479 buffer_add(val_buf,"VALUES (");
1483 osrfHash* field = NULL;
1484 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1485 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1487 const char* field_name = osrfHashIteratorKey( field_itr );
1489 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1492 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1495 if (field_object && field_object->classname) {
1496 value = oilsFMGetString(
1498 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1501 value = jsonObjectToSimpleString( field_object );
1505 osrfAppSessionStatus(
1507 OSRF_STATUS_BADREQUEST,
1508 "osrfMethodException",
1510 "No value available for column"
1514 "%s: NULL value for field %s of class %s",
1517 osrfHashGet(meta, "classname")
1519 osrfHashIteratorFree( field_itr );
1520 buffer_free( val_buf );
1521 buffer_free( col_buf );
1522 buffer_free( table_buf );
1530 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1531 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1534 buffer_add(col_buf, field_name);
1536 if (!field_object || field_object->type == JSON_NULL) {
1537 buffer_add( val_buf, "DEFAULT" );
1539 } else if ( !strcmp(get_primitive( field ), "number") ) {
1540 const char* numtype = get_datatype( field );
1541 if ( !strcmp( numtype, "INT8") ) {
1542 buffer_fadd( val_buf, "%lld", atoll(value) );
1544 } else if ( !strcmp( numtype, "INT") ) {
1545 buffer_fadd( val_buf, "%d", atoi(value) );
1547 } else if ( !strcmp( numtype, "NUMERIC") ) {
1548 buffer_fadd( val_buf, "%f", atof(value) );
1551 if ( dbi_conn_quote_string(writehandle, &value) ) {
1552 OSRF_BUFFER_ADD( val_buf, value );
1555 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1556 osrfAppSessionStatus(
1558 OSRF_STATUS_INTERNALSERVERERROR,
1559 "osrfMethodException",
1561 "Error quoting string -- please see the error log for more details"
1564 buffer_free(table_buf);
1565 buffer_free(col_buf);
1566 buffer_free(val_buf);
1576 osrfHashIteratorFree( field_itr );
1578 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1579 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1581 char* table_str = buffer_release(table_buf);
1582 char* col_str = buffer_release(col_buf);
1583 char* val_str = buffer_release(val_buf);
1584 growing_buffer* sql = buffer_init(128);
1585 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1590 char* query = buffer_release(sql);
1592 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1595 dbi_result result = dbi_conn_query(writehandle, query);
1597 jsonObject* obj = NULL;
1600 obj = jsonNewObject(NULL);
1603 "%s ERROR inserting %s object using query [%s]",
1605 osrfHashGet(meta, "fieldmapper"),
1608 osrfAppSessionStatus(
1610 OSRF_STATUS_INTERNALSERVERERROR,
1611 "osrfMethodException",
1613 "INSERT error -- please see the error log for more details"
1618 char* id = oilsFMGetString(target, pkey);
1620 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1621 growing_buffer* _id = buffer_init(10);
1622 buffer_fadd(_id, "%lld", new_id);
1623 id = buffer_release(_id);
1626 // Find quietness specification, if present
1627 const char* quiet_str = NULL;
1629 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1631 quiet_str = jsonObjectGetString( quiet_obj );
1634 if( str_is_true( quiet_str ) ) { // if quietness is specified
1635 obj = jsonNewObject(id);
1639 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1640 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1642 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1644 jsonObjectFree( where_clause );
1649 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1652 jsonObjectFree( list );
1665 * Fetch one row from a specified table, using a specified value
1666 * for the primary key
1668 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1678 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1680 const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos); // key value
1684 "%s retrieving %s object with primary key value of %s",
1686 osrfHashGet( class_def, "fieldmapper" ),
1687 jsonObjectGetString( id_obj )
1690 // Build a WHERE clause based on the key value
1691 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1694 osrfHashGet( class_def, "primarykey" ),
1695 jsonObjectClone( id_obj )
1698 jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
1700 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
1702 jsonObjectFree( where_clause );
1706 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
1707 jsonObjectFree( list );
1710 if(!verifyObjectPCRUD(ctx, obj)) {
1711 jsonObjectFree(obj);
1714 growing_buffer* msg = buffer_init(128);
1715 OSRF_BUFFER_ADD( msg, MODULENAME );
1716 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1718 char* m = buffer_release(msg);
1719 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1730 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1731 growing_buffer* val_buf = buffer_init(32);
1732 const char* numtype = get_datatype( field );
1734 if ( !strncmp( numtype, "INT", 3 ) ) {
1735 if (value->type == JSON_NUMBER)
1736 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1737 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1739 //const char* val_str = jsonObjectGetString( value );
1740 //buffer_fadd( val_buf, "%ld", atol(val_str) );
1741 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1744 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
1745 if (value->type == JSON_NUMBER)
1746 //buffer_fadd( val_buf, "%f", jsonObjectGetNumber(value) );
1747 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1749 //const char* val_str = jsonObjectGetString( value );
1750 //buffer_fadd( val_buf, "%f", atof(val_str) );
1751 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1755 // Presumably this was really intended ot be a string, so quote it
1756 char* str = jsonObjectToSimpleString( value );
1757 if ( dbi_conn_quote_string(dbhandle, &str) ) {
1758 OSRF_BUFFER_ADD( val_buf, str );
1761 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
1763 buffer_free(val_buf);
1768 return buffer_release(val_buf);
1771 static char* searchINPredicate (const char* class_alias, osrfHash* field,
1772 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1773 growing_buffer* sql_buf = buffer_init(32);
1779 osrfHashGet(field, "name")
1783 buffer_add(sql_buf, "IN (");
1784 } else if (!(strcasecmp(op,"not in"))) {
1785 buffer_add(sql_buf, "NOT IN (");
1787 buffer_add(sql_buf, "IN (");
1790 if (node->type == JSON_HASH) {
1791 // subquery predicate
1792 char* subpred = SELECT(
1794 jsonObjectGetKey( node, "select" ),
1795 jsonObjectGetKey( node, "from" ),
1796 jsonObjectGetKey( node, "where" ),
1797 jsonObjectGetKey( node, "having" ),
1798 jsonObjectGetKey( node, "order_by" ),
1799 jsonObjectGetKey( node, "limit" ),
1800 jsonObjectGetKey( node, "offset" ),
1806 buffer_add(sql_buf, subpred);
1809 buffer_free( sql_buf );
1813 } else if (node->type == JSON_ARRAY) {
1814 // literal value list
1815 int in_item_index = 0;
1816 int in_item_first = 1;
1817 const jsonObject* in_item;
1818 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1823 buffer_add(sql_buf, ", ");
1826 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1827 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1828 MODULENAME, json_type( in_item->type ) );
1829 buffer_free(sql_buf);
1833 // Append the literal value -- quoted if not a number
1834 if ( JSON_NUMBER == in_item->type ) {
1835 char* val = jsonNumberToDBString( field, in_item );
1836 OSRF_BUFFER_ADD( sql_buf, val );
1839 } else if ( !strcmp( get_primitive( field ), "number") ) {
1840 char* val = jsonNumberToDBString( field, in_item );
1841 OSRF_BUFFER_ADD( sql_buf, val );
1845 char* key_string = jsonObjectToSimpleString(in_item);
1846 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1847 OSRF_BUFFER_ADD( sql_buf, key_string );
1850 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1852 buffer_free(sql_buf);
1858 if( in_item_first ) {
1859 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1860 buffer_free( sql_buf );
1864 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1865 MODULENAME, json_type( node->type ) );
1866 buffer_free(sql_buf);
1870 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1872 return buffer_release(sql_buf);
1875 // Receive a JSON_ARRAY representing a function call. The first
1876 // entry in the array is the function name. The rest are parameters.
1877 static char* searchValueTransform( const jsonObject* array ) {
1879 if( array->size < 1 ) {
1880 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
1884 // Get the function name
1885 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
1886 if( func_item->type != JSON_STRING ) {
1887 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
1888 MODULENAME, json_type( func_item->type ) );
1892 growing_buffer* sql_buf = buffer_init(32);
1894 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1895 OSRF_BUFFER_ADD( sql_buf, "( " );
1897 // Get the parameters
1898 int func_item_index = 1; // We already grabbed the zeroth entry
1899 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1901 // Add a separator comma, if we need one
1902 if( func_item_index > 2 )
1903 buffer_add( sql_buf, ", " );
1905 // Add the current parameter
1906 if (func_item->type == JSON_NULL) {
1907 buffer_add( sql_buf, "NULL" );
1909 char* val = jsonObjectToSimpleString(func_item);
1910 if ( dbi_conn_quote_string(dbhandle, &val) ) {
1911 OSRF_BUFFER_ADD( sql_buf, val );
1914 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1915 buffer_free(sql_buf);
1922 buffer_add( sql_buf, " )" );
1924 return buffer_release(sql_buf);
1927 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
1928 const jsonObject* node, const char* op) {
1930 if( ! is_good_operator( op ) ) {
1931 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
1935 char* val = searchValueTransform(node);
1939 growing_buffer* sql_buf = buffer_init(32);
1944 osrfHashGet(field, "name"),
1951 return buffer_release(sql_buf);
1954 // class_alias is a class name or other table alias
1955 // field is a field definition as stored in the IDL
1956 // node comes from the method parameter, and may represent an entry in the SELECT list
1957 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
1958 growing_buffer* sql_buf = buffer_init(32);
1960 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
1961 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
1963 if(transform_subcolumn) {
1964 if( ! is_identifier( transform_subcolumn ) ) {
1965 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
1966 MODULENAME, transform_subcolumn );
1967 buffer_free( sql_buf );
1970 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
1973 if (field_transform) {
1975 if( ! is_identifier( field_transform ) ) {
1976 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
1977 MODULENAME, field_transform );
1978 buffer_free( sql_buf );
1982 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class_alias, osrfHashGet(field, "name"));
1983 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
1986 if( array->type != JSON_ARRAY ) {
1987 osrfLogError( OSRF_LOG_MARK,
1988 "%s: Expected JSON_ARRAY for function params; found %s",
1989 MODULENAME, json_type( array->type ) );
1990 buffer_free( sql_buf );
1993 int func_item_index = 0;
1994 jsonObject* func_item;
1995 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1997 char* val = jsonObjectToSimpleString(func_item);
2000 buffer_add( sql_buf, ",NULL" );
2001 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
2002 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2003 OSRF_BUFFER_ADD( sql_buf, val );
2005 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
2007 buffer_free(sql_buf);
2014 buffer_add( sql_buf, " )" );
2017 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
2020 if (transform_subcolumn)
2021 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2023 return buffer_release(sql_buf);
2026 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2027 const jsonObject* node, const char* op ) {
2029 if( ! is_good_operator( op ) ) {
2030 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2034 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2035 if( ! field_transform )
2038 int extra_parens = 0; // boolean
2040 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2041 if ( ! value_obj ) {
2042 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2044 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
2045 free(field_transform);
2049 } else if ( value_obj->type == JSON_ARRAY ) {
2050 value = searchValueTransform( value_obj );
2052 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
2053 free( field_transform );
2056 } else if ( value_obj->type == JSON_HASH ) {
2057 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2059 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
2060 free(field_transform);
2064 } else if ( value_obj->type == JSON_NUMBER ) {
2065 value = jsonNumberToDBString( field, value_obj );
2066 } else if ( value_obj->type == JSON_NULL ) {
2067 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
2068 free(field_transform);
2070 } else if ( value_obj->type == JSON_BOOL ) {
2071 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
2072 free(field_transform);
2075 if ( !strcmp( get_primitive( field ), "number") ) {
2076 value = jsonNumberToDBString( field, value_obj );
2078 value = jsonObjectToSimpleString( value_obj );
2079 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2080 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
2082 free(field_transform);
2088 const char* left_parens = "";
2089 const char* right_parens = "";
2091 if( extra_parens ) {
2096 growing_buffer* sql_buf = buffer_init(32);
2100 "%s%s %s %s %s %s%s",
2111 free(field_transform);
2113 return buffer_release(sql_buf);
2116 static char* searchSimplePredicate (const char* op, const char* class_alias,
2117 osrfHash* field, const jsonObject* node) {
2119 if( ! is_good_operator( op ) ) {
2120 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2126 // Get the value to which we are comparing the specified column
2127 if (node->type != JSON_NULL) {
2128 if ( node->type == JSON_NUMBER ) {
2129 val = jsonNumberToDBString( field, node );
2130 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2131 val = jsonNumberToDBString( field, node );
2133 val = jsonObjectToSimpleString(node);
2138 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2139 // Value is not numeric; enclose it in quotes
2140 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2141 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2147 // Compare to a null value
2148 val = strdup( "NULL" );
2149 if (strcmp( op, "=" ))
2155 growing_buffer* sql_buf = buffer_init(32);
2156 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2157 char* pred = buffer_release( sql_buf );
2164 static char* searchBETWEENPredicate (const char* class_alias,
2165 osrfHash* field, const jsonObject* node) {
2167 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2168 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2170 if( NULL == y_node ) {
2171 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2174 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2175 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2182 if ( !strcmp( get_primitive( field ), "number") ) {
2183 x_string = jsonNumberToDBString(field, x_node);
2184 y_string = jsonNumberToDBString(field, y_node);
2187 x_string = jsonObjectToSimpleString(x_node);
2188 y_string = jsonObjectToSimpleString(y_node);
2189 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2190 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2191 MODULENAME, x_string, y_string);
2198 growing_buffer* sql_buf = buffer_init(32);
2199 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2200 class_alias, osrfHashGet(field, "name"), x_string, y_string );
2204 return buffer_release(sql_buf);
2207 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2208 jsonObject* node, osrfMethodContext* ctx ) {
2211 if (node->type == JSON_ARRAY) { // equality IN search
2212 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2213 } else if (node->type == JSON_HASH) { // other search
2214 jsonIterator* pred_itr = jsonNewIterator( node );
2215 if( !jsonIteratorHasNext( pred_itr ) ) {
2216 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2217 MODULENAME, osrfHashGet(field, "name") );
2219 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2221 // Verify that there are no additional predicates
2222 if( jsonIteratorHasNext( pred_itr ) ) {
2223 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2224 MODULENAME, osrfHashGet(field, "name") );
2225 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2226 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2227 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2228 pred = searchINPredicate( class_info->alias, field, pred_node, pred_itr->key, ctx );
2229 else if ( pred_node->type == JSON_ARRAY )
2230 pred = searchFunctionPredicate( class_info->alias, field, pred_node, pred_itr->key );
2231 else if ( pred_node->type == JSON_HASH )
2232 pred = searchFieldTransformPredicate( class_info, field, pred_node, pred_itr->key );
2234 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2236 jsonIteratorFree(pred_itr);
2238 } else if (node->type == JSON_NULL) { // IS NULL search
2239 growing_buffer* _p = buffer_init(64);
2242 "\"%s\".%s IS NULL",
2243 class_info->class_name,
2244 osrfHashGet(field, "name")
2246 pred = buffer_release(_p);
2247 } else { // equality search
2248 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2267 field : call_number,
2283 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2285 const jsonObject* working_hash;
2286 jsonObject* freeable_hash = NULL;
2288 if (join_hash->type == JSON_HASH) {
2289 working_hash = join_hash;
2290 } else if (join_hash->type == JSON_STRING) {
2291 // turn it into a JSON_HASH by creating a wrapper
2292 // around a copy of the original
2293 const char* _tmp = jsonObjectGetString( join_hash );
2294 freeable_hash = jsonNewObjectType(JSON_HASH);
2295 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2296 working_hash = freeable_hash;
2300 "%s: JOIN failed; expected JSON object type not found",
2306 growing_buffer* join_buf = buffer_init(128);
2307 const char* leftclass = left_info->class_name;
2309 jsonObject* snode = NULL;
2310 jsonIterator* search_itr = jsonNewIterator( working_hash );
2312 while ( (snode = jsonIteratorNext( search_itr )) ) {
2313 const char* right_alias = search_itr->key;
2315 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2317 class = right_alias;
2319 const ClassInfo* right_info = add_joined_class( right_alias, class );
2323 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2327 jsonIteratorFree( search_itr );
2328 buffer_free( join_buf );
2330 jsonObjectFree( freeable_hash );
2333 osrfHash* links = right_info->links;
2334 const char* table = right_info->source_def;
2336 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2337 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2339 if (field && !fkey) {
2340 // Look up the corresponding join column in the IDL.
2341 // The link must be defined in the child table,
2342 // and point to the right parent table.
2343 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2344 const char* reltype = NULL;
2345 const char* other_class = NULL;
2346 reltype = osrfHashGet( idl_link, "reltype" );
2347 if( reltype && strcmp( reltype, "has_many" ) )
2348 other_class = osrfHashGet( idl_link, "class" );
2349 if( other_class && !strcmp( other_class, leftclass ) )
2350 fkey = osrfHashGet( idl_link, "key" );
2354 "%s: JOIN failed. No link defined from %s.%s to %s",
2360 buffer_free(join_buf);
2362 jsonObjectFree(freeable_hash);
2363 jsonIteratorFree(search_itr);
2367 } else if (!field && fkey) {
2368 // Look up the corresponding join column in the IDL.
2369 // The link must be defined in the child table,
2370 // and point to the right parent table.
2371 osrfHash* left_links = left_info->links;
2372 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2373 const char* reltype = NULL;
2374 const char* other_class = NULL;
2375 reltype = osrfHashGet( idl_link, "reltype" );
2376 if( reltype && strcmp( reltype, "has_many" ) )
2377 other_class = osrfHashGet( idl_link, "class" );
2378 if( other_class && !strcmp( other_class, class ) )
2379 field = osrfHashGet( idl_link, "key" );
2383 "%s: JOIN failed. No link defined from %s.%s to %s",
2389 buffer_free(join_buf);
2391 jsonObjectFree(freeable_hash);
2392 jsonIteratorFree(search_itr);
2396 } else if (!field && !fkey) {
2397 osrfHash* left_links = left_info->links;
2399 // For each link defined for the left class:
2400 // see if the link references the joined class
2401 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2402 osrfHash* curr_link = NULL;
2403 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2404 const char* other_class = osrfHashGet( curr_link, "class" );
2405 if( other_class && !strcmp( other_class, class ) ) {
2407 // In the IDL, the parent class doesn't know then names of the child
2408 // columns that are pointing to it, so don't use that end of the link
2409 const char* reltype = osrfHashGet( curr_link, "reltype" );
2410 if( reltype && strcmp( reltype, "has_many" ) ) {
2411 // Found a link between the classes
2412 fkey = osrfHashIteratorKey( itr );
2413 field = osrfHashGet( curr_link, "key" );
2418 osrfHashIteratorFree( itr );
2420 if (!field || !fkey) {
2421 // Do another such search, with the classes reversed
2423 // For each link defined for the joined class:
2424 // see if the link references the left class
2425 osrfHashIterator* itr = osrfNewHashIterator( links );
2426 osrfHash* curr_link = NULL;
2427 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2428 const char* other_class = osrfHashGet( curr_link, "class" );
2429 if( other_class && !strcmp( other_class, leftclass ) ) {
2431 // In the IDL, the parent class doesn't know then names of the child
2432 // columns that are pointing to it, so don't use that end of the link
2433 const char* reltype = osrfHashGet( curr_link, "reltype" );
2434 if( reltype && strcmp( reltype, "has_many" ) ) {
2435 // Found a link between the classes
2436 field = osrfHashIteratorKey( itr );
2437 fkey = osrfHashGet( curr_link, "key" );
2442 osrfHashIteratorFree( itr );
2445 if (!field || !fkey) {
2448 "%s: JOIN failed. No link defined between %s and %s",
2453 buffer_free(join_buf);
2455 jsonObjectFree(freeable_hash);
2456 jsonIteratorFree(search_itr);
2462 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2464 if ( !strcasecmp(type,"left") ) {
2465 buffer_add(join_buf, " LEFT JOIN");
2466 } else if ( !strcasecmp(type,"right") ) {
2467 buffer_add(join_buf, " RIGHT JOIN");
2468 } else if ( !strcasecmp(type,"full") ) {
2469 buffer_add(join_buf, " FULL JOIN");
2471 buffer_add(join_buf, " INNER JOIN");
2474 buffer_add(join_buf, " INNER JOIN");
2477 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2478 table, right_alias, right_alias, field, left_info->alias, fkey);
2480 // Add any other join conditions as specified by "filter"
2481 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2483 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2484 if ( filter_op && !strcasecmp("or",filter_op) ) {
2485 buffer_add( join_buf, " OR " );
2487 buffer_add( join_buf, " AND " );
2490 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2492 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2493 OSRF_BUFFER_ADD( join_buf, jpred );
2498 "%s: JOIN failed. Invalid conditional expression.",
2501 jsonIteratorFree( search_itr );
2502 buffer_free( join_buf );
2504 jsonObjectFree( freeable_hash );
2509 buffer_add(join_buf, " ) ");
2511 // Recursively add a nested join, if one is present
2512 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2514 char* jpred = searchJOIN( join_filter, right_info );
2516 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2517 OSRF_BUFFER_ADD( join_buf, jpred );
2520 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2521 jsonIteratorFree( search_itr );
2522 buffer_free( join_buf );
2524 jsonObjectFree( freeable_hash );
2531 jsonObjectFree(freeable_hash);
2532 jsonIteratorFree(search_itr);
2534 return buffer_release(join_buf);
2539 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2540 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2541 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2543 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2545 search_hash is the JSON expression of the conditions.
2546 meta is the class definition from the IDL, for the relevant table.
2547 opjoin_type indicates whether multiple conditions, if present, should be
2548 connected by AND or OR.
2549 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2550 to pass it to other functions -- and all they do with it is to use the session
2551 and request members to send error messages back to the client.
2555 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
2556 int opjoin_type, osrfMethodContext* ctx ) {
2560 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2563 class_info->class_def,
2568 growing_buffer* sql_buf = buffer_init(128);
2570 jsonObject* node = NULL;
2573 if ( search_hash->type == JSON_ARRAY ) {
2574 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2575 if( 0 == search_hash->size ) {
2578 "%s: Invalid predicate structure: empty JSON array",
2581 buffer_free( sql_buf );
2585 unsigned long i = 0;
2586 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
2590 if (opjoin_type == OR_OP_JOIN)
2591 buffer_add(sql_buf, " OR ");
2593 buffer_add(sql_buf, " AND ");
2596 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2598 buffer_free( sql_buf );
2602 buffer_fadd(sql_buf, "( %s )", subpred);
2606 } else if ( search_hash->type == JSON_HASH ) {
2607 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2608 jsonIterator* search_itr = jsonNewIterator( search_hash );
2609 if( !jsonIteratorHasNext( search_itr ) ) {
2612 "%s: Invalid predicate structure: empty JSON object",
2615 jsonIteratorFree( search_itr );
2616 buffer_free( sql_buf );
2620 while ( (node = jsonIteratorNext( search_itr )) ) {
2625 if (opjoin_type == OR_OP_JOIN)
2626 buffer_add(sql_buf, " OR ");
2628 buffer_add(sql_buf, " AND ");
2631 if ( '+' == search_itr->key[ 0 ] ) {
2633 // This plus sign prefixes a class name or other table alias;
2634 // make sure the table alias is in scope
2635 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
2636 if( ! alias_info ) {
2639 "%s: Invalid table alias \"%s\" in WHERE clause",
2643 jsonIteratorFree( search_itr );
2644 buffer_free( sql_buf );
2648 if ( node->type == JSON_STRING ) {
2649 // It's the name of a column; make sure it belongs to the class
2650 const char* fieldname = jsonObjectGetString( node );
2651 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
2654 "%s: Invalid column name \"%s\" in WHERE clause for table alias \"%s\"",
2659 jsonIteratorFree( search_itr );
2660 buffer_free( sql_buf );
2664 buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
2666 // It's something more complicated
2667 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
2669 jsonIteratorFree( search_itr );
2670 buffer_free( sql_buf );
2674 buffer_fadd(sql_buf, "( %s )", subpred);
2677 } else if ( '-' == search_itr->key[ 0 ] ) {
2678 if ( !strcasecmp("-or",search_itr->key) ) {
2679 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
2681 jsonIteratorFree( search_itr );
2682 buffer_free( sql_buf );
2686 buffer_fadd(sql_buf, "( %s )", subpred);
2688 } else if ( !strcasecmp("-and",search_itr->key) ) {
2689 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2691 jsonIteratorFree( search_itr );
2692 buffer_free( sql_buf );
2696 buffer_fadd(sql_buf, "( %s )", subpred);
2698 } else if ( !strcasecmp("-not",search_itr->key) ) {
2699 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2701 jsonIteratorFree( search_itr );
2702 buffer_free( sql_buf );
2706 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2708 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2709 char* subpred = SELECT(
2711 jsonObjectGetKey( node, "select" ),
2712 jsonObjectGetKey( node, "from" ),
2713 jsonObjectGetKey( node, "where" ),
2714 jsonObjectGetKey( node, "having" ),
2715 jsonObjectGetKey( node, "order_by" ),
2716 jsonObjectGetKey( node, "limit" ),
2717 jsonObjectGetKey( node, "offset" ),
2723 jsonIteratorFree( search_itr );
2724 buffer_free( sql_buf );
2728 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2730 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2731 char* subpred = SELECT(
2733 jsonObjectGetKey( node, "select" ),
2734 jsonObjectGetKey( node, "from" ),
2735 jsonObjectGetKey( node, "where" ),
2736 jsonObjectGetKey( node, "having" ),
2737 jsonObjectGetKey( node, "order_by" ),
2738 jsonObjectGetKey( node, "limit" ),
2739 jsonObjectGetKey( node, "offset" ),
2745 jsonIteratorFree( search_itr );
2746 buffer_free( sql_buf );
2750 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2752 } else { // Invalid "minus" operator
2755 "%s: Invalid operator \"%s\" in WHERE clause",
2759 jsonIteratorFree( search_itr );
2760 buffer_free( sql_buf );
2766 const char* class = class_info->class_name;
2767 osrfHash* fields = class_info->fields;
2768 osrfHash* field = osrfHashGet( fields, search_itr->key );
2771 const char* table = class_info->source_def;
2774 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2777 table ? table : "?",
2780 jsonIteratorFree(search_itr);
2781 buffer_free(sql_buf);
2785 char* subpred = searchPredicate( class_info, field, node, ctx );
2787 buffer_free(sql_buf);
2788 jsonIteratorFree(search_itr);
2792 buffer_add( sql_buf, subpred );
2796 jsonIteratorFree(search_itr);
2799 // ERROR ... only hash and array allowed at this level
2800 char* predicate_string = jsonObjectToJSON( search_hash );
2803 "%s: Invalid predicate structure: %s",
2807 buffer_free(sql_buf);
2808 free(predicate_string);
2812 return buffer_release(sql_buf);
2815 /* Build a JSON_ARRAY of field names for a given table alias
2817 static jsonObject* defaultSelectList( const char* table_alias ) {
2822 ClassInfo* class_info = search_all_alias( table_alias );
2823 if( ! class_info ) {
2826 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
2833 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
2834 osrfHash* field_def = NULL;
2835 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
2836 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2837 const char* field_name = osrfHashIteratorKey( field_itr );
2838 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2839 jsonObjectPush( array, jsonNewObject( field_name ) );
2842 osrfHashIteratorFree( field_itr );
2848 /* method context */ osrfMethodContext* ctx,
2850 /* SELECT */ jsonObject* selhash,
2851 /* FROM */ jsonObject* join_hash,
2852 /* WHERE */ jsonObject* search_hash,
2853 /* HAVING */ jsonObject* having_hash,
2854 /* ORDER BY */ jsonObject* order_hash,
2855 /* LIMIT */ jsonObject* limit,
2856 /* OFFSET */ jsonObject* offset,
2857 /* flags */ int flags
2859 const char* locale = osrf_message_get_last_locale();
2861 // general tmp objects
2862 const jsonObject* tmp_const;
2863 jsonObject* selclass = NULL;
2864 jsonObject* snode = NULL;
2865 jsonObject* onode = NULL;
2867 char* string = NULL;
2868 int from_function = 0;
2873 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale);
2875 // punt if there's no FROM clause
2876 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2879 "%s: FROM clause is missing or empty",
2883 osrfAppSessionStatus(
2885 OSRF_STATUS_INTERNALSERVERERROR,
2886 "osrfMethodException",
2888 "FROM clause is missing or empty in JSON query"
2893 // Push a node onto the stack for the current query. Every level of
2894 // subquery gets its own QueryFrame on the Stack.
2897 // the core search class
2898 const char* core_class = NULL;
2900 // get the core class -- the only key of the top level FROM clause, or a string
2901 if (join_hash->type == JSON_HASH) {
2902 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2903 snode = jsonIteratorNext( tmp_itr );
2905 // Populate the current QueryFrame with information
2906 // about the core class
2907 if( add_query_core( NULL, tmp_itr->key ) ) {
2909 osrfAppSessionStatus(
2911 OSRF_STATUS_INTERNALSERVERERROR,
2912 "osrfMethodException",
2914 "Unable to look up core class"
2918 core_class = curr_query->core.class_name;
2921 jsonObject* extra = jsonIteratorNext( tmp_itr );
2923 jsonIteratorFree( tmp_itr );
2926 // There shouldn't be more than one entry in join_hash
2930 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2934 osrfAppSessionStatus(
2936 OSRF_STATUS_INTERNALSERVERERROR,
2937 "osrfMethodException",
2939 "Malformed FROM clause in JSON query"
2941 return NULL; // Malformed join_hash; extra entry
2943 } else if (join_hash->type == JSON_ARRAY) {
2944 // We're selecting from a function, not from a table
2946 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
2949 } else if (join_hash->type == JSON_STRING) {
2950 // Populate the current QueryFrame with information
2951 // about the core class
2952 core_class = jsonObjectGetString( join_hash );
2954 if( add_query_core( NULL, core_class ) ) {
2956 osrfAppSessionStatus(
2958 OSRF_STATUS_INTERNALSERVERERROR,
2959 "osrfMethodException",
2961 "Unable to look up core class"
2969 "%s: FROM clause is unexpected JSON type: %s",
2971 json_type( join_hash->type )
2974 osrfAppSessionStatus(
2976 OSRF_STATUS_INTERNALSERVERERROR,
2977 "osrfMethodException",
2979 "Ill-formed FROM clause in JSON query"
2984 // Build the join clause, if any, while filling out the list
2985 // of joined classes in the current QueryFrame.
2986 char* join_clause = NULL;
2987 if( join_hash && ! from_function ) {
2989 join_clause = searchJOIN( join_hash, &curr_query->core );
2990 if( ! join_clause ) {
2992 osrfAppSessionStatus(
2994 OSRF_STATUS_INTERNALSERVERERROR,
2995 "osrfMethodException",
2997 "Unable to construct JOIN clause(s)"
3003 // For in case we don't get a select list
3004 jsonObject* defaultselhash = NULL;
3006 // if there is no select list, build a default select list ...
3007 if (!selhash && !from_function) {
3008 jsonObject* default_list = defaultSelectList( core_class );
3009 if( ! default_list ) {
3011 osrfAppSessionStatus(
3013 OSRF_STATUS_INTERNALSERVERERROR,
3014 "osrfMethodException",
3016 "Unable to build default SELECT clause in JSON query"
3018 free( join_clause );
3023 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
3024 jsonObjectSetKey( selhash, core_class, default_list );
3027 // The SELECT clause can be encoded only by a hash
3028 if( !from_function && selhash->type != JSON_HASH ) {
3031 "%s: Expected JSON_HASH for SELECT clause; found %s",
3033 json_type( selhash->type )
3037 osrfAppSessionStatus(
3039 OSRF_STATUS_INTERNALSERVERERROR,
3040 "osrfMethodException",
3042 "Malformed SELECT clause in JSON query"
3044 free( join_clause );
3048 // If you see a null or wild card specifier for the core class, or an
3049 // empty array, replace it with a default SELECT list
3050 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3052 int default_needed = 0; // boolean
3053 if( JSON_STRING == tmp_const->type
3054 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3056 else if( JSON_NULL == tmp_const->type )
3059 if( default_needed ) {
3060 // Build a default SELECT list
3061 jsonObject* default_list = defaultSelectList( core_class );
3062 if( ! default_list ) {
3064 osrfAppSessionStatus(
3066 OSRF_STATUS_INTERNALSERVERERROR,
3067 "osrfMethodException",
3069 "Can't build default SELECT clause in JSON query"
3071 free( join_clause );
3076 jsonObjectSetKey( selhash, core_class, default_list );
3080 // temp buffers for the SELECT list and GROUP BY clause
3081 growing_buffer* select_buf = buffer_init(128);
3082 growing_buffer* group_buf = buffer_init(128);
3084 int aggregate_found = 0; // boolean
3086 // Build a select list
3087 if(from_function) // From a function we select everything
3088 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3091 // Build the SELECT list as SQL
3095 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3096 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3098 const char* cname = selclass_itr->key;
3100 // Make sure the target relation is in the FROM clause.
3102 // At this point join_hash is a step down from the join_hash we
3103 // received as a parameter. If the original was a JSON_STRING,
3104 // then json_hash is now NULL. If the original was a JSON_HASH,
3105 // then json_hash is now the first (and only) entry in it,
3106 // denoting the core class. We've already excluded the
3107 // possibility that the original was a JSON_ARRAY, because in
3108 // that case from_function would be non-NULL, and we wouldn't
3111 // If the current table alias isn't in scope, bail out
3112 ClassInfo* class_info = search_alias( cname );
3113 if( ! class_info ) {
3116 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3121 osrfAppSessionStatus(
3123 OSRF_STATUS_INTERNALSERVERERROR,
3124 "osrfMethodException",
3126 "Selected class not in FROM clause in JSON query"
3128 jsonIteratorFree( selclass_itr );
3129 buffer_free( select_buf );
3130 buffer_free( group_buf );
3131 if( defaultselhash ) jsonObjectFree( defaultselhash );
3132 free( join_clause );
3136 if( selclass->type != JSON_ARRAY ) {
3139 "%s: Malformed SELECT list for class \"%s\"; not an array",
3144 osrfAppSessionStatus(
3146 OSRF_STATUS_INTERNALSERVERERROR,
3147 "osrfMethodException",
3149 "Selected class not in FROM clause in JSON query"
3152 jsonIteratorFree( selclass_itr );
3153 buffer_free( select_buf );
3154 buffer_free( group_buf );
3155 if( defaultselhash ) jsonObjectFree( defaultselhash );
3156 free( join_clause );
3160 // Look up some attributes of the current class
3161 osrfHash* idlClass = class_info->class_def;
3162 osrfHash* class_field_set = class_info->fields;
3163 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3164 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3166 if( 0 == selclass->size ) {
3169 "%s: No columns selected from \"%s\"",
3175 // stitch together the column list for the current table alias...
3176 unsigned long field_idx = 0;
3177 jsonObject* selfield = NULL;
3178 while((selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3180 // If we need a separator comma, add one
3184 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3187 // if the field specification is a string, add it to the list
3188 if (selfield->type == JSON_STRING) {
3190 // Look up the field in the IDL
3191 const char* col_name = jsonObjectGetString( selfield );
3192 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3194 // No such field in current class
3197 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3203 osrfAppSessionStatus(
3205 OSRF_STATUS_INTERNALSERVERERROR,
3206 "osrfMethodException",
3208 "Selected column not defined in JSON query"
3210 jsonIteratorFree( selclass_itr );
3211 buffer_free( select_buf );
3212 buffer_free( group_buf );
3213 if( defaultselhash ) jsonObjectFree( defaultselhash );
3214 free( join_clause );
3216 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3217 // Virtual field not allowed
3220 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3226 osrfAppSessionStatus(
3228 OSRF_STATUS_INTERNALSERVERERROR,
3229 "osrfMethodException",
3231 "Selected column may not be virtual in JSON query"
3233 jsonIteratorFree( selclass_itr );
3234 buffer_free( select_buf );
3235 buffer_free( group_buf );
3236 if( defaultselhash ) jsonObjectFree( defaultselhash );
3237 free( join_clause );
3243 if (flags & DISABLE_I18N)
3246 i18n = osrfHashGet(field_def, "i18n");
3248 if( str_is_true( i18n ) ) {
3249 buffer_fadd( select_buf,
3250 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3251 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3253 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3256 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3259 // ... but it could be an object, in which case we check for a Field Transform
3260 } else if (selfield->type == JSON_HASH) {
3262 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3264 // Get the field definition from the IDL
3265 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3267 // No such field in current class
3270 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3276 osrfAppSessionStatus(
3278 OSRF_STATUS_INTERNALSERVERERROR,
3279 "osrfMethodException",
3281 "Selected column is not defined in JSON query"
3283 jsonIteratorFree( selclass_itr );
3284 buffer_free( select_buf );
3285 buffer_free( group_buf );
3286 if( defaultselhash ) jsonObjectFree( defaultselhash );
3287 free( join_clause );
3289 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3290 // No such field in current class
3293 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3299 osrfAppSessionStatus(
3301 OSRF_STATUS_INTERNALSERVERERROR,
3302 "osrfMethodException",
3304 "Selected column is virtual in JSON query"
3306 jsonIteratorFree( selclass_itr );
3307 buffer_free( select_buf );
3308 buffer_free( group_buf );
3309 if( defaultselhash ) jsonObjectFree( defaultselhash );
3310 free( join_clause );
3314 // Decide what to use as a column alias
3316 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3317 _alias = jsonObjectGetString( tmp_const );
3318 } else { // Use field name as the alias
3322 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3323 char* transform_str = searchFieldTransform(class_info->alias, field_def, selfield);
3324 if( transform_str ) {
3325 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3326 free(transform_str);
3329 osrfAppSessionStatus(
3331 OSRF_STATUS_INTERNALSERVERERROR,
3332 "osrfMethodException",
3334 "Unable to generate transform function in JSON query"
3336 jsonIteratorFree( selclass_itr );
3337 buffer_free( select_buf );
3338 buffer_free( group_buf );
3339 if( defaultselhash ) jsonObjectFree( defaultselhash );
3340 free( join_clause );
3347 if (flags & DISABLE_I18N)
3350 i18n = osrfHashGet(field_def, "i18n");
3352 if( str_is_true( i18n ) ) {
3353 buffer_fadd( select_buf,
3354 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3355 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3357 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3360 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3367 "%s: Selected item is unexpected JSON type: %s",
3369 json_type( selfield->type )
3372 osrfAppSessionStatus(
3374 OSRF_STATUS_INTERNALSERVERERROR,
3375 "osrfMethodException",
3377 "Ill-formed SELECT item in JSON query"
3379 jsonIteratorFree( selclass_itr );
3380 buffer_free( select_buf );
3381 buffer_free( group_buf );
3382 if( defaultselhash ) jsonObjectFree( defaultselhash );
3383 free( join_clause );
3387 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3388 if( obj_is_true( agg_obj ) )
3389 aggregate_found = 1;
3391 // Append a comma (except for the first one)
3392 // and add the column to a GROUP BY clause
3396 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3398 buffer_fadd(group_buf, " %d", sel_pos);
3402 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3404 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3405 if ( ! obj_is_true( aggregate_obj ) ) {
3409 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3412 buffer_fadd(group_buf, " %d", sel_pos);
3415 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3419 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3422 _column = searchFieldTransform(class_info->alias, field, selfield);
3423 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3424 OSRF_BUFFER_ADD(group_buf, _column);
3425 _column = searchFieldTransform(class_info->alias, field, selfield);
3432 } // end while -- iterating across SELECT columns
3434 } // end while -- iterating across classes
3436 jsonIteratorFree(selclass_itr);
3440 char* col_list = buffer_release(select_buf);
3442 // Make sure the SELECT list isn't empty. This can happen, for example,
3443 // if we try to build a default SELECT clause from a non-core table.
3446 osrfLogError(OSRF_LOG_MARK, "%s: SELECT clause is empty", MODULENAME );
3448 osrfAppSessionStatus(
3450 OSRF_STATUS_INTERNALSERVERERROR,
3451 "osrfMethodException",
3453 "SELECT list is empty"
3456 buffer_free( group_buf );
3457 if( defaultselhash ) jsonObjectFree( defaultselhash );
3458 free( join_clause );
3463 if (from_function) table = searchValueTransform(join_hash);
3464 else table = strdup( curr_query->core.source_def );
3468 osrfAppSessionStatus(
3470 OSRF_STATUS_INTERNALSERVERERROR,
3471 "osrfMethodException",
3473 "Unable to identify table for core class"
3476 buffer_free( group_buf );
3477 if( defaultselhash ) jsonObjectFree( defaultselhash );
3478 free( join_clause );
3482 // Put it all together
3483 growing_buffer* sql_buf = buffer_init(128);
3484 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3488 // Append the join clause, if any
3490 buffer_add(sql_buf, join_clause);
3494 char* order_by_list = NULL;
3495 char* having_buf = NULL;
3497 if (!from_function) {
3499 // Build a WHERE clause, if there is one
3500 if ( search_hash ) {
3501 buffer_add(sql_buf, " WHERE ");
3503 // and it's on the WHERE clause
3504 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
3507 osrfAppSessionStatus(
3509 OSRF_STATUS_INTERNALSERVERERROR,
3510 "osrfMethodException",
3512 "Severe query error in WHERE predicate -- see error log for more details"
3515 buffer_free(group_buf);
3516 buffer_free(sql_buf);
3517 if (defaultselhash) jsonObjectFree(defaultselhash);
3521 buffer_add(sql_buf, pred);
3525 // Build a HAVING clause, if there is one
3526 if ( having_hash ) {
3528 // and it's on the the WHERE clause
3529 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
3531 if( ! having_buf ) {
3533 osrfAppSessionStatus(
3535 OSRF_STATUS_INTERNALSERVERERROR,
3536 "osrfMethodException",
3538 "Severe query error in HAVING predicate -- see error log for more details"
3541 buffer_free(group_buf);
3542 buffer_free(sql_buf);
3543 if (defaultselhash) jsonObjectFree(defaultselhash);
3548 growing_buffer* order_buf = NULL; // to collect ORDER BY list
3550 // Build an ORDER BY clause, if there is one
3551 if( NULL == order_hash )
3552 ; // No ORDER BY? do nothing
3553 else if( JSON_ARRAY == order_hash->type ) {
3554 // Array of field specifications, each specification being a
3555 // hash to define the class, field, and other details
3557 jsonObject* order_spec;
3558 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
3560 if( JSON_HASH != order_spec->type ) {
3561 osrfLogError(OSRF_LOG_MARK,
3562 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
3563 MODULENAME, json_type( order_spec->type ) );
3565 osrfAppSessionStatus(
3567 OSRF_STATUS_INTERNALSERVERERROR,
3568 "osrfMethodException",
3570 "Malformed ORDER BY clause -- see error log for more details"
3572 buffer_free( order_buf );
3574 buffer_free(group_buf);
3575 buffer_free(sql_buf);
3576 if (defaultselhash) jsonObjectFree(defaultselhash);
3580 const char* class_alias =
3581 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
3583 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
3586 OSRF_BUFFER_ADD(order_buf, ", ");
3588 order_buf = buffer_init(128);
3590 if( !field || !class_alias ) {
3591 osrfLogError(OSRF_LOG_MARK,
3592 "%s: Missing class or field name in field specification of ORDER BY clause",
3595 osrfAppSessionStatus(
3597 OSRF_STATUS_INTERNALSERVERERROR,
3598 "osrfMethodException",
3600 "Malformed ORDER BY clause -- see error log for more details"
3602 buffer_free( order_buf );
3604 buffer_free(group_buf);
3605 buffer_free(sql_buf);
3606 if (defaultselhash) jsonObjectFree(defaultselhash);
3610 ClassInfo* order_class_info = search_alias( class_alias );
3611 if( ! order_class_info ) {
3612 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
3613 "not in FROM clause", MODULENAME, class_alias );
3615 osrfAppSessionStatus(
3617 OSRF_STATUS_INTERNALSERVERERROR,
3618 "osrfMethodException",
3620 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3623 buffer_free(group_buf);
3624 buffer_free(sql_buf);
3625 if (defaultselhash) jsonObjectFree(defaultselhash);
3629 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
3631 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
3632 MODULENAME, class_alias, field );
3634 osrfAppSessionStatus(
3636 OSRF_STATUS_INTERNALSERVERERROR,
3637 "osrfMethodException",
3639 "Invalid field referenced in ORDER BY clause -- see error log for more details"
3642 buffer_free(group_buf);
3643 buffer_free(sql_buf);
3644 if (defaultselhash) jsonObjectFree(defaultselhash);
3646 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3647 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3648 MODULENAME, field );
3650 osrfAppSessionStatus(
3652 OSRF_STATUS_INTERNALSERVERERROR,
3653 "osrfMethodException",
3655 "Virtual field in ORDER BY clause -- see error log for more details"
3657 buffer_free( order_buf );
3659 buffer_free(group_buf);
3660 buffer_free(sql_buf);
3661 if (defaultselhash) jsonObjectFree(defaultselhash);
3665 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
3666 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
3667 if( ! transform_str ) {
3669 osrfAppSessionStatus(
3671 OSRF_STATUS_INTERNALSERVERERROR,
3672 "osrfMethodException",
3674 "Severe query error in ORDER BY clause -- see error log for more details"
3676 buffer_free( order_buf );
3678 buffer_free(group_buf);
3679 buffer_free(sql_buf);
3680 if (defaultselhash) jsonObjectFree(defaultselhash);
3684 OSRF_BUFFER_ADD( order_buf, transform_str );
3685 free( transform_str );
3688 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
3690 const char* direction =
3691 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
3693 if( direction[ 0 ] || 'D' == direction[ 0 ] )
3694 OSRF_BUFFER_ADD( order_buf, " DESC" );
3696 OSRF_BUFFER_ADD( order_buf, " ASC" );
3699 } else if( JSON_HASH == order_hash->type ) {
3700 // This hash is keyed on class alias. Each class has either
3701 // an array of field names or a hash keyed on field name.
3702 jsonIterator* class_itr = jsonNewIterator( order_hash );
3703 while ( (snode = jsonIteratorNext( class_itr )) ) {
3705 ClassInfo* order_class_info = search_alias( class_itr->key );
3706 if( ! order_class_info ) {
3707 osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
3708 MODULENAME, class_itr->key );
3710 osrfAppSessionStatus(
3712 OSRF_STATUS_INTERNALSERVERERROR,
3713 "osrfMethodException",
3715 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3717 jsonIteratorFree( class_itr );
3718 buffer_free( order_buf );
3720 buffer_free(group_buf);
3721 buffer_free(sql_buf);
3722 if (defaultselhash) jsonObjectFree(defaultselhash);
3726 osrfHash* field_list_def = order_class_info->fields;
3728 if ( snode->type == JSON_HASH ) {
3730 // Hash is keyed on field names from the current class. For each field
3731 // there is another layer of hash to define the sorting details, if any,
3732 // or a string to indicate direction of sorting.
3733 jsonIterator* order_itr = jsonNewIterator( snode );
3734 while ( (onode = jsonIteratorNext( order_itr )) ) {
3736 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
3738 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3739 MODULENAME, order_itr->key );
3741 osrfAppSessionStatus(
3743 OSRF_STATUS_INTERNALSERVERERROR,
3744 "osrfMethodException",
3746 "Invalid field in ORDER BY clause -- see error log for more details"
3748 jsonIteratorFree( order_itr );
3749 jsonIteratorFree( class_itr );
3750 buffer_free( order_buf );
3752 buffer_free(group_buf);
3753 buffer_free(sql_buf);
3754 if (defaultselhash) jsonObjectFree(defaultselhash);
3756 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3757 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3758 MODULENAME, order_itr->key );
3760 osrfAppSessionStatus(
3762 OSRF_STATUS_INTERNALSERVERERROR,
3763 "osrfMethodException",
3765 "Virtual field in ORDER BY clause -- see error log for more details"
3767 jsonIteratorFree( order_itr );
3768 jsonIteratorFree( class_itr );
3769 buffer_free( order_buf );
3771 buffer_free(group_buf);
3772 buffer_free(sql_buf);
3773 if (defaultselhash) jsonObjectFree(defaultselhash);
3777 const char* direction = NULL;
3778 if ( onode->type == JSON_HASH ) {
3779 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3780 string = searchFieldTransform(
3782 osrfHashGet( field_list_def, order_itr->key ),
3786 if( ctx ) osrfAppSessionStatus(
3788 OSRF_STATUS_INTERNALSERVERERROR,
3789 "osrfMethodException",
3791 "Severe query error in ORDER BY clause -- see error log for more details"
3793 jsonIteratorFree( order_itr );
3794 jsonIteratorFree( class_itr );
3796 buffer_free(group_buf);
3797 buffer_free(order_buf);
3798 buffer_free(sql_buf);
3799 if (defaultselhash) jsonObjectFree(defaultselhash);
3803 growing_buffer* field_buf = buffer_init(16);
3804 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3805 string = buffer_release(field_buf);
3808 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3809 const char* dir = jsonObjectGetString(tmp_const);
3810 if (!strncasecmp(dir, "d", 1)) {
3811 direction = " DESC";
3817 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
3818 osrfLogError( OSRF_LOG_MARK,
3819 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
3820 MODULENAME, json_type( onode->type ) );
3822 osrfAppSessionStatus(
3824 OSRF_STATUS_INTERNALSERVERERROR,
3825 "osrfMethodException",
3827 "Malformed ORDER BY clause -- see error log for more details"
3829 jsonIteratorFree( order_itr );
3830 jsonIteratorFree( class_itr );
3832 buffer_free(group_buf);
3833 buffer_free(order_buf);
3834 buffer_free(sql_buf);
3835 if (defaultselhash) jsonObjectFree(defaultselhash);
3839 string = strdup(order_itr->key);
3840 const char* dir = jsonObjectGetString(onode);
3841 if (!strncasecmp(dir, "d", 1)) {
3842 direction = " DESC";
3849 OSRF_BUFFER_ADD(order_buf, ", ");
3851 order_buf = buffer_init(128);
3853 OSRF_BUFFER_ADD(order_buf, string);
3857 OSRF_BUFFER_ADD(order_buf, direction);
3861 jsonIteratorFree(order_itr);
3863 } else if ( snode->type == JSON_ARRAY ) {
3865 // Array is a list of fields from the current class
3866 unsigned long order_idx = 0;
3867 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
3869 const char* _f = jsonObjectGetString( onode );
3871 osrfHash* field_def = osrfHashGet( field_list_def, _f );
3873 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3876 osrfAppSessionStatus(
3878 OSRF_STATUS_INTERNALSERVERERROR,
3879 "osrfMethodException",
3881 "Invalid 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);
3890 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3891 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3894 osrfAppSessionStatus(
3896 OSRF_STATUS_INTERNALSERVERERROR,
3897 "osrfMethodException",
3899 "Virtual field in ORDER BY clause -- see error log for more details"
3901 jsonIteratorFree( class_itr );
3902 buffer_free( order_buf );
3904 buffer_free(group_buf);
3905 buffer_free(sql_buf);
3906 if (defaultselhash) jsonObjectFree(defaultselhash);
3911 OSRF_BUFFER_ADD(order_buf, ", ");
3913 order_buf = buffer_init(128);
3915 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
3919 // IT'S THE OOOOOOOOOOOLD STYLE!
3921 osrfLogError(OSRF_LOG_MARK,
3922 "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3924 osrfAppSessionStatus(
3926 OSRF_STATUS_INTERNALSERVERERROR,
3927 "osrfMethodException",
3929 "Severe query error -- see error log for more details"
3934 buffer_free(group_buf);
3935 buffer_free(order_buf);
3936 buffer_free(sql_buf);
3937 if (defaultselhash) jsonObjectFree(defaultselhash);
3938 jsonIteratorFree(class_itr);
3942 jsonIteratorFree( class_itr );
3944 osrfLogError(OSRF_LOG_MARK,
3945 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
3946 MODULENAME, json_type( order_hash->type ) );
3948 osrfAppSessionStatus(
3950 OSRF_STATUS_INTERNALSERVERERROR,
3951 "osrfMethodException",
3953 "Malformed ORDER BY clause -- see error log for more details"
3955 buffer_free( order_buf );
3957 buffer_free(group_buf);
3958 buffer_free(sql_buf);
3959 if (defaultselhash) jsonObjectFree(defaultselhash);
3964 order_by_list = buffer_release( order_buf );
3968 string = buffer_release(group_buf);
3970 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
3971 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3972 OSRF_BUFFER_ADD( sql_buf, string );
3977 if( having_buf && *having_buf ) {
3978 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3979 OSRF_BUFFER_ADD( sql_buf, having_buf );
3983 if( order_by_list ) {
3985 if ( *order_by_list ) {
3986 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3987 OSRF_BUFFER_ADD( sql_buf, order_by_list );
3990 free( order_by_list );
3994 const char* str = jsonObjectGetString(limit);
3995 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
3999 const char* str = jsonObjectGetString(offset);
4000 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
4003 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4005 if (defaultselhash) jsonObjectFree(defaultselhash);
4007 return buffer_release(sql_buf);
4009 } // end of SELECT()
4011 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4013 const char* locale = osrf_message_get_last_locale();
4015 osrfHash* fields = osrfHashGet(meta, "fields");
4016 char* core_class = osrfHashGet(meta, "classname");
4018 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4020 jsonObject* node = NULL;
4021 jsonObject* snode = NULL;
4022 jsonObject* onode = NULL;
4023 const jsonObject* _tmp = NULL;
4024 jsonObject* selhash = NULL;
4025 jsonObject* defaultselhash = NULL;
4027 growing_buffer* sql_buf = buffer_init(128);
4028 growing_buffer* select_buf = buffer_init(128);
4030 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4031 defaultselhash = jsonNewObjectType(JSON_HASH);
4032 selhash = defaultselhash;
4035 // If there's no SELECT list for the core class, build one
4036 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
4037 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4039 // Add every non-virtual field to the field list
4040 osrfHash* field_def = NULL;
4041 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4042 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4043 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4044 const char* field = osrfHashIteratorKey( field_itr );
4045 jsonObjectPush( field_list, jsonNewObject( field ) );
4048 osrfHashIteratorFree( field_itr );
4049 jsonObjectSetKey( selhash, core_class, field_list );
4053 jsonIterator* class_itr = jsonNewIterator( selhash );
4054 while ( (snode = jsonIteratorNext( class_itr )) ) {
4056 const char* cname = class_itr->key;
4057 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4058 if (!idlClass) continue;
4060 if (strcmp(core_class,class_itr->key)) {
4061 if (!join_hash) continue;
4063 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
4065 jsonObjectFree(found);
4069 jsonObjectFree(found);
4072 jsonIterator* select_itr = jsonNewIterator( snode );
4073 while ( (node = jsonIteratorNext( select_itr )) ) {
4074 const char* item_str = jsonObjectGetString( node );
4075 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4076 char* fname = osrfHashGet(field, "name");
4078 if (!field) continue;
4083 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
4088 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4089 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4092 i18n = osrfHashGet(field, "i18n");
4094 if( str_is_true( i18n ) ) {
4095 char* pkey = osrfHashGet(idlClass, "primarykey");
4096 char* tname = osrfHashGet(idlClass, "tablename");
4098 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);
4100 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4103 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4107 jsonIteratorFree(select_itr);
4110 jsonIteratorFree(class_itr);
4112 char* col_list = buffer_release(select_buf);
4113 char* table = getSourceDefinition(meta);
4115 table = strdup( "(null)" );
4117 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4121 // Clear the query stack (as a fail-safe precaution against possible
4122 // leftover garbage); then push the first query frame onto the stack.
4123 clear_query_stack();
4125 if( add_query_core( NULL, core_class ) ) {
4127 osrfAppSessionStatus(
4129 OSRF_STATUS_INTERNALSERVERERROR,
4130 "osrfMethodException",
4132 "Unable to build query frame for core class"
4138 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4139 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
4140 OSRF_BUFFER_ADD(sql_buf, join_clause);
4144 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4145 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
4147 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
4149 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4151 osrfAppSessionStatus(
4153 OSRF_STATUS_INTERNALSERVERERROR,
4154 "osrfMethodException",
4156 "Severe query error -- see error log for more details"
4158 buffer_free(sql_buf);
4159 if(defaultselhash) jsonObjectFree(defaultselhash);
4160 clear_query_stack();
4163 buffer_add(sql_buf, pred);
4168 char* string = NULL;
4169 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4171 growing_buffer* order_buf = buffer_init(128);
4174 jsonIterator* class_itr = jsonNewIterator( _tmp );
4175 while ( (snode = jsonIteratorNext( class_itr )) ) {
4177 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4180 if ( snode->type == JSON_HASH ) {
4182 jsonIterator* order_itr = jsonNewIterator( snode );
4183 while ( (onode = jsonIteratorNext( order_itr )) ) {
4185 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4186 class_itr->key, order_itr->key );
4190 char* direction = NULL;
4191 if ( onode->type == JSON_HASH ) {
4192 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4193 string = searchFieldTransform( class_itr->key, field_def, onode );
4195 osrfAppSessionStatus(
4197 OSRF_STATUS_INTERNALSERVERERROR,
4198 "osrfMethodException",
4200 "Severe query error in ORDER BY clause -- see error log for more details"
4202 jsonIteratorFree( order_itr );
4203 jsonIteratorFree( class_itr );
4204 buffer_free( order_buf );
4205 buffer_free( sql_buf );
4206 if( defaultselhash ) jsonObjectFree( defaultselhash );
4207 clear_query_stack();
4211 growing_buffer* field_buf = buffer_init(16);
4212 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4213 string = buffer_release(field_buf);
4216 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4217 const char* dir = jsonObjectGetString(_tmp);
4218 if (!strncasecmp(dir, "d", 1)) {
4219 direction = " DESC";
4226 string = strdup(order_itr->key);
4227 const char* dir = jsonObjectGetString(onode);
4228 if (!strncasecmp(dir, "d", 1)) {
4229 direction = " DESC";
4238 buffer_add(order_buf, ", ");
4241 buffer_add(order_buf, string);
4245 buffer_add(order_buf, direction);
4250 jsonIteratorFree(order_itr);
4253 const char* str = jsonObjectGetString(snode);
4254 buffer_add(order_buf, str);
4260 jsonIteratorFree(class_itr);
4262 string = buffer_release(order_buf);
4265 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4266 OSRF_BUFFER_ADD( sql_buf, string );
4272 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4273 const char* str = jsonObjectGetString(_tmp);
4281 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4283 const char* str = jsonObjectGetString(_tmp);
4292 if (defaultselhash) jsonObjectFree(defaultselhash);
4293 clear_query_stack();
4295 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4296 return buffer_release(sql_buf);
4299 int doJSONSearch ( osrfMethodContext* ctx ) {
4300 if(osrfMethodVerifyContext( ctx )) {
4301 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4305 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
4310 dbhandle = writehandle;
4312 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4316 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4317 flags |= SELECT_DISTINCT;
4319 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4320 flags |= DISABLE_I18N;
4322 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4325 jsonObjectGetKey( hash, "select" ),
4326 jsonObjectGetKey( hash, "from" ),
4327 jsonObjectGetKey( hash, "where" ),
4328 jsonObjectGetKey( hash, "having" ),
4329 jsonObjectGetKey( hash, "order_by" ),
4330 jsonObjectGetKey( hash, "limit" ),
4331 jsonObjectGetKey( hash, "offset" ),
4334 clear_query_stack();
4341 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4342 dbi_result result = dbi_conn_query(dbhandle, sql);
4345 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4347 if (dbi_result_first_row(result)) {
4348 /* JSONify the result */
4349 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4352 jsonObject* return_val = oilsMakeJSONFromResult( result );
4353 osrfAppRespond( ctx, return_val );
4354 jsonObjectFree( return_val );
4355 } while (dbi_result_next_row(result));
4358 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4361 osrfAppRespondComplete( ctx, NULL );
4363 /* clean up the query */
4364 dbi_result_free(result);
4368 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4369 osrfAppSessionStatus(
4371 OSRF_STATUS_INTERNALSERVERERROR,
4372 "osrfMethodException",
4374 "Severe query error -- see error log for more details"
4382 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4383 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4386 dbhandle = writehandle;
4388 osrfHash* links = osrfHashGet(meta, "links");
4389 osrfHash* fields = osrfHashGet(meta, "fields");
4390 char* core_class = osrfHashGet(meta, "classname");
4391 char* pkey = osrfHashGet(meta, "primarykey");
4393 const jsonObject* _tmp;
4396 char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4398 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4403 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4405 dbi_result result = dbi_conn_query(dbhandle, sql);
4406 if( NULL == result ) {
4407 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4408 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4409 osrfAppSessionStatus(
4411 OSRF_STATUS_INTERNALSERVERERROR,
4412 "osrfMethodException",
4414 "Severe query error -- see error log for more details"
4421 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4424 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
4425 osrfHash* dedup = osrfNewHash();
4427 if (dbi_result_first_row(result)) {
4428 /* JSONify the result */
4429 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4431 obj = oilsMakeFieldmapperFromResult( result, meta );
4432 char* pkey_val = oilsFMGetString( obj, pkey );
4433 if ( osrfHashGet( dedup, pkey_val ) ) {
4434 jsonObjectFree(obj);
4437 osrfHashSet( dedup, pkey_val, pkey_val );
4438 jsonObjectPush(res_list, obj);
4440 } while (dbi_result_next_row(result));
4442 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
4446 osrfHashFree(dedup);
4447 /* clean up the query */
4448 dbi_result_free(result);
4451 if (res_list->size && query_hash) {
4452 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
4454 int x = (int)jsonObjectGetNumber(_tmp);
4455 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
4457 const jsonObject* temp_blob;
4458 if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
4460 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
4461 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
4463 osrfStringArray* link_fields = NULL;
4466 if (flesh_fields->size == 1) {
4467 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
4468 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
4473 link_fields = osrfNewStringArray(1);
4474 jsonIterator* _i = jsonNewIterator( flesh_fields );
4475 while ((_f = jsonIteratorNext( _i ))) {
4476 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4478 jsonIteratorFree(_i);
4483 unsigned long res_idx = 0;
4484 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
4489 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4491 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4493 osrfHash* kid_link = osrfHashGet(links, link_field);
4494 if (!kid_link) continue;
4496 osrfHash* field = osrfHashGet(fields, link_field);
4497 if (!field) continue;
4499 osrfHash* value_field = field;
4501 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4502 if (!kid_idl) continue;
4504 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4505 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4508 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4509 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4512 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4514 if (link_map->size > 0) {
4515 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4518 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4523 osrfHashGet(kid_link, "class"),
4530 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4531 osrfHashGet(kid_link, "field"),
4532 osrfHashGet(kid_link, "class"),
4533 osrfHashGet(kid_link, "key"),
4534 osrfHashGet(kid_link, "reltype")
4537 const char* search_key = jsonObjectGetString(
4540 atoi( osrfHashGet(value_field, "array_position") )
4545 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4549 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
4551 // construct WHERE clause
4552 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
4555 osrfHashGet(kid_link, "key"),
4556 jsonNewObject( search_key )
4559 // construct the rest of the query
4560 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
4561 jsonObjectSetKey( rest_of_query, "flesh",
4562 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4566 jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
4568 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
4569 jsonObjectSetKey( rest_of_query, "order_by",
4570 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
4574 if (jsonObjectGetKeyConst(query_hash, "select")) {
4575 jsonObjectSetKey( rest_of_query, "select",
4576 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
4580 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
4581 where_clause, rest_of_query, err);
4583 jsonObjectFree( where_clause );
4584 jsonObjectFree( rest_of_query );
4587 osrfStringArrayFree(link_fields);
4588 jsonObjectFree(res_list);
4589 jsonObjectFree(flesh_blob);
4593 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4595 jsonObject* X = NULL;
4596 if ( link_map->size > 0 && kids->size > 0 ) {
4598 kids = jsonNewObjectType(JSON_ARRAY);
4600 jsonObject* _k_node;
4601 unsigned long res_idx = 0;
4602 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
4608 (unsigned long)atoi(
4614 osrfHashGet(kid_link, "class")
4618 osrfStringArrayGetString( link_map, 0 )
4626 } // end while loop traversing X
4629 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4630 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4633 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4634 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4638 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4639 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4642 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4643 jsonObjectClone( kids )
4648 jsonObjectFree(kids);
4652 jsonObjectFree( kids );
4654 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4655 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4658 } // end while loop traversing res_list
4659 jsonObjectFree( flesh_blob );
4660 osrfStringArrayFree(link_fields);
4669 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
4671 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4673 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
4675 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
4678 if (!verifyObjectClass(ctx, target)) {
4683 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4684 osrfAppSessionStatus(
4686 OSRF_STATUS_BADREQUEST,
4687 "osrfMethodException",
4689 "No active transaction -- required for UPDATE"
4695 // The following test is harmless but redundant. If a class is
4696 // readonly, we don't register an update method for it.
4697 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4698 osrfAppSessionStatus(
4700 OSRF_STATUS_BADREQUEST,
4701 "osrfMethodException",
4703 "Cannot UPDATE readonly class"
4709 dbhandle = writehandle;
4711 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4713 // Set the last_xact_id
4714 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4716 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
4717 trans_id, target->classname, index);
4718 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4721 char* pkey = osrfHashGet(meta, "primarykey");
4722 osrfHash* fields = osrfHashGet(meta, "fields");
4724 char* id = oilsFMGetString( target, pkey );
4728 "%s updating %s object with %s = %s",
4730 osrfHashGet(meta, "fieldmapper"),
4735 growing_buffer* sql = buffer_init(128);
4736 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4739 osrfHash* field_def = NULL;
4740 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4741 while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
4743 // Skip virtual fields, and the primary key
4744 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
4747 const char* field_name = osrfHashIteratorKey( field_itr );
4748 if( ! strcmp( field_name, pkey ) )
4751 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4753 int value_is_numeric = 0; // boolean
4755 if (field_object && field_object->classname) {
4756 value = oilsFMGetString(
4758 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4761 value = jsonObjectToSimpleString( field_object );
4762 if( field_object && JSON_NUMBER == field_object->type )
4763 value_is_numeric = 1;
4766 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
4767 osrfHashGet(meta, "fieldmapper"), field_name, value);
4769 if (!field_object || field_object->type == JSON_NULL) {
4770 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
4771 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
4772 if (first) first = 0;
4773 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4774 buffer_fadd( sql, " %s = NULL", field_name );
4777 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
4778 if (first) first = 0;
4779 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4781 const char* numtype = get_datatype( field_def );
4782 if ( !strncmp( numtype, "INT", 3 ) ) {
4783 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
4784 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
4785 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
4787 // Must really be intended as a string, so quote it
4788 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4789 buffer_fadd( sql, " %s = %s", field_name, value );
4791 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4792 osrfAppSessionStatus(
4794 OSRF_STATUS_INTERNALSERVERERROR,
4795 "osrfMethodException",
4797 "Error quoting string -- please see the error log for more details"
4801 osrfHashIteratorFree( field_itr );
4808 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
4811 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4812 if (first) first = 0;
4813 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4814 buffer_fadd( sql, " %s = %s", field_name, value );
4817 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4818 osrfAppSessionStatus(
4820 OSRF_STATUS_INTERNALSERVERERROR,
4821 "osrfMethodException",
4823 "Error quoting string -- please see the error log for more details"
4827 osrfHashIteratorFree( field_itr );
4838 osrfHashIteratorFree( field_itr );
4840 jsonObject* obj = jsonNewObject(id);
4842 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4843 dbi_conn_quote_string(dbhandle, &id);
4845 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
4847 char* query = buffer_release(sql);
4848 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
4850 dbi_result result = dbi_conn_query(dbhandle, query);
4854 jsonObjectFree(obj);
4855 obj = jsonNewObject(NULL);
4858 "%s ERROR updating %s object with %s = %s",
4860 osrfHashGet(meta, "fieldmapper"),
4871 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4873 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4875 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4876 osrfAppSessionStatus(
4878 OSRF_STATUS_BADREQUEST,
4879 "osrfMethodException",
4881 "No active transaction -- required for DELETE"
4887 // The following test is harmless but redundant. If a class is
4888 // readonly, we don't register a delete method for it.
4889 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4890 osrfAppSessionStatus(
4892 OSRF_STATUS_BADREQUEST,
4893 "osrfMethodException",
4895 "Cannot DELETE readonly class"
4901 dbhandle = writehandle;
4905 char* pkey = osrfHashGet(meta, "primarykey");
4913 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4914 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4919 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4922 if (!verifyObjectPCRUD( ctx, NULL )) {
4927 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4932 "%s deleting %s object with %s = %s",
4934 osrfHashGet(meta, "fieldmapper"),
4939 obj = jsonNewObject(id);
4941 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4942 dbi_conn_quote_string(writehandle, &id);
4944 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4947 jsonObjectFree(obj);
4948 obj = jsonNewObject(NULL);
4951 "%s ERROR deleting %s object with %s = %s",
4953 osrfHashGet(meta, "fieldmapper"),
4966 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4967 if(!(result && meta)) return jsonNULL;
4969 jsonObject* object = jsonNewObject(NULL);
4970 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4972 osrfHash* fields = osrfHashGet(meta, "fields");
4974 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4978 char dt_string[256];
4982 int columnIndex = 1;
4984 unsigned short type;
4985 const char* columnName;
4987 /* cycle through the column list */
4988 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
4990 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4992 fmIndex = -1; // reset the position
4994 /* determine the field type and storage attributes */
4995 type = dbi_result_get_field_type_idx(result, columnIndex);
4996 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
4998 /* fetch the fieldmapper index */
4999 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
5001 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
5004 const char* pos = (char*)osrfHashGet(_f, "array_position");
5005 if ( !pos ) continue;
5007 fmIndex = atoi( pos );
5008 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
5013 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5014 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
5019 case DBI_TYPE_INTEGER :
5021 if( attr & DBI_INTEGER_SIZE8 )
5022 jsonObjectSetIndex( object, fmIndex,
5023 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
5025 jsonObjectSetIndex( object, fmIndex,
5026 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
5030 case DBI_TYPE_DECIMAL :
5031 jsonObjectSetIndex( object, fmIndex,
5032 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
5035 case DBI_TYPE_STRING :
5041 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
5046 case DBI_TYPE_DATETIME :
5048 memset(dt_string, '\0', sizeof(dt_string));
5049 memset(&gmdt, '\0', sizeof(gmdt));
5051 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5054 if (!(attr & DBI_DATETIME_DATE)) {
5055 gmtime_r( &_tmp_dt, &gmdt );
5056 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5057 } else if (!(attr & DBI_DATETIME_TIME)) {
5058 localtime_r( &_tmp_dt, &gmdt );
5059 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5061 localtime_r( &_tmp_dt, &gmdt );
5062 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5065 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
5069 case DBI_TYPE_BINARY :
5070 osrfLogError( OSRF_LOG_MARK,
5071 "Can't do binary at column %s : index %d", columnName, columnIndex);
5080 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5081 if(!result) return jsonNULL;
5083 jsonObject* object = jsonNewObject(NULL);
5086 char dt_string[256];
5090 int columnIndex = 1;
5092 unsigned short type;
5093 const char* columnName;
5095 /* cycle through the column list */
5096 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5098 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5100 fmIndex = -1; // reset the position
5102 /* determine the field type and storage attributes */
5103 type = dbi_result_get_field_type_idx(result, columnIndex);
5104 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5106 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5107 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
5112 case DBI_TYPE_INTEGER :
5114 if( attr & DBI_INTEGER_SIZE8 )
5115 jsonObjectSetKey( object, columnName,
5116 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)) );
5118 jsonObjectSetKey( object, columnName,
5119 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
5122 case DBI_TYPE_DECIMAL :
5123 jsonObjectSetKey( object, columnName,
5124 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
5127 case DBI_TYPE_STRING :
5128 jsonObjectSetKey( object, columnName,
5129 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
5132 case DBI_TYPE_DATETIME :
5134 memset(dt_string, '\0', sizeof(dt_string));
5135 memset(&gmdt, '\0', sizeof(gmdt));
5137 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5140 if (!(attr & DBI_DATETIME_DATE)) {
5141 gmtime_r( &_tmp_dt, &gmdt );
5142 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5143 } else if (!(attr & DBI_DATETIME_TIME)) {
5144 localtime_r( &_tmp_dt, &gmdt );
5145 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5147 localtime_r( &_tmp_dt, &gmdt );
5148 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5151 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
5154 case DBI_TYPE_BINARY :
5155 osrfLogError( OSRF_LOG_MARK,
5156 "Can't do binary at column %s : index %d", columnName, columnIndex );
5160 } // end while loop traversing result
5165 // Interpret a string as true or false
5166 static int str_is_true( const char* str ) {
5167 if( NULL == str || strcasecmp( str, "true" ) )
5173 // Interpret a jsonObject as true or false
5174 static int obj_is_true( const jsonObject* obj ) {
5177 else switch( obj->type )
5185 if( strcasecmp( obj->value.s, "true" ) )
5189 case JSON_NUMBER : // Support 1/0 for perl's sake
5190 if( jsonObjectGetNumber( obj ) == 1.0 )
5199 // Translate a numeric code into a text string identifying a type of
5200 // jsonObject. To be used for building error messages.
5201 static const char* json_type( int code ) {
5207 return "JSON_ARRAY";
5209 return "JSON_STRING";
5211 return "JSON_NUMBER";
5217 return "(unrecognized)";
5221 // Extract the "primitive" attribute from an IDL field definition.
5222 // If we haven't initialized the app, then we must be running in
5223 // some kind of testbed. In that case, default to "string".
5224 static const char* get_primitive( osrfHash* field ) {
5225 const char* s = osrfHashGet( field, "primitive" );
5227 if( child_initialized )
5230 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5232 osrfHashGet( field, "name" )
5240 // Extract the "datatype" attribute from an IDL field definition.
5241 // If we haven't initialized the app, then we must be running in
5242 // some kind of testbed. In that case, default to to NUMERIC,
5243 // since we look at the datatype only for numbers.
5244 static const char* get_datatype( osrfHash* field ) {
5245 const char* s = osrfHashGet( field, "datatype" );
5247 if( child_initialized )
5250 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5252 osrfHashGet( field, "name" )
5261 If the input string is potentially a valid SQL identifier, return 1.
5264 Purpose: to prevent certain kinds of SQL injection. To that end we
5265 don't necessarily need to follow all the rules exactly, such as requiring
5266 that the first character not be a digit.
5268 We allow leading and trailing white space. In between, we do not allow
5269 punctuation (except for underscores and dollar signs), control
5270 characters, or embedded white space.
5272 More pedantically we should allow quoted identifiers containing arbitrary
5273 characters, but for the foreseeable future such quoted identifiers are not
5274 likely to be an issue.
5276 static int is_identifier( const char* s) {
5280 // Skip leading white space
5281 while( isspace( (unsigned char) *s ) )
5285 return 0; // Nothing but white space? Not okay.
5287 // Check each character until we reach white space or
5288 // end-of-string. Letters, digits, underscores, and
5289 // dollar signs are okay. With the exception of periods
5290 // (as in schema.identifier), control characters and other
5291 // punctuation characters are not okay. Anything else
5292 // is okay -- it could for example be part of a multibyte
5293 // UTF8 character such as a letter with diacritical marks,
5294 // and those are allowed.
5296 if( isalnum( (unsigned char) *s )
5300 ; // Fine; keep going
5301 else if( ispunct( (unsigned char) *s )
5302 || iscntrl( (unsigned char) *s ) )
5305 } while( *s && ! isspace( (unsigned char) *s ) );
5307 // If we found any white space in the above loop,
5308 // the rest had better be all white space.
5310 while( isspace( (unsigned char) *s ) )
5314 return 0; // White space was embedded within non-white space
5320 Determine whether to accept a character string as a comparison operator.
5321 Return 1 if it's good, or 0 if it's bad.
5323 We don't validate it for real. We just make sure that it doesn't contain
5324 any semicolons or white space (with special exceptions for a few specific
5325 operators). The idea is to block certain kinds of SQL injection. If it
5326 has no semicolons or white space but it's still not a valid operator, then
5327 the database will complain.
5329 Another approach would be to compare the string against a short list of
5330 approved operators. We don't do that because we want to allow custom
5331 operators like ">100*", which would be difficult or impossible to
5332 express otherwise in a JSON query.
5334 static int is_good_operator( const char* op ) {
5335 if( !op ) return 0; // Sanity check
5339 if( isspace( (unsigned char) *s ) ) {
5340 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
5341 // and IS NOT DISTINCT FROM.
5342 if( !strcasecmp( op, "similar to" ) )
5344 else if( !strcasecmp( op, "is distinct from" ) )
5346 else if( !strcasecmp( op, "is not distinct from" ) )
5351 else if( ';' == *s )
5358 /* ----------------------------------------------------------------------------------
5359 The following machinery supports a stack of query frames for use by SELECT().
5361 A query frame caches information about one level of a SELECT query. When we enter
5362 a subquery, we push another query frame onto the stack, and pop it off when we leave.
5364 The query frame stores information about the core class, and about any joined classes
5367 The main purpose is to map table aliases to classes and tables, so that a query can
5368 join to the same table more than once. A secondary goal is to reduce the number of
5369 lookups in the IDL by caching the results.
5370 ----------------------------------------------------------------------------------*/
5372 #define STATIC_CLASS_INFO_COUNT 3
5374 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
5376 /* ---------------------------------------------------------------------------
5377 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5379 ---------------------------------------------------------------------------*/
5380 static ClassInfo* allocate_class_info( void ) {
5381 // In order to reduce the number of mallocs and frees, we return a static
5382 // instance of ClassInfo, if we can find one that we're not already using.
5383 // We rely on the fact that the compiler will implicitly initialize the
5384 // static instances so that in_use == 0.
5387 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5388 if( ! static_class_info[ i ].in_use ) {
5389 static_class_info[ i ].in_use = 1;
5390 return static_class_info + i;
5394 // The static ones are all in use. Malloc one.
5396 return safe_malloc( sizeof( ClassInfo ) );
5399 /* --------------------------------------------------------------------------
5400 Free any malloc'd memory owned by a ClassInfo; return it to a pristine state
5401 ---------------------------------------------------------------------------*/
5402 static void clear_class_info( ClassInfo* info ) {
5407 // Free any malloc'd strings
5409 if( info->alias != info->alias_store )
5410 free( info->alias );
5412 if( info->class_name != info->class_name_store )
5413 free( info->class_name );
5415 free( info->source_def );
5417 info->alias = info->class_name = info->source_def = NULL;
5421 /* --------------------------------------------------------------------------
5422 Deallocate a ClassInfo and everything it owns
5423 ---------------------------------------------------------------------------*/
5424 static void free_class_info( ClassInfo* info ) {
5429 clear_class_info( info );
5431 // If it's one of the static instances, just mark it as not in use
5434 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5435 if( info == static_class_info + i ) {
5436 static_class_info[ i ].in_use = 0;
5441 // Otherwise it must have been malloc'd, so free it
5446 /* --------------------------------------------------------------------------
5447 Populate an already-allocated ClassInfo. Return 0 if successful, 1 if not.
5448 ---------------------------------------------------------------------------*/
5449 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
5452 osrfLogError( OSRF_LOG_MARK,
5453 "%s ERROR: No ClassInfo available to populate", MODULENAME );
5454 info->alias = info->class_name = info->source_def = NULL;
5455 info->class_def = info->fields = info->links = NULL;
5460 osrfLogError( OSRF_LOG_MARK,
5461 "%s ERROR: No class name provided for lookup", MODULENAME );
5462 info->alias = info->class_name = info->source_def = NULL;
5463 info->class_def = info->fields = info->links = NULL;
5467 // Alias defaults to class name if not supplied
5468 if( ! alias || ! alias[ 0 ] )
5471 // Look up class info in the IDL
5472 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
5474 osrfLogError( OSRF_LOG_MARK,
5475 "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
5476 info->alias = info->class_name = info->source_def = NULL;
5477 info->class_def = info->fields = info->links = NULL;
5479 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
5480 osrfLogError( OSRF_LOG_MARK,
5481 "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
5482 info->alias = info->class_name = info->source_def = NULL;
5483 info->class_def = info->fields = info->links = NULL;
5487 osrfHash* links = osrfHashGet( class_def, "links" );
5489 osrfLogError( OSRF_LOG_MARK,
5490 "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
5491 info->alias = info->class_name = info->source_def = NULL;
5492 info->class_def = info->fields = info->links = NULL;
5496 osrfHash* fields = osrfHashGet( class_def, "fields" );
5498 osrfLogError( OSRF_LOG_MARK,
5499 "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
5500 info->alias = info->class_name = info->source_def = NULL;
5501 info->class_def = info->fields = info->links = NULL;
5505 char* source_def = getSourceDefinition( class_def );
5509 // We got everything we need, so populate the ClassInfo
5510 if( strlen( alias ) > ALIAS_STORE_SIZE )
5511 info->alias = strdup( alias );
5513 strcpy( info->alias_store, alias );
5514 info->alias = info->alias_store;
5517 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
5518 info->class_name = strdup( class );
5520 strcpy( info->class_name_store, class );
5521 info->class_name = info->class_name_store;
5524 info->source_def = source_def;
5526 info->class_def = class_def;
5527 info->links = links;
5528 info->fields = fields;
5533 #define STATIC_FRAME_COUNT 3
5535 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
5537 /* ---------------------------------------------------------------------------
5538 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5540 ---------------------------------------------------------------------------*/
5541 static QueryFrame* allocate_frame( void ) {
5542 // In order to reduce the number of mallocs and frees, we return a static
5543 // instance of QueryFrame, if we can find one that we're not already using.
5544 // We rely on the fact that the compiler will implicitly initialize the
5545 // static instances so that in_use == 0.
5548 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5549 if( ! static_frame[ i ].in_use ) {
5550 static_frame[ i ].in_use = 1;
5551 return static_frame + i;
5555 // The static ones are all in use. Malloc one.
5557 return safe_malloc( sizeof( QueryFrame ) );
5560 /* --------------------------------------------------------------------------
5561 Free a QueryFrame, and all the memory it owns.
5562 ---------------------------------------------------------------------------*/
5563 static void free_query_frame( QueryFrame* frame ) {
5568 clear_class_info( &frame->core );
5570 // Free the join list
5572 ClassInfo* info = frame->join_list;
5575 free_class_info( info );
5579 frame->join_list = NULL;
5582 // If the frame is a static instance, just mark it as unused
5584 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5585 if( frame == static_frame + i ) {
5586 static_frame[ i ].in_use = 0;
5591 // Otherwise it must have been malloc'd, so free it
5596 /* --------------------------------------------------------------------------
5597 Search a given QueryFrame for a specified alias. If you find it, return
5598 a pointer to the corresponding ClassInfo. Otherwise return NULL.
5599 ---------------------------------------------------------------------------*/
5600 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
5601 if( ! frame || ! target ) {
5605 ClassInfo* found_class = NULL;
5607 if( !strcmp( target, frame->core.alias ) )
5608 return &(frame->core);
5610 ClassInfo* curr_class = frame->join_list;
5611 while( curr_class ) {
5612 if( strcmp( target, curr_class->alias ) )
5613 curr_class = curr_class->next;
5615 found_class = curr_class;
5624 /* --------------------------------------------------------------------------
5625 Push a new (blank) QueryFrame onto the stack.
5626 ---------------------------------------------------------------------------*/
5627 static void push_query_frame( void ) {
5628 QueryFrame* frame = allocate_frame();
5629 frame->join_list = NULL;
5630 frame->next = curr_query;
5632 // Initialize the ClassInfo for the core class
5633 ClassInfo* core = &frame->core;
5634 core->alias = core->class_name = core->source_def = NULL;
5635 core->class_def = core->fields = core->links = NULL;
5640 /* --------------------------------------------------------------------------
5641 Pop a QueryFrame off the stack and destroy it
5642 ---------------------------------------------------------------------------*/
5643 static void pop_query_frame( void ) {
5648 QueryFrame* popped = curr_query;
5649 curr_query = popped->next;
5651 free_query_frame( popped );
5654 /* --------------------------------------------------------------------------
5655 Populate the ClassInfo for the core class. Return 0 if successful, 1 if not.
5656 ---------------------------------------------------------------------------*/
5657 static int add_query_core( const char* alias, const char* class_name ) {
5660 if( ! curr_query ) {
5661 osrfLogError( OSRF_LOG_MARK,
5662 "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
5664 } else if( curr_query->core.alias ) {
5665 osrfLogError( OSRF_LOG_MARK,
5666 "%s ERROR: Core class %s already populated as %s",
5667 MODULENAME, curr_query->core.class_name, curr_query->core.alias );
5671 build_class_info( &curr_query->core, alias, class_name );
5672 if( curr_query->core.alias )
5675 osrfLogError( OSRF_LOG_MARK,
5676 "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
5681 /* --------------------------------------------------------------------------
5682 Search the current QueryFrame for a specified alias. If you find it,
5683 return a pointer to the corresponding ClassInfo. Otherwise return NULL.
5684 ---------------------------------------------------------------------------*/
5685 static ClassInfo* search_alias( const char* target ) {
5686 return search_alias_in_frame( curr_query, target );
5689 /* --------------------------------------------------------------------------
5690 Search all levels of query for a specified alias, starting with the
5691 current query. If you find it, return a pointer to the corresponding
5692 ClassInfo. Otherwise return NULL.
5693 ---------------------------------------------------------------------------*/
5694 static ClassInfo* search_all_alias( const char* target ) {
5695 ClassInfo* found_class = NULL;
5696 QueryFrame* curr_frame = curr_query;
5698 while( curr_frame ) {
5699 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
5702 curr_frame = curr_frame->next;
5708 /* --------------------------------------------------------------------------
5709 Add a class to the list of classes joined to the current query.
5710 ---------------------------------------------------------------------------*/
5711 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
5713 if( ! classname || ! *classname ) { // sanity check
5714 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
5721 const ClassInfo* conflict = search_alias( alias );
5723 osrfLogError( OSRF_LOG_MARK,
5724 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
5725 MODULENAME, alias, conflict->class_name );
5729 ClassInfo* info = allocate_class_info();
5731 if( build_class_info( info, alias, classname ) ) {
5732 free_class_info( info );
5736 // Add the new ClassInfo to the join list of the current QueryFrame
5737 info->next = curr_query->join_list;
5738 curr_query->join_list = info;
5743 /* --------------------------------------------------------------------------
5744 Destroy all nodes on the query stack.
5745 ---------------------------------------------------------------------------*/
5746 static void clear_query_stack( void ) {