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,
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 char*, 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 char*, osrfHash*, jsonObject*, osrfMethodContext* );
105 static char* searchJOIN ( const jsonObject*, osrfHash* );
106 static char* searchWHERE ( const jsonObject*, osrfHash*, 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] );
233 osrfStringArray* classes = osrfHashKeys( oilsIDL() );
234 osrfLogDebug(OSRF_LOG_MARK, "%d classes loaded", classes->size );
235 osrfLogDebug(OSRF_LOG_MARK,
236 "At most %d methods will be generated", classes->size * global_method_count);
238 // For each class in IDL...
239 while ( (classname = osrfStringArrayGetString(classes, c_index++)) ) {
240 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
242 osrfHash* idlClass = osrfHashGet(oilsIDL(), classname);
244 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), MODULENAME )) {
245 osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on", MODULENAME, classname);
249 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
250 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
254 // Look up some other attributes of the current class
255 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
256 if( !idlClass_fieldmapper ) {
257 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL", classname );
262 osrfHash* idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
263 if (!idlClass_permacrud) {
264 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no permacrud in IDL", classname );
268 const char* readonly = osrfHashGet(idlClass, "readonly");
271 for( i = 0; i < global_method_count; ++i ) { // for each global method
272 const char* method_type = global_method[ i ];
273 osrfLogDebug(OSRF_LOG_MARK,
274 "Using files to build %s class methods for %s", method_type, classname);
277 const char* tmp_method = method_type;
278 if ( *tmp_method == 'i' || *tmp_method == 's') {
279 tmp_method = "retrieve";
281 if (!osrfHashGet( idlClass_permacrud, tmp_method )) continue;
284 if ( str_is_true( readonly ) &&
285 ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd')
288 buffer_reset( method_name );
290 buffer_fadd(method_name, "%s.%s.%s", MODULENAME, method_type, classname);
294 char* _fm = strdup( idlClass_fieldmapper );
295 part = strtok_r(_fm, ":", &st_tmp);
297 buffer_fadd(method_name, "%s.direct.%s", MODULENAME, part);
299 while ((part = strtok_r(NULL, ":", &st_tmp))) {
300 OSRF_BUFFER_ADD_CHAR(method_name, '.');
301 OSRF_BUFFER_ADD(method_name, part);
303 OSRF_BUFFER_ADD_CHAR(method_name, '.');
304 OSRF_BUFFER_ADD(method_name, method_type);
308 char* method = buffer_data(method_name);
311 if (*method_type == 'i' || *method_type == 's') {
312 flags = flags | OSRF_METHOD_STREAMING;
315 osrfHash* method_meta = osrfNewHash();
316 osrfHashSet( method_meta, idlClass, "class");
317 osrfHashSet( method_meta, method, "methodname" );
318 osrfHashSet( method_meta, strdup(method_type), "methodtype" );
320 osrfAppRegisterExtendedMethod(
323 "dispatchCRUDMethod",
331 } // end for each global method
332 } // end for each class in IDL
334 buffer_free( method_name );
335 osrfStringArrayFree( classes );
340 static char* getSourceDefinition( osrfHash* class ) {
342 char* tabledef = osrfHashGet(class, "tablename");
345 tabledef = strdup(tabledef);
347 tabledef = osrfHashGet(class, "source_definition");
349 growing_buffer* tablebuf = buffer_init(128);
350 buffer_fadd( tablebuf, "(%s)", tabledef );
351 tabledef = buffer_release(tablebuf);
353 const char* classname = osrfHashGet( class, "classname" );
358 "%s ERROR No tablename or source_definition for class \"%s\"",
369 * Connects to the database
371 int osrfAppChildInit() {
373 osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
374 dbi_initialize(NULL);
375 osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
377 char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
378 char* user = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
379 char* host = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
380 char* port = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
381 char* db = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
382 char* pw = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
383 char* md = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion", MODULENAME);
385 osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
386 writehandle = dbi_conn_new(driver);
389 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
392 osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
394 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
395 "port=%s, user=%s, pw=%s, db=%s", MODULENAME, host, port, user, pw, db );
397 if(host) dbi_conn_set_option(writehandle, "host", host );
398 if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
399 if(user) dbi_conn_set_option(writehandle, "username", user);
400 if(pw) dbi_conn_set_option(writehandle, "password", pw );
401 if(db) dbi_conn_set_option(writehandle, "dbname", db );
403 if(md) max_flesh_depth = atoi(md);
404 if(max_flesh_depth < 0) max_flesh_depth = 1;
405 if(max_flesh_depth > 1000) max_flesh_depth = 1000;
414 if (dbi_conn_connect(writehandle) < 0) {
416 if (dbi_conn_connect(writehandle) < 0) {
417 dbi_conn_error(writehandle, &err);
418 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
423 osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
427 osrfStringArray* classes = osrfHashKeys( oilsIDL() );
429 while ( (classname = osrfStringArrayGetString(classes, i++)) ) {
430 osrfHash* class = osrfHashGet( oilsIDL(), classname );
431 osrfHash* fields = osrfHashGet( class, "fields" );
433 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
434 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
438 char* tabledef = getSourceDefinition(class);
440 tabledef = strdup( "(null)" );
442 growing_buffer* sql_buf = buffer_init(32);
443 buffer_fadd( sql_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
447 char* sql = buffer_release(sql_buf);
448 osrfLogDebug(OSRF_LOG_MARK, "%s Investigatory SQL = %s", MODULENAME, sql);
450 dbi_result result = dbi_conn_query(writehandle, sql);
456 const char* columnName;
458 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
460 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
462 /* fetch the fieldmapper index */
463 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
465 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", (char*)columnName);
467 /* determine the field type and storage attributes */
469 switch( dbi_result_get_field_type_idx(result, columnIndex) ) {
471 case DBI_TYPE_INTEGER : {
473 if ( !osrfHashGet(_f, "primitive") )
474 osrfHashSet(_f,"number", "primitive");
476 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
477 if( attr & DBI_INTEGER_SIZE8 )
478 osrfHashSet(_f,"INT8", "datatype");
480 osrfHashSet(_f,"INT", "datatype");
483 case DBI_TYPE_DECIMAL :
484 if ( !osrfHashGet(_f, "primitive") )
485 osrfHashSet(_f,"number", "primitive");
487 osrfHashSet(_f,"NUMERIC", "datatype");
490 case DBI_TYPE_STRING :
491 if ( !osrfHashGet(_f, "primitive") )
492 osrfHashSet(_f,"string", "primitive");
493 osrfHashSet(_f,"TEXT", "datatype");
496 case DBI_TYPE_DATETIME :
497 if ( !osrfHashGet(_f, "primitive") )
498 osrfHashSet(_f,"string", "primitive");
500 osrfHashSet(_f,"TIMESTAMP", "datatype");
503 case DBI_TYPE_BINARY :
504 if ( !osrfHashGet(_f, "primitive") )
505 osrfHashSet(_f,"string", "primitive");
507 osrfHashSet(_f,"BYTEA", "datatype");
512 "Setting [%s] to primitive [%s] and datatype [%s]...",
514 osrfHashGet(_f, "primitive"),
515 osrfHashGet(_f, "datatype")
519 } // end while loop for traversing result
520 dbi_result_free(result);
522 osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", (char*)classname);
526 osrfStringArrayFree(classes);
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)
1111 osrfStringArray* class_list;
1113 if (foreign_context) {
1114 class_list = osrfHashKeys( foreign_context );
1115 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_list->size);
1117 if (class_list->size > 0) {
1120 char* class_name = NULL;
1121 while ( (class_name = osrfStringArrayGetString(class_list, i++)) ) {
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 osrfStringArrayFree(class_list);
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 osrfStringArrayFree(class_list);
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 (");
1485 osrfStringArray* field_list = osrfHashKeys( fields );
1486 while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
1488 osrfHash* field = osrfHashGet( fields, field_name );
1490 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1493 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1496 if (field_object && field_object->classname) {
1497 value = oilsFMGetString(
1499 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1502 value = jsonObjectToSimpleString( field_object );
1509 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1510 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1513 buffer_add(col_buf, field_name);
1515 if (!field_object || field_object->type == JSON_NULL) {
1516 buffer_add( val_buf, "DEFAULT" );
1518 } else if ( !strcmp(get_primitive( field ), "number") ) {
1519 const char* numtype = get_datatype( field );
1520 if ( !strcmp( numtype, "INT8") ) {
1521 buffer_fadd( val_buf, "%lld", atoll(value) );
1523 } else if ( !strcmp( numtype, "INT") ) {
1524 buffer_fadd( val_buf, "%d", atoi(value) );
1526 } else if ( !strcmp( numtype, "NUMERIC") ) {
1527 buffer_fadd( val_buf, "%f", atof(value) );
1530 if ( dbi_conn_quote_string(writehandle, &value) ) {
1531 OSRF_BUFFER_ADD( val_buf, value );
1534 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1535 osrfAppSessionStatus(
1537 OSRF_STATUS_INTERNALSERVERERROR,
1538 "osrfMethodException",
1540 "Error quoting string -- please see the error log for more details"
1543 buffer_free(table_buf);
1544 buffer_free(col_buf);
1545 buffer_free(val_buf);
1556 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1557 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1559 char* table_str = buffer_release(table_buf);
1560 char* col_str = buffer_release(col_buf);
1561 char* val_str = buffer_release(val_buf);
1562 growing_buffer* sql = buffer_init(128);
1563 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1568 char* query = buffer_release(sql);
1570 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1573 dbi_result result = dbi_conn_query(writehandle, query);
1575 jsonObject* obj = NULL;
1578 obj = jsonNewObject(NULL);
1581 "%s ERROR inserting %s object using query [%s]",
1583 osrfHashGet(meta, "fieldmapper"),
1586 osrfAppSessionStatus(
1588 OSRF_STATUS_INTERNALSERVERERROR,
1589 "osrfMethodException",
1591 "INSERT error -- please see the error log for more details"
1596 char* id = oilsFMGetString(target, pkey);
1598 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1599 growing_buffer* _id = buffer_init(10);
1600 buffer_fadd(_id, "%lld", new_id);
1601 id = buffer_release(_id);
1604 // Find quietness specification, if present
1605 const char* quiet_str = NULL;
1607 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1609 quiet_str = jsonObjectGetString( quiet_obj );
1612 if( str_is_true( quiet_str ) ) { // if quietness is specified
1613 obj = jsonNewObject(id);
1617 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1618 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1620 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1622 jsonObjectFree( where_clause );
1627 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1630 jsonObjectFree( list );
1643 * Fetch one row from a specified table, using a specified value
1644 * for the primary key
1646 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1656 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1658 const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos); // key value
1662 "%s retrieving %s object with primary key value of %s",
1664 osrfHashGet( class_def, "fieldmapper" ),
1665 jsonObjectGetString( id_obj )
1668 // Build a WHERE clause based on the key value
1669 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1672 osrfHashGet( class_def, "primarykey" ),
1673 jsonObjectClone( id_obj )
1676 jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
1678 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
1680 jsonObjectFree( where_clause );
1684 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
1685 jsonObjectFree( list );
1688 if(!verifyObjectPCRUD(ctx, obj)) {
1689 jsonObjectFree(obj);
1692 growing_buffer* msg = buffer_init(128);
1693 OSRF_BUFFER_ADD( msg, MODULENAME );
1694 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1696 char* m = buffer_release(msg);
1697 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1708 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1709 growing_buffer* val_buf = buffer_init(32);
1710 const char* numtype = get_datatype( field );
1712 if ( !strncmp( numtype, "INT", 3 ) ) {
1713 if (value->type == JSON_NUMBER)
1714 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1715 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1717 //const char* val_str = jsonObjectGetString( value );
1718 //buffer_fadd( val_buf, "%ld", atol(val_str) );
1719 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1722 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
1723 if (value->type == JSON_NUMBER)
1724 //buffer_fadd( val_buf, "%f", jsonObjectGetNumber(value) );
1725 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1727 //const char* val_str = jsonObjectGetString( value );
1728 //buffer_fadd( val_buf, "%f", atof(val_str) );
1729 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1733 // Presumably this was really intended ot be a string, so quote it
1734 char* str = jsonObjectToSimpleString( value );
1735 if ( dbi_conn_quote_string(dbhandle, &str) ) {
1736 OSRF_BUFFER_ADD( val_buf, str );
1739 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
1741 buffer_free(val_buf);
1746 return buffer_release(val_buf);
1749 static char* searchINPredicate (const char* class, osrfHash* field,
1750 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1751 growing_buffer* sql_buf = buffer_init(32);
1757 osrfHashGet(field, "name")
1761 buffer_add(sql_buf, "IN (");
1762 } else if (!(strcasecmp(op,"not in"))) {
1763 buffer_add(sql_buf, "NOT IN (");
1765 buffer_add(sql_buf, "IN (");
1768 if (node->type == JSON_HASH) {
1769 // subquery predicate
1770 char* subpred = SELECT(
1772 jsonObjectGetKey( node, "select" ),
1773 jsonObjectGetKey( node, "from" ),
1774 jsonObjectGetKey( node, "where" ),
1775 jsonObjectGetKey( node, "having" ),
1776 jsonObjectGetKey( node, "order_by" ),
1777 jsonObjectGetKey( node, "limit" ),
1778 jsonObjectGetKey( node, "offset" ),
1784 buffer_add(sql_buf, subpred);
1787 buffer_free( sql_buf );
1791 } else if (node->type == JSON_ARRAY) {
1792 // literal value list
1793 int in_item_index = 0;
1794 int in_item_first = 1;
1795 const jsonObject* in_item;
1796 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1801 buffer_add(sql_buf, ", ");
1804 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1805 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1806 MODULENAME, json_type( in_item->type ) );
1807 buffer_free(sql_buf);
1811 // Append the literal value -- quoted if not a number
1812 if ( JSON_NUMBER == in_item->type ) {
1813 char* val = jsonNumberToDBString( field, in_item );
1814 OSRF_BUFFER_ADD( sql_buf, val );
1817 } else if ( !strcmp( get_primitive( field ), "number") ) {
1818 char* val = jsonNumberToDBString( field, in_item );
1819 OSRF_BUFFER_ADD( sql_buf, val );
1823 char* key_string = jsonObjectToSimpleString(in_item);
1824 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1825 OSRF_BUFFER_ADD( sql_buf, key_string );
1828 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1830 buffer_free(sql_buf);
1836 if( in_item_first ) {
1837 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1838 buffer_free( sql_buf );
1842 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1843 MODULENAME, json_type( node->type ) );
1844 buffer_free(sql_buf);
1848 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1850 return buffer_release(sql_buf);
1853 // Receive a JSON_ARRAY representing a function call. The first
1854 // entry in the array is the function name. The rest are parameters.
1855 static char* searchValueTransform( const jsonObject* array ) {
1857 if( array->size < 1 ) {
1858 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
1862 // Get the function name
1863 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
1864 if( func_item->type != JSON_STRING ) {
1865 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
1866 MODULENAME, json_type( func_item->type ) );
1870 growing_buffer* sql_buf = buffer_init(32);
1872 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1873 OSRF_BUFFER_ADD( sql_buf, "( " );
1875 // Get the parameters
1876 int func_item_index = 1; // We already grabbed the zeroth entry
1877 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1879 // Add a separator comma, if we need one
1880 if( func_item_index > 2 )
1881 buffer_add( sql_buf, ", " );
1883 // Add the current parameter
1884 if (func_item->type == JSON_NULL) {
1885 buffer_add( sql_buf, "NULL" );
1887 char* val = jsonObjectToSimpleString(func_item);
1888 if ( dbi_conn_quote_string(dbhandle, &val) ) {
1889 OSRF_BUFFER_ADD( sql_buf, val );
1892 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1893 buffer_free(sql_buf);
1900 buffer_add( sql_buf, " )" );
1902 return buffer_release(sql_buf);
1905 static char* searchFunctionPredicate (const char* class, osrfHash* field,
1906 const jsonObject* node, const char* op) {
1908 if( ! is_good_operator( op ) ) {
1909 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
1913 char* val = searchValueTransform(node);
1917 growing_buffer* sql_buf = buffer_init(32);
1922 osrfHashGet(field, "name"),
1929 return buffer_release(sql_buf);
1932 // class is a class name
1933 // field is a field definition as stored in the IDL
1934 // node comes from the method parameter, and may represent an entry in the SELECT list
1935 static char* searchFieldTransform (const char* class, osrfHash* field, const jsonObject* node) {
1936 growing_buffer* sql_buf = buffer_init(32);
1938 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
1939 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
1941 if(transform_subcolumn) {
1942 if( ! is_identifier( transform_subcolumn ) ) {
1943 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
1944 MODULENAME, transform_subcolumn );
1945 buffer_free( sql_buf );
1948 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
1951 if (field_transform) {
1953 if( ! is_identifier( field_transform ) ) {
1954 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
1955 MODULENAME, field_transform );
1956 buffer_free( sql_buf );
1960 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class, osrfHashGet(field, "name"));
1961 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
1964 if( array->type != JSON_ARRAY ) {
1965 osrfLogError( OSRF_LOG_MARK,
1966 "%s: Expected JSON_ARRAY for function params; found %s",
1967 MODULENAME, json_type( array->type ) );
1968 buffer_free( sql_buf );
1971 int func_item_index = 0;
1972 jsonObject* func_item;
1973 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1975 char* val = jsonObjectToSimpleString(func_item);
1978 buffer_add( sql_buf, ",NULL" );
1979 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
1980 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
1981 OSRF_BUFFER_ADD( sql_buf, val );
1983 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1985 buffer_free(sql_buf);
1992 buffer_add( sql_buf, " )" );
1995 buffer_fadd( sql_buf, "\"%s\".%s", class, osrfHashGet(field, "name"));
1998 if (transform_subcolumn)
1999 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2001 return buffer_release(sql_buf);
2004 static char* searchFieldTransformPredicate (const char* class, osrfHash* field,
2005 const jsonObject* node, const char* op ) {
2007 if( ! is_good_operator( op ) ) {
2008 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2012 char* field_transform = searchFieldTransform( class, field, node );
2013 if( ! field_transform )
2016 int extra_parens = 0; // boolean
2018 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2019 if ( ! value_obj ) {
2020 value = searchWHERE( node, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
2022 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
2023 free(field_transform);
2027 } else if ( value_obj->type == JSON_ARRAY ) {
2028 value = searchValueTransform( value_obj );
2030 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
2031 free( field_transform );
2034 } else if ( value_obj->type == JSON_HASH ) {
2035 value = searchWHERE( value_obj, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
2037 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
2038 free(field_transform);
2042 } else if ( value_obj->type == JSON_NUMBER ) {
2043 value = jsonNumberToDBString( field, value_obj );
2044 } else if ( value_obj->type == JSON_NULL ) {
2045 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
2046 free(field_transform);
2048 } else if ( value_obj->type == JSON_BOOL ) {
2049 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
2050 free(field_transform);
2053 if ( !strcmp( get_primitive( field ), "number") ) {
2054 value = jsonNumberToDBString( field, value_obj );
2056 value = jsonObjectToSimpleString( value_obj );
2057 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2058 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
2060 free(field_transform);
2066 const char* left_parens = "";
2067 const char* right_parens = "";
2069 if( extra_parens ) {
2074 growing_buffer* sql_buf = buffer_init(32);
2078 "%s%s %s %s %s %s%s",
2089 free(field_transform);
2091 return buffer_release(sql_buf);
2094 static char* searchSimplePredicate (const char* op, const char* class,
2095 osrfHash* field, const jsonObject* node) {
2097 if( ! is_good_operator( op ) ) {
2098 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2104 // Get the value to which we are comparing the specified column
2105 if (node->type != JSON_NULL) {
2106 if ( node->type == JSON_NUMBER ) {
2107 val = jsonNumberToDBString( field, node );
2108 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2109 val = jsonNumberToDBString( field, node );
2111 val = jsonObjectToSimpleString(node);
2116 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2117 // Value is not numeric; enclose it in quotes
2118 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2119 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2125 // Compare to a null value
2126 val = strdup( "NULL" );
2127 if (strcmp( op, "=" ))
2133 growing_buffer* sql_buf = buffer_init(32);
2134 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class, osrfHashGet(field, "name"), op, val );
2135 char* pred = buffer_release( sql_buf );
2142 static char* searchBETWEENPredicate (const char* class, osrfHash* field, const jsonObject* node) {
2144 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2145 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2147 if( NULL == y_node ) {
2148 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2151 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2152 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2159 if ( !strcmp( get_primitive( field ), "number") ) {
2160 x_string = jsonNumberToDBString(field, x_node);
2161 y_string = jsonNumberToDBString(field, y_node);
2164 x_string = jsonObjectToSimpleString(x_node);
2165 y_string = jsonObjectToSimpleString(y_node);
2166 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2167 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2168 MODULENAME, x_string, y_string);
2175 growing_buffer* sql_buf = buffer_init(32);
2176 buffer_fadd( sql_buf, "%s BETWEEN %s AND %s", osrfHashGet(field, "name"), x_string, y_string );
2180 return buffer_release(sql_buf);
2183 static char* searchPredicate ( const char* class, osrfHash* field,
2184 jsonObject* node, osrfMethodContext* ctx ) {
2187 if (node->type == JSON_ARRAY) { // equality IN search
2188 pred = searchINPredicate( class, field, node, NULL, ctx );
2189 } else if (node->type == JSON_HASH) { // other search
2190 jsonIterator* pred_itr = jsonNewIterator( node );
2191 if( !jsonIteratorHasNext( pred_itr ) ) {
2192 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2193 MODULENAME, osrfHashGet(field, "name") );
2195 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2197 // Verify that there are no additional predicates
2198 if( jsonIteratorHasNext( pred_itr ) ) {
2199 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2200 MODULENAME, osrfHashGet(field, "name") );
2201 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2202 pred = searchBETWEENPredicate( class, field, pred_node );
2203 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2204 pred = searchINPredicate( class, field, pred_node, pred_itr->key, ctx );
2205 else if ( pred_node->type == JSON_ARRAY )
2206 pred = searchFunctionPredicate( class, field, pred_node, pred_itr->key );
2207 else if ( pred_node->type == JSON_HASH )
2208 pred = searchFieldTransformPredicate( class, field, pred_node, pred_itr->key );
2210 pred = searchSimplePredicate( pred_itr->key, class, field, pred_node );
2212 jsonIteratorFree(pred_itr);
2214 } else if (node->type == JSON_NULL) { // IS NULL search
2215 growing_buffer* _p = buffer_init(64);
2218 "\"%s\".%s IS NULL",
2220 osrfHashGet(field, "name")
2222 pred = buffer_release(_p);
2223 } else { // equality search
2224 pred = searchSimplePredicate( "=", class, field, node );
2243 field : call_number,
2259 static char* searchJOIN ( const jsonObject* join_hash, osrfHash* leftmeta ) {
2261 const jsonObject* working_hash;
2262 jsonObject* freeable_hash = NULL;
2264 if (join_hash->type == JSON_HASH) {
2265 working_hash = join_hash;
2266 } else if (join_hash->type == JSON_STRING) {
2267 // turn it into a JSON_HASH by creating a wrapper
2268 // around a copy of the original
2269 const char* _tmp = jsonObjectGetString( join_hash );
2270 freeable_hash = jsonNewObjectType(JSON_HASH);
2271 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2272 working_hash = freeable_hash;
2276 "%s: JOIN failed; expected JSON object type not found",
2282 growing_buffer* join_buf = buffer_init(128);
2283 const char* leftclass = osrfHashGet(leftmeta, "classname");
2285 jsonObject* snode = NULL;
2286 jsonIterator* search_itr = jsonNewIterator( working_hash );
2288 while ( (snode = jsonIteratorNext( search_itr )) ) {
2289 const char* class = search_itr->key;
2290 const char* table_alias = NULL; // stubbed out for now
2291 const ClassInfo* class_info = add_joined_class( table_alias, class );
2295 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2299 jsonIteratorFree( search_itr );
2300 buffer_free( join_buf );
2302 jsonObjectFree( freeable_hash );
2305 osrfHash* idlClass = class_info->class_def;
2306 osrfHash* links = class_info->links;
2307 const char* table = class_info->source_def;
2309 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2310 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2312 if (field && !fkey) {
2313 // Look up the corresponding join column in the IDL.
2314 // The link must be defined in the child table,
2315 // and point to the right parent table.
2316 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2317 const char* reltype = NULL;
2318 const char* other_class = NULL;
2319 reltype = osrfHashGet( idl_link, "reltype" );
2320 if( reltype && strcmp( reltype, "has_many" ) )
2321 other_class = osrfHashGet( idl_link, "class" );
2322 if( other_class && !strcmp( other_class, leftclass ) )
2323 fkey = osrfHashGet( idl_link, "key" );
2327 "%s: JOIN failed. No link defined from %s.%s to %s",
2333 buffer_free(join_buf);
2335 jsonObjectFree(freeable_hash);
2336 jsonIteratorFree(search_itr);
2340 } else if (!field && fkey) {
2341 // Look up the corresponding join column in the IDL.
2342 // The link must be defined in the child table,
2343 // and point to the right parent table.
2344 osrfHash* left_links = (osrfHash*) osrfHashGet( leftmeta, "links" );
2345 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2346 const char* reltype = NULL;
2347 const char* other_class = NULL;
2348 reltype = osrfHashGet( idl_link, "reltype" );
2349 if( reltype && strcmp( reltype, "has_many" ) )
2350 other_class = osrfHashGet( idl_link, "class" );
2351 if( other_class && !strcmp( other_class, class ) )
2352 field = osrfHashGet( idl_link, "key" );
2356 "%s: JOIN failed. No link defined from %s.%s to %s",
2362 buffer_free(join_buf);
2364 jsonObjectFree(freeable_hash);
2365 jsonIteratorFree(search_itr);
2369 } else if (!field && !fkey) {
2370 osrfHash* left_links = (osrfHash*) osrfHashGet( leftmeta, "links" );
2372 // For each link defined for the left class:
2373 // see if the link references the joined class
2374 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2375 osrfHash* curr_link = NULL;
2376 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2377 const char* other_class = osrfHashGet( curr_link, "class" );
2378 if( other_class && !strcmp( other_class, class ) ) {
2380 // In the IDL, the parent class doesn't know then names of the child
2381 // columns that are pointing to it, so don't use that end of the link
2382 const char* reltype = osrfHashGet( curr_link, "reltype" );
2383 if( reltype && strcmp( reltype, "has_many" ) ) {
2384 // Found a link between the classes
2385 fkey = osrfHashIteratorKey( itr );
2386 field = osrfHashGet( curr_link, "key" );
2391 osrfHashIteratorFree( itr );
2393 if (!field || !fkey) {
2394 // Do another such search, with the classes reversed
2396 // For each link defined for the joined class:
2397 // see if the link references the left class
2398 osrfHashIterator* itr = osrfNewHashIterator( links );
2399 osrfHash* curr_link = NULL;
2400 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2401 const char* other_class = osrfHashGet( curr_link, "class" );
2402 if( other_class && !strcmp( other_class, leftclass ) ) {
2404 // In the IDL, the parent class doesn't know then names of the child
2405 // columns that are pointing to it, so don't use that end of the link
2406 const char* reltype = osrfHashGet( curr_link, "reltype" );
2407 if( reltype && strcmp( reltype, "has_many" ) ) {
2408 // Found a link between the classes
2409 field = osrfHashIteratorKey( itr );
2410 fkey = osrfHashGet( curr_link, "key" );
2415 osrfHashIteratorFree( itr );
2418 if (!field || !fkey) {
2421 "%s: JOIN failed. No link defined between %s and %s",
2426 buffer_free(join_buf);
2428 jsonObjectFree(freeable_hash);
2429 jsonIteratorFree(search_itr);
2435 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2437 if ( !strcasecmp(type,"left") ) {
2438 buffer_add(join_buf, " LEFT JOIN");
2439 } else if ( !strcasecmp(type,"right") ) {
2440 buffer_add(join_buf, " RIGHT JOIN");
2441 } else if ( !strcasecmp(type,"full") ) {
2442 buffer_add(join_buf, " FULL JOIN");
2444 buffer_add(join_buf, " INNER JOIN");
2447 buffer_add(join_buf, " INNER JOIN");
2450 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2451 table, class, class, field, leftclass, fkey);
2453 // Add any other join conditions as specified by "filter"
2454 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2456 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2457 if ( filter_op && !strcasecmp("or",filter_op) ) {
2458 buffer_add( join_buf, " OR " );
2460 buffer_add( join_buf, " AND " );
2463 char* jpred = searchWHERE( filter, idlClass, AND_OP_JOIN, NULL );
2465 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2466 OSRF_BUFFER_ADD( join_buf, jpred );
2471 "%s: JOIN failed. Invalid conditional expression.",
2474 jsonIteratorFree( search_itr );
2475 buffer_free( join_buf );
2477 jsonObjectFree( freeable_hash );
2482 buffer_add(join_buf, " ) ");
2484 // Recursively add a nested join, if one is present
2485 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2487 char* jpred = searchJOIN( join_filter, idlClass );
2489 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2490 OSRF_BUFFER_ADD( join_buf, jpred );
2493 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2494 jsonIteratorFree( search_itr );
2495 buffer_free( join_buf );
2497 jsonObjectFree( freeable_hash );
2504 jsonObjectFree(freeable_hash);
2505 jsonIteratorFree(search_itr);
2507 return buffer_release(join_buf);
2512 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2513 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2514 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2516 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2518 search_hash is the JSON expression of the conditions.
2519 meta is the class definition from the IDL, for the relevant table.
2520 opjoin_type indicates whether multiple conditions, if present, should be
2521 connected by AND or OR.
2522 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2523 to pass it to other functions -- and all they do with it is to use the session
2524 and request members to send error messages back to the client.
2528 static char* searchWHERE ( const jsonObject* search_hash, osrfHash* meta, int opjoin_type, osrfMethodContext* ctx ) {
2532 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2540 growing_buffer* sql_buf = buffer_init(128);
2542 jsonObject* node = NULL;
2545 if ( search_hash->type == JSON_ARRAY ) {
2546 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2547 jsonIterator* search_itr = jsonNewIterator( search_hash );
2548 if( !jsonIteratorHasNext( search_itr ) ) {
2551 "%s: Invalid predicate structure: empty JSON array",
2554 jsonIteratorFree( search_itr );
2555 buffer_free( sql_buf );
2559 while ( (node = jsonIteratorNext( search_itr )) ) {
2563 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2564 else buffer_add(sql_buf, " AND ");
2567 char* subpred = searchWHERE( node, meta, opjoin_type, ctx );
2569 buffer_fadd(sql_buf, "( %s )", subpred);
2572 jsonIteratorFree( search_itr );
2573 buffer_free( sql_buf );
2577 jsonIteratorFree(search_itr);
2579 } else if ( search_hash->type == JSON_HASH ) {
2580 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2581 jsonIterator* search_itr = jsonNewIterator( search_hash );
2582 if( !jsonIteratorHasNext( search_itr ) ) {
2585 "%s: Invalid predicate structure: empty JSON object",
2588 jsonIteratorFree( search_itr );
2589 buffer_free( sql_buf );
2593 while ( (node = jsonIteratorNext( search_itr )) ) {
2598 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2599 else buffer_add(sql_buf, " AND ");
2602 if ( '+' == search_itr->key[ 0 ] ) {
2603 if ( node->type == JSON_STRING ) {
2604 // Intended purpose; to allow reference to a Boolean column
2606 // Verify that the class alias is not empty
2607 if( '\0' == search_itr->key[ 1 ] ) {
2610 "%s: Table alias is empty",
2613 jsonIteratorFree( search_itr );
2614 buffer_free( sql_buf );
2618 // Verify that the string looks like an identifier.
2619 const char* subpred = jsonObjectGetString( node );
2620 if( ! is_identifier( subpred ) ) {
2623 "%s: Invalid boolean identifier in WHERE clause: \"%s\"",
2627 jsonIteratorFree( search_itr );
2628 buffer_free( sql_buf );
2632 buffer_fadd(sql_buf, " \"%s\".%s ", search_itr->key + 1, subpred);
2634 char* subpred = searchWHERE( node, osrfHashGet( oilsIDL(), search_itr->key + 1 ), AND_OP_JOIN, ctx );
2636 buffer_fadd(sql_buf, "( %s )", subpred);
2639 jsonIteratorFree( search_itr );
2640 buffer_free( sql_buf );
2644 } else if ( !strcasecmp("-or",search_itr->key) ) {
2645 char* subpred = searchWHERE( node, meta, OR_OP_JOIN, ctx );
2647 buffer_fadd(sql_buf, "( %s )", subpred);
2650 buffer_free( sql_buf );
2653 } else if ( !strcasecmp("-and",search_itr->key) ) {
2654 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2656 buffer_fadd(sql_buf, "( %s )", subpred);
2659 buffer_free( sql_buf );
2662 } else if ( !strcasecmp("-not",search_itr->key) ) {
2663 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2665 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2668 buffer_free( sql_buf );
2671 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2672 char* subpred = SELECT(
2674 jsonObjectGetKey( node, "select" ),
2675 jsonObjectGetKey( node, "from" ),
2676 jsonObjectGetKey( node, "where" ),
2677 jsonObjectGetKey( node, "having" ),
2678 jsonObjectGetKey( node, "order_by" ),
2679 jsonObjectGetKey( node, "limit" ),
2680 jsonObjectGetKey( node, "offset" ),
2686 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2689 buffer_free( sql_buf );
2692 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2693 char* subpred = SELECT(
2695 jsonObjectGetKey( node, "select" ),
2696 jsonObjectGetKey( node, "from" ),
2697 jsonObjectGetKey( node, "where" ),
2698 jsonObjectGetKey( node, "having" ),
2699 jsonObjectGetKey( node, "order_by" ),
2700 jsonObjectGetKey( node, "limit" ),
2701 jsonObjectGetKey( node, "offset" ),
2707 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2710 buffer_free( sql_buf );
2716 char* class = osrfHashGet(meta, "classname");
2717 osrfHash* fields = osrfHashGet(meta, "fields");
2718 osrfHash* field = osrfHashGet( fields, search_itr->key );
2722 char* table = getSourceDefinition(meta);
2724 table = strdup( "(?)" );
2727 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2733 buffer_free(sql_buf);
2735 jsonIteratorFree(search_itr);
2739 char* subpred = searchPredicate( class, field, node, ctx );
2741 buffer_add( sql_buf, subpred );
2744 buffer_free(sql_buf);
2745 jsonIteratorFree(search_itr);
2750 jsonIteratorFree(search_itr);
2753 // ERROR ... only hash and array allowed at this level
2754 char* predicate_string = jsonObjectToJSON( search_hash );
2757 "%s: Invalid predicate structure: %s",
2761 buffer_free(sql_buf);
2762 free(predicate_string);
2766 return buffer_release(sql_buf);
2770 /* method context */ osrfMethodContext* ctx,
2772 /* SELECT */ jsonObject* selhash,
2773 /* FROM */ jsonObject* join_hash,
2774 /* WHERE */ jsonObject* search_hash,
2775 /* HAVING */ jsonObject* having_hash,
2776 /* ORDER BY */ jsonObject* order_hash,
2777 /* LIMIT */ jsonObject* limit,
2778 /* OFFSET */ jsonObject* offset,
2779 /* flags */ int flags
2781 const char* locale = osrf_message_get_last_locale();
2783 // general tmp objects
2784 const jsonObject* tmp_const;
2785 jsonObject* selclass = NULL;
2786 jsonObject* selfield = NULL;
2787 jsonObject* snode = NULL;
2788 jsonObject* onode = NULL;
2790 char* string = NULL;
2791 int from_function = 0;
2796 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale);
2798 // punt if there's no core class
2799 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2802 "%s: FROM clause is missing or empty",
2806 osrfAppSessionStatus(
2808 OSRF_STATUS_INTERNALSERVERERROR,
2809 "osrfMethodException",
2811 "FROM clause is missing or empty in JSON query"
2816 // Push a node onto the stack for the current query. Every level of
2817 // subquery gets its own QueryFrame on the Stack.
2820 // the core search class
2821 const char* core_class = NULL;
2823 // get the core class -- the only key of the top level FROM clause, or a string
2824 if (join_hash->type == JSON_HASH) {
2825 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2826 snode = jsonIteratorNext( tmp_itr );
2828 // Populate the current QueryFrame with information
2829 // about the core class
2830 if( add_query_core( NULL, tmp_itr->key ) ) {
2832 osrfAppSessionStatus(
2834 OSRF_STATUS_INTERNALSERVERERROR,
2835 "osrfMethodException",
2837 "Unable to look up core class"
2841 core_class = curr_query->core.class_name;
2844 jsonObject* extra = jsonIteratorNext( tmp_itr );
2846 jsonIteratorFree( tmp_itr );
2849 // There shouldn't be more than one entry in join_hash
2853 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2857 osrfAppSessionStatus(
2859 OSRF_STATUS_INTERNALSERVERERROR,
2860 "osrfMethodException",
2862 "Malformed FROM clause in JSON query"
2864 return NULL; // Malformed join_hash; extra entry
2866 } else if (join_hash->type == JSON_ARRAY) {
2867 // We're selecting from a function, not from a table
2869 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
2872 } else if (join_hash->type == JSON_STRING) {
2873 // Populate the current QueryFrame with information
2874 // about the core class
2875 core_class = jsonObjectGetString( join_hash );
2877 if( add_query_core( NULL, core_class ) ) {
2879 osrfAppSessionStatus(
2881 OSRF_STATUS_INTERNALSERVERERROR,
2882 "osrfMethodException",
2884 "Unable to look up core class"
2892 "%s: FROM clause is unexpected JSON type: %s",
2894 json_type( join_hash->type )
2897 osrfAppSessionStatus(
2899 OSRF_STATUS_INTERNALSERVERERROR,
2900 "osrfMethodException",
2902 "Ill-formed FROM clause in JSON query"
2907 // Build the join clause, if any, while filling out the list
2908 // of joined classes in the current QueryFrame.
2909 char* join_clause = NULL;
2910 if( join_hash && ! from_function ) {
2912 join_clause = searchJOIN( join_hash, curr_query->core.class_def );
2913 if( ! join_clause ) {
2915 osrfAppSessionStatus(
2917 OSRF_STATUS_INTERNALSERVERERROR,
2918 "osrfMethodException",
2920 "Unable to construct JOIN clause(s)"
2926 // For in case we don't get a select list
2927 jsonObject* defaultselhash = NULL;
2929 // if the select list is empty, or the core class field list is '*',
2930 // build the default select list ...
2932 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
2933 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2934 } else if( selhash->type != JSON_HASH ) {
2937 "%s: Expected JSON_HASH for SELECT clause; found %s",
2939 json_type( selhash->type )
2943 osrfAppSessionStatus(
2945 OSRF_STATUS_INTERNALSERVERERROR,
2946 "osrfMethodException",
2948 "Malformed SELECT clause in JSON query"
2950 free( join_clause );
2952 } else if ( (tmp_const = jsonObjectGetKeyConst( selhash, core_class )) && tmp_const->type == JSON_STRING ) {
2953 const char* _x = jsonObjectGetString( tmp_const );
2954 if (!strncmp( "*", _x, 1 )) {
2955 jsonObjectRemoveKey( selhash, core_class );
2956 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2961 growing_buffer* sql_buf = buffer_init(128);
2963 // temp buffers for the SELECT list and GROUP BY clause
2964 growing_buffer* select_buf = buffer_init(128);
2965 growing_buffer* group_buf = buffer_init(128);
2967 int aggregate_found = 0; // boolean
2969 // Build a select list
2970 if(from_function) // From a function we select everything
2971 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
2974 // If we need to build a default list, prepare to do so
2975 jsonObject* _tmp = jsonObjectGetKey( selhash, core_class );
2976 if ( _tmp && !_tmp->size ) {
2978 osrfHash* core_fields = curr_query->core.fields;
2980 osrfHashIterator* field_itr = osrfNewHashIterator( core_fields );
2981 osrfHash* field_def;
2982 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2983 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2984 // This field is not virtual, so add it to the list
2985 jsonObjectPush( _tmp, jsonNewObject( osrfHashIteratorKey( field_itr ) ) );
2988 osrfHashIteratorFree( field_itr );
2991 // Now build the actual select list
2995 jsonIterator* selclass_itr = jsonNewIterator( selhash );
2996 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
2998 const char* cname = selclass_itr->key;
3000 // Make sure the target relation is in the FROM clause.
3002 // At this point join_hash is a step down from the join_hash we
3003 // received as a parameter. If the original was a JSON_STRING,
3004 // then json_hash is now NULL. If the original was a JSON_HASH,
3005 // then json_hash is now the first (and only) entry in it,
3006 // denoting the core class. We've already excluded the
3007 // possibility that the original was a JSON_ARRAY, because in
3008 // that case from_function would be non-NULL, and we wouldn't
3011 // If the current class isn't the core class
3012 // and it isn't in the join tree, bail out
3013 ClassInfo* class_info = search_alias( cname );
3014 if( ! class_info ) {
3017 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3022 osrfAppSessionStatus(
3024 OSRF_STATUS_INTERNALSERVERERROR,
3025 "osrfMethodException",
3027 "Selected class not in FROM clause in JSON query"
3029 jsonIteratorFree( selclass_itr );
3030 buffer_free( sql_buf );
3031 buffer_free( select_buf );
3032 buffer_free( group_buf );
3033 if( defaultselhash ) jsonObjectFree( defaultselhash );
3034 free( join_clause );
3038 // Capture some attributes of the current class
3039 osrfHash* idlClass = class_info->class_def;
3040 osrfHash* class_field_set = class_info->fields;
3041 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3042 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3044 // stitch together the column list ...
3045 jsonIterator* select_itr = jsonNewIterator( selclass );
3046 while ( (selfield = jsonIteratorNext( select_itr )) ) { // for each SELECT column
3048 // If we need a separator comma, add one
3052 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3055 // ... if it's a string, just toss it on the pile
3056 if (selfield->type == JSON_STRING) {
3058 // Look up the field in the IDL
3059 const char* col_name = jsonObjectGetString( selfield );
3060 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3062 // No such field in current class
3065 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3071 osrfAppSessionStatus(
3073 OSRF_STATUS_INTERNALSERVERERROR,
3074 "osrfMethodException",
3076 "Selected column not defined in JSON query"
3078 jsonIteratorFree( select_itr );
3079 jsonIteratorFree( selclass_itr );
3080 buffer_free( sql_buf );
3081 buffer_free( select_buf );
3082 buffer_free( group_buf );
3083 if( defaultselhash ) jsonObjectFree( defaultselhash );
3084 free( join_clause );
3086 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3087 // Virtual field not allowed
3090 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3096 osrfAppSessionStatus(
3098 OSRF_STATUS_INTERNALSERVERERROR,
3099 "osrfMethodException",
3101 "Selected column may not be virtual in JSON query"
3103 jsonIteratorFree( select_itr );
3104 jsonIteratorFree( selclass_itr );
3105 buffer_free( sql_buf );
3106 buffer_free( select_buf );
3107 buffer_free( group_buf );
3108 if( defaultselhash ) jsonObjectFree( defaultselhash );
3109 free( join_clause );
3115 if (flags & DISABLE_I18N)
3118 i18n = osrfHashGet(field_def, "i18n");
3120 if( str_is_true( i18n ) ) {
3121 buffer_fadd( select_buf,
3122 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3123 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3125 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3128 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3131 // ... but it could be an object, in which case we check for a Field Transform
3132 } else if (selfield->type == JSON_HASH) {
3134 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3136 // Get the field definition from the IDL
3137 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3139 // No such field in current class
3142 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3148 osrfAppSessionStatus(
3150 OSRF_STATUS_INTERNALSERVERERROR,
3151 "osrfMethodException",
3153 "Selected column is not defined in JSON query"
3155 jsonIteratorFree( select_itr );
3156 jsonIteratorFree( selclass_itr );
3157 buffer_free( sql_buf );
3158 buffer_free( select_buf );
3159 buffer_free( group_buf );
3160 if( defaultselhash ) jsonObjectFree( defaultselhash );
3161 free( join_clause );
3163 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3164 // No such field in current class
3167 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3173 osrfAppSessionStatus(
3175 OSRF_STATUS_INTERNALSERVERERROR,
3176 "osrfMethodException",
3178 "Selected column is virtual in JSON query"
3180 jsonIteratorFree( select_itr );
3181 jsonIteratorFree( selclass_itr );
3182 buffer_free( sql_buf );
3183 buffer_free( select_buf );
3184 buffer_free( group_buf );
3185 if( defaultselhash ) jsonObjectFree( defaultselhash );
3186 free( join_clause );
3190 // Decide what to use as a column alias
3192 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3193 _alias = jsonObjectGetString( tmp_const );
3194 } else { // Use field name as the alias
3198 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3199 char* transform_str = searchFieldTransform(cname, field_def, selfield);
3200 if( transform_str ) {
3201 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3202 free(transform_str);
3205 osrfAppSessionStatus(
3207 OSRF_STATUS_INTERNALSERVERERROR,
3208 "osrfMethodException",
3210 "Unable to generate transform function in JSON query"
3212 jsonIteratorFree( select_itr );
3213 jsonIteratorFree( selclass_itr );
3214 buffer_free( sql_buf );
3215 buffer_free( select_buf );
3216 buffer_free( group_buf );
3217 if( defaultselhash ) jsonObjectFree( defaultselhash );
3218 free( join_clause );
3225 if (flags & DISABLE_I18N)
3228 i18n = osrfHashGet(field_def, "i18n");
3230 if( str_is_true( i18n ) ) {
3231 buffer_fadd( select_buf,
3232 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3233 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3235 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3238 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3245 "%s: Selected item is unexpected JSON type: %s",
3247 json_type( selfield->type )
3250 osrfAppSessionStatus(
3252 OSRF_STATUS_INTERNALSERVERERROR,
3253 "osrfMethodException",
3255 "Ill-formed SELECT item in JSON query"
3257 jsonIteratorFree( select_itr );
3258 jsonIteratorFree( selclass_itr );
3259 buffer_free( sql_buf );
3260 buffer_free( select_buf );
3261 buffer_free( group_buf );
3262 if( defaultselhash ) jsonObjectFree( defaultselhash );
3263 free( join_clause );
3267 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3268 if( obj_is_true( agg_obj ) )
3269 aggregate_found = 1;
3271 // Append a comma (except for the first one)
3272 // and add the column to a GROUP BY clause
3276 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3278 buffer_fadd(group_buf, " %d", sel_pos);
3282 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3284 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3285 if ( ! obj_is_true( aggregate_obj ) ) {
3289 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3292 buffer_fadd(group_buf, " %d", sel_pos);
3295 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3299 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3302 _column = searchFieldTransform(cname, field, selfield);
3303 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3304 OSRF_BUFFER_ADD(group_buf, _column);
3305 _column = searchFieldTransform(cname, field, selfield);
3312 } // end while -- iterating across SELECT columns
3314 jsonIteratorFree(select_itr);
3315 } // end while -- iterating across classes
3317 jsonIteratorFree(selclass_itr);
3321 char* col_list = buffer_release(select_buf);
3323 if (from_function) table = searchValueTransform(join_hash);
3324 else table = strdup( curr_query->core.source_def );
3328 osrfAppSessionStatus(
3330 OSRF_STATUS_INTERNALSERVERERROR,
3331 "osrfMethodException",
3333 "Unable to identify table for core class"
3336 buffer_free( sql_buf );
3337 buffer_free( group_buf );
3338 if( defaultselhash ) jsonObjectFree( defaultselhash );
3339 free( join_clause );
3343 // Put it all together
3344 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3348 // Append the join clause, if any
3350 buffer_add(sql_buf, join_clause);
3354 char* order_by_list = NULL;
3355 char* having_buf = NULL;
3357 if (!from_function) {
3359 // Build a WHERE clause, if there is one
3360 if ( search_hash ) {
3361 buffer_add(sql_buf, " WHERE ");
3363 // and it's on the WHERE clause
3364 char* pred = searchWHERE( search_hash, curr_query->core.class_def, AND_OP_JOIN, ctx );
3367 buffer_add(sql_buf, pred);
3371 osrfAppSessionStatus(
3373 OSRF_STATUS_INTERNALSERVERERROR,
3374 "osrfMethodException",
3376 "Severe query error in WHERE predicate -- see error log for more details"
3379 buffer_free(group_buf);
3380 buffer_free(sql_buf);
3381 if (defaultselhash) jsonObjectFree(defaultselhash);
3386 // Build a HAVING clause, if there is one
3387 if ( having_hash ) {
3389 // and it's on the the WHERE clause
3390 having_buf = searchWHERE( having_hash, curr_query->core.class_def, AND_OP_JOIN, ctx );
3392 if( ! having_buf ) {
3394 osrfAppSessionStatus(
3396 OSRF_STATUS_INTERNALSERVERERROR,
3397 "osrfMethodException",
3399 "Severe query error in HAVING predicate -- see error log for more details"
3402 buffer_free(group_buf);
3403 buffer_free(sql_buf);
3404 if (defaultselhash) jsonObjectFree(defaultselhash);
3409 growing_buffer* order_buf = NULL; // to collect ORDER BY list
3411 // Build an ORDER BY clause, if there is one
3412 if( NULL == order_hash )
3413 ; // No ORDER BY? do nothing
3414 else if( JSON_ARRAY == order_hash->type ) {
3415 // Array of field specifications, each specification being a
3416 // hash to define the class, field, and other details
3418 jsonObject* order_spec;
3419 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
3421 if( JSON_HASH != order_spec->type ) {
3422 osrfLogError(OSRF_LOG_MARK,
3423 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
3424 MODULENAME, json_type( order_spec->type ) );
3426 osrfAppSessionStatus(
3428 OSRF_STATUS_INTERNALSERVERERROR,
3429 "osrfMethodException",
3431 "Malformed ORDER BY clause -- see error log for more details"
3433 buffer_free( order_buf );
3435 buffer_free(group_buf);
3436 buffer_free(sql_buf);
3437 if (defaultselhash) jsonObjectFree(defaultselhash);
3442 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
3444 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
3447 OSRF_BUFFER_ADD(order_buf, ", ");
3449 order_buf = buffer_init(128);
3451 if( !field || !class ) {
3452 osrfLogError(OSRF_LOG_MARK,
3453 "%s: Missing class or field name in field specification of ORDER BY clause",
3456 osrfAppSessionStatus(
3458 OSRF_STATUS_INTERNALSERVERERROR,
3459 "osrfMethodException",
3461 "Malformed ORDER BY clause -- see error log for more details"
3463 buffer_free( order_buf );
3465 buffer_free(group_buf);
3466 buffer_free(sql_buf);
3467 if (defaultselhash) jsonObjectFree(defaultselhash);
3471 ClassInfo* order_class_info = search_alias( class );
3472 if( ! order_class_info ) {
3473 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
3474 "not in FROM clause", MODULENAME, class );
3476 osrfAppSessionStatus(
3478 OSRF_STATUS_INTERNALSERVERERROR,
3479 "osrfMethodException",
3481 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3484 buffer_free(group_buf);
3485 buffer_free(sql_buf);
3486 if (defaultselhash) jsonObjectFree(defaultselhash);
3490 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
3492 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
3493 MODULENAME, class, field );
3495 osrfAppSessionStatus(
3497 OSRF_STATUS_INTERNALSERVERERROR,
3498 "osrfMethodException",
3500 "Invalid field referenced in ORDER BY clause -- see error log for more details"
3503 buffer_free(group_buf);
3504 buffer_free(sql_buf);
3505 if (defaultselhash) jsonObjectFree(defaultselhash);
3507 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3508 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3509 MODULENAME, field );
3511 osrfAppSessionStatus(
3513 OSRF_STATUS_INTERNALSERVERERROR,
3514 "osrfMethodException",
3516 "Virtual field in ORDER BY clause -- see error log for more details"
3518 buffer_free( order_buf );
3520 buffer_free(group_buf);
3521 buffer_free(sql_buf);
3522 if (defaultselhash) jsonObjectFree(defaultselhash);
3526 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
3527 char* transform_str = searchFieldTransform( class, field_def, order_spec );
3528 if( ! transform_str ) {
3530 osrfAppSessionStatus(
3532 OSRF_STATUS_INTERNALSERVERERROR,
3533 "osrfMethodException",
3535 "Severe query error in ORDER BY clause -- see error log for more details"
3537 buffer_free( order_buf );
3539 buffer_free(group_buf);
3540 buffer_free(sql_buf);
3541 if (defaultselhash) jsonObjectFree(defaultselhash);
3545 OSRF_BUFFER_ADD( order_buf, transform_str );
3546 free( transform_str );
3549 buffer_fadd( order_buf, "\"%s\".%s", class, field );
3551 const char* direction =
3552 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
3554 if( direction[ 0 ] || 'D' == direction[ 0 ] )
3555 OSRF_BUFFER_ADD( order_buf, " DESC" );
3557 OSRF_BUFFER_ADD( order_buf, " ASC" );
3560 } else if( JSON_HASH == order_hash->type ) {
3561 // This hash is keyed on class name. Each class has either
3562 // an array of field names or a hash keyed on field name.
3563 jsonIterator* class_itr = jsonNewIterator( order_hash );
3564 while ( (snode = jsonIteratorNext( class_itr )) ) {
3566 ClassInfo* order_class_info = search_alias( class_itr->key );
3567 if( ! order_class_info ) {
3568 osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
3569 MODULENAME, class_itr->key );
3571 osrfAppSessionStatus(
3573 OSRF_STATUS_INTERNALSERVERERROR,
3574 "osrfMethodException",
3576 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3578 jsonIteratorFree( class_itr );
3579 buffer_free( order_buf );
3581 buffer_free(group_buf);
3582 buffer_free(sql_buf);
3583 if (defaultselhash) jsonObjectFree(defaultselhash);
3587 osrfHash* field_list_def = order_class_info->fields;
3589 if ( snode->type == JSON_HASH ) {
3591 // Hash is keyed on field names from the current class. For each field
3592 // there is another layer of hash to define the sorting details, if any,
3593 // or a string to indicate direction of sorting.
3594 jsonIterator* order_itr = jsonNewIterator( snode );
3595 while ( (onode = jsonIteratorNext( order_itr )) ) {
3597 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
3599 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3600 MODULENAME, order_itr->key );
3602 osrfAppSessionStatus(
3604 OSRF_STATUS_INTERNALSERVERERROR,
3605 "osrfMethodException",
3607 "Invalid field in ORDER BY clause -- see error log for more details"
3609 jsonIteratorFree( order_itr );
3610 jsonIteratorFree( class_itr );
3611 buffer_free( order_buf );
3613 buffer_free(group_buf);
3614 buffer_free(sql_buf);
3615 if (defaultselhash) jsonObjectFree(defaultselhash);
3617 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3618 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3619 MODULENAME, order_itr->key );
3621 osrfAppSessionStatus(
3623 OSRF_STATUS_INTERNALSERVERERROR,
3624 "osrfMethodException",
3626 "Virtual field in ORDER BY clause -- see error log for more details"
3628 jsonIteratorFree( order_itr );
3629 jsonIteratorFree( class_itr );
3630 buffer_free( order_buf );
3632 buffer_free(group_buf);
3633 buffer_free(sql_buf);
3634 if (defaultselhash) jsonObjectFree(defaultselhash);
3638 const char* direction = NULL;
3639 if ( onode->type == JSON_HASH ) {
3640 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3641 string = searchFieldTransform(
3643 osrfHashGet( field_list_def, order_itr->key ),
3647 if( ctx ) osrfAppSessionStatus(
3649 OSRF_STATUS_INTERNALSERVERERROR,
3650 "osrfMethodException",
3652 "Severe query error in ORDER BY clause -- see error log for more details"
3654 jsonIteratorFree( order_itr );
3655 jsonIteratorFree( class_itr );
3657 buffer_free(group_buf);
3658 buffer_free(order_buf);
3659 buffer_free(sql_buf);
3660 if (defaultselhash) jsonObjectFree(defaultselhash);
3664 growing_buffer* field_buf = buffer_init(16);
3665 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3666 string = buffer_release(field_buf);
3669 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3670 const char* dir = jsonObjectGetString(tmp_const);
3671 if (!strncasecmp(dir, "d", 1)) {
3672 direction = " DESC";
3678 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
3679 osrfLogError( OSRF_LOG_MARK,
3680 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
3681 MODULENAME, json_type( onode->type ) );
3683 osrfAppSessionStatus(
3685 OSRF_STATUS_INTERNALSERVERERROR,
3686 "osrfMethodException",
3688 "Malformed ORDER BY clause -- see error log for more details"
3690 jsonIteratorFree( order_itr );
3691 jsonIteratorFree( class_itr );
3693 buffer_free(group_buf);
3694 buffer_free(order_buf);
3695 buffer_free(sql_buf);
3696 if (defaultselhash) jsonObjectFree(defaultselhash);
3700 string = strdup(order_itr->key);
3701 const char* dir = jsonObjectGetString(onode);
3702 if (!strncasecmp(dir, "d", 1)) {
3703 direction = " DESC";
3710 OSRF_BUFFER_ADD(order_buf, ", ");
3712 order_buf = buffer_init(128);
3714 OSRF_BUFFER_ADD(order_buf, string);
3718 OSRF_BUFFER_ADD(order_buf, direction);
3722 jsonIteratorFree(order_itr);
3724 } else if ( snode->type == JSON_ARRAY ) {
3726 // Array is a list of fields from the current class
3727 unsigned long order_idx = 0;
3728 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
3730 const char* _f = jsonObjectGetString( onode );
3732 osrfHash* field_def = osrfHashGet( field_list_def, _f );
3734 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3737 osrfAppSessionStatus(
3739 OSRF_STATUS_INTERNALSERVERERROR,
3740 "osrfMethodException",
3742 "Invalid field in ORDER BY clause -- see error log for more details"
3744 jsonIteratorFree( class_itr );
3745 buffer_free( order_buf );
3747 buffer_free(group_buf);
3748 buffer_free(sql_buf);
3749 if (defaultselhash) jsonObjectFree(defaultselhash);
3751 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3752 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3755 osrfAppSessionStatus(
3757 OSRF_STATUS_INTERNALSERVERERROR,
3758 "osrfMethodException",
3760 "Virtual field in ORDER BY clause -- see error log for more details"
3762 jsonIteratorFree( class_itr );
3763 buffer_free( order_buf );
3765 buffer_free(group_buf);
3766 buffer_free(sql_buf);
3767 if (defaultselhash) jsonObjectFree(defaultselhash);
3772 OSRF_BUFFER_ADD(order_buf, ", ");
3774 order_buf = buffer_init(128);
3776 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
3780 // IT'S THE OOOOOOOOOOOLD STYLE!
3782 osrfLogError(OSRF_LOG_MARK,
3783 "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3785 osrfAppSessionStatus(
3787 OSRF_STATUS_INTERNALSERVERERROR,
3788 "osrfMethodException",
3790 "Severe query error -- see error log for more details"
3795 buffer_free(group_buf);
3796 buffer_free(order_buf);
3797 buffer_free(sql_buf);
3798 if (defaultselhash) jsonObjectFree(defaultselhash);
3799 jsonIteratorFree(class_itr);
3803 jsonIteratorFree( class_itr );
3805 osrfLogError(OSRF_LOG_MARK,
3806 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
3807 MODULENAME, json_type( order_hash->type ) );
3809 osrfAppSessionStatus(
3811 OSRF_STATUS_INTERNALSERVERERROR,
3812 "osrfMethodException",
3814 "Malformed ORDER BY clause -- see error log for more details"
3816 buffer_free( order_buf );
3818 buffer_free(group_buf);
3819 buffer_free(sql_buf);
3820 if (defaultselhash) jsonObjectFree(defaultselhash);
3825 order_by_list = buffer_release( order_buf );
3829 string = buffer_release(group_buf);
3831 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
3832 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3833 OSRF_BUFFER_ADD( sql_buf, string );
3838 if( having_buf && *having_buf ) {
3839 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3840 OSRF_BUFFER_ADD( sql_buf, having_buf );
3844 if( order_by_list ) {
3846 if ( *order_by_list ) {
3847 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3848 OSRF_BUFFER_ADD( sql_buf, order_by_list );
3851 free( order_by_list );
3855 const char* str = jsonObjectGetString(limit);
3856 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
3860 const char* str = jsonObjectGetString(offset);
3861 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
3864 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3866 if (defaultselhash) jsonObjectFree(defaultselhash);
3868 return buffer_release(sql_buf);
3870 } // end of SELECT()
3872 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
3874 const char* locale = osrf_message_get_last_locale();
3876 osrfHash* fields = osrfHashGet(meta, "fields");
3877 char* core_class = osrfHashGet(meta, "classname");
3879 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
3881 jsonObject* node = NULL;
3882 jsonObject* snode = NULL;
3883 jsonObject* onode = NULL;
3884 const jsonObject* _tmp = NULL;
3885 jsonObject* selhash = NULL;
3886 jsonObject* defaultselhash = NULL;
3888 growing_buffer* sql_buf = buffer_init(128);
3889 growing_buffer* select_buf = buffer_init(128);
3891 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
3892 defaultselhash = jsonNewObjectType(JSON_HASH);
3893 selhash = defaultselhash;
3896 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
3897 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
3898 jsonObject* flist = jsonObjectGetKey( selhash, core_class );
3903 osrfStringArray* keys = osrfHashKeys( fields );
3904 while ( (field = osrfStringArrayGetString(keys, i++)) ) {
3905 if( ! str_is_true( osrfHashGet( osrfHashGet( fields, field ), "virtual" ) ) )
3906 jsonObjectPush( flist, jsonNewObject( field ) );
3908 osrfStringArrayFree(keys);
3912 jsonIterator* class_itr = jsonNewIterator( selhash );
3913 while ( (snode = jsonIteratorNext( class_itr )) ) {
3915 char* cname = class_itr->key;
3916 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
3917 if (!idlClass) continue;
3919 if (strcmp(core_class,class_itr->key)) {
3920 if (!join_hash) continue;
3922 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
3924 jsonObjectFree(found);
3928 jsonObjectFree(found);
3931 jsonIterator* select_itr = jsonNewIterator( snode );
3932 while ( (node = jsonIteratorNext( select_itr )) ) {
3933 const char* item_str = jsonObjectGetString( node );
3934 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
3935 char* fname = osrfHashGet(field, "name");
3937 if (!field) continue;
3942 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
3947 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
3948 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
3951 i18n = osrfHashGet(field, "i18n");
3953 if( str_is_true( i18n ) ) {
3954 char* pkey = osrfHashGet(idlClass, "primarykey");
3955 char* tname = osrfHashGet(idlClass, "tablename");
3957 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);
3959 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3962 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3966 jsonIteratorFree(select_itr);
3969 jsonIteratorFree(class_itr);
3971 char* col_list = buffer_release(select_buf);
3972 char* table = getSourceDefinition(meta);
3974 table = strdup( "(null)" );
3976 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
3980 // Clear the query stack (as a fail-safe precaution against possible
3981 // leftover garbage); then push the first query frame onto the stack.
3982 clear_query_stack();
3984 if( add_query_core( NULL, core_class ) ) {
3986 osrfAppSessionStatus(
3988 OSRF_STATUS_INTERNALSERVERERROR,
3989 "osrfMethodException",
3991 "Unable to build query frame for core class"
3997 char* join_clause = searchJOIN( join_hash, meta );
3998 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
3999 OSRF_BUFFER_ADD(sql_buf, join_clause);
4003 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4004 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
4006 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
4008 char* pred = searchWHERE( search_hash, meta, AND_OP_JOIN, ctx );
4010 osrfAppSessionStatus(
4012 OSRF_STATUS_INTERNALSERVERERROR,
4013 "osrfMethodException",
4015 "Severe query error -- see error log for more details"
4017 buffer_free(sql_buf);
4018 if(defaultselhash) jsonObjectFree(defaultselhash);
4019 clear_query_stack();
4022 buffer_add(sql_buf, pred);
4027 char* string = NULL;
4028 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4030 growing_buffer* order_buf = buffer_init(128);
4033 jsonIterator* class_itr = jsonNewIterator( _tmp );
4034 while ( (snode = jsonIteratorNext( class_itr )) ) {
4036 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4039 if ( snode->type == JSON_HASH ) {
4041 jsonIterator* order_itr = jsonNewIterator( snode );
4042 while ( (onode = jsonIteratorNext( order_itr )) ) {
4044 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4045 class_itr->key, order_itr->key );
4049 char* direction = NULL;
4050 if ( onode->type == JSON_HASH ) {
4051 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4052 string = searchFieldTransform( class_itr->key, field_def, onode );
4054 osrfAppSessionStatus(
4056 OSRF_STATUS_INTERNALSERVERERROR,
4057 "osrfMethodException",
4059 "Severe query error in ORDER BY clause -- see error log for more details"
4061 jsonIteratorFree( order_itr );
4062 jsonIteratorFree( class_itr );
4063 buffer_free( order_buf );
4064 buffer_free( sql_buf );
4065 if( defaultselhash ) jsonObjectFree( defaultselhash );
4066 clear_query_stack();
4070 growing_buffer* field_buf = buffer_init(16);
4071 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4072 string = buffer_release(field_buf);
4075 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4076 const char* dir = jsonObjectGetString(_tmp);
4077 if (!strncasecmp(dir, "d", 1)) {
4078 direction = " DESC";
4085 string = strdup(order_itr->key);
4086 const char* dir = jsonObjectGetString(onode);
4087 if (!strncasecmp(dir, "d", 1)) {
4088 direction = " DESC";
4097 buffer_add(order_buf, ", ");
4100 buffer_add(order_buf, string);
4104 buffer_add(order_buf, direction);
4109 jsonIteratorFree(order_itr);
4112 const char* str = jsonObjectGetString(snode);
4113 buffer_add(order_buf, str);
4119 jsonIteratorFree(class_itr);
4121 string = buffer_release(order_buf);
4124 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4125 OSRF_BUFFER_ADD( sql_buf, string );
4131 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4132 const char* str = jsonObjectGetString(_tmp);
4140 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4142 const char* str = jsonObjectGetString(_tmp);
4151 if (defaultselhash) jsonObjectFree(defaultselhash);
4152 clear_query_stack();
4154 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4155 return buffer_release(sql_buf);
4158 int doJSONSearch ( osrfMethodContext* ctx ) {
4159 if(osrfMethodVerifyContext( ctx )) {
4160 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4164 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
4169 dbhandle = writehandle;
4171 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4175 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4176 flags |= SELECT_DISTINCT;
4178 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4179 flags |= DISABLE_I18N;
4181 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4184 jsonObjectGetKey( hash, "select" ),
4185 jsonObjectGetKey( hash, "from" ),
4186 jsonObjectGetKey( hash, "where" ),
4187 jsonObjectGetKey( hash, "having" ),
4188 jsonObjectGetKey( hash, "order_by" ),
4189 jsonObjectGetKey( hash, "limit" ),
4190 jsonObjectGetKey( hash, "offset" ),
4193 clear_query_stack();
4200 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4201 dbi_result result = dbi_conn_query(dbhandle, sql);
4204 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4206 if (dbi_result_first_row(result)) {
4207 /* JSONify the result */
4208 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4211 jsonObject* return_val = oilsMakeJSONFromResult( result );
4212 osrfAppRespond( ctx, return_val );
4213 jsonObjectFree( return_val );
4214 } while (dbi_result_next_row(result));
4217 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4220 osrfAppRespondComplete( ctx, NULL );
4222 /* clean up the query */
4223 dbi_result_free(result);
4227 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4228 osrfAppSessionStatus(
4230 OSRF_STATUS_INTERNALSERVERERROR,
4231 "osrfMethodException",
4233 "Severe query error -- see error log for more details"
4241 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4242 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4245 dbhandle = writehandle;
4247 osrfHash* links = osrfHashGet(meta, "links");
4248 osrfHash* fields = osrfHashGet(meta, "fields");
4249 char* core_class = osrfHashGet(meta, "classname");
4250 char* pkey = osrfHashGet(meta, "primarykey");
4252 const jsonObject* _tmp;
4255 char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4257 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4262 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4264 dbi_result result = dbi_conn_query(dbhandle, sql);
4265 if( NULL == result ) {
4266 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4267 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4268 osrfAppSessionStatus(
4270 OSRF_STATUS_INTERNALSERVERERROR,
4271 "osrfMethodException",
4273 "Severe query error -- see error log for more details"
4280 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4283 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
4284 osrfHash* dedup = osrfNewHash();
4286 if (dbi_result_first_row(result)) {
4287 /* JSONify the result */
4288 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4290 obj = oilsMakeFieldmapperFromResult( result, meta );
4291 char* pkey_val = oilsFMGetString( obj, pkey );
4292 if ( osrfHashGet( dedup, pkey_val ) ) {
4293 jsonObjectFree(obj);
4296 osrfHashSet( dedup, pkey_val, pkey_val );
4297 jsonObjectPush(res_list, obj);
4299 } while (dbi_result_next_row(result));
4301 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
4305 osrfHashFree(dedup);
4306 /* clean up the query */
4307 dbi_result_free(result);
4310 if (res_list->size && query_hash) {
4311 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
4313 int x = (int)jsonObjectGetNumber(_tmp);
4314 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
4316 const jsonObject* temp_blob;
4317 if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
4319 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
4320 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
4322 osrfStringArray* link_fields = NULL;
4325 if (flesh_fields->size == 1) {
4326 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
4327 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
4332 link_fields = osrfNewStringArray(1);
4333 jsonIterator* _i = jsonNewIterator( flesh_fields );
4334 while ((_f = jsonIteratorNext( _i ))) {
4335 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4337 jsonIteratorFree(_i);
4342 unsigned long res_idx = 0;
4343 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
4348 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4350 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4352 osrfHash* kid_link = osrfHashGet(links, link_field);
4353 if (!kid_link) continue;
4355 osrfHash* field = osrfHashGet(fields, link_field);
4356 if (!field) continue;
4358 osrfHash* value_field = field;
4360 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4361 if (!kid_idl) continue;
4363 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4364 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4367 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4368 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4371 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4373 if (link_map->size > 0) {
4374 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4377 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4382 osrfHashGet(kid_link, "class"),
4389 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4390 osrfHashGet(kid_link, "field"),
4391 osrfHashGet(kid_link, "class"),
4392 osrfHashGet(kid_link, "key"),
4393 osrfHashGet(kid_link, "reltype")
4396 const char* search_key = jsonObjectGetString(
4399 atoi( osrfHashGet(value_field, "array_position") )
4404 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4408 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
4410 // construct WHERE clause
4411 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
4414 osrfHashGet(kid_link, "key"),
4415 jsonNewObject( search_key )
4418 // construct the rest of the query
4419 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
4420 jsonObjectSetKey( rest_of_query, "flesh",
4421 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4425 jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
4427 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
4428 jsonObjectSetKey( rest_of_query, "order_by",
4429 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
4433 if (jsonObjectGetKeyConst(query_hash, "select")) {
4434 jsonObjectSetKey( rest_of_query, "select",
4435 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
4439 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
4440 where_clause, rest_of_query, err);
4442 jsonObjectFree( where_clause );
4443 jsonObjectFree( rest_of_query );
4446 osrfStringArrayFree(link_fields);
4447 jsonObjectFree(res_list);
4448 jsonObjectFree(flesh_blob);
4452 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4454 jsonObject* X = NULL;
4455 if ( link_map->size > 0 && kids->size > 0 ) {
4457 kids = jsonNewObjectType(JSON_ARRAY);
4459 jsonObject* _k_node;
4460 unsigned long res_idx = 0;
4461 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
4467 (unsigned long)atoi(
4473 osrfHashGet(kid_link, "class")
4477 osrfStringArrayGetString( link_map, 0 )
4485 } // end while loop traversing X
4488 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4489 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4492 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4493 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4497 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4498 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4501 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4502 jsonObjectClone( kids )
4507 jsonObjectFree(kids);
4511 jsonObjectFree( kids );
4513 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4514 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4517 } // end while loop traversing res_list
4518 jsonObjectFree( flesh_blob );
4519 osrfStringArrayFree(link_fields);
4528 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
4530 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4532 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
4534 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
4537 if (!verifyObjectClass(ctx, target)) {
4542 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4543 osrfAppSessionStatus(
4545 OSRF_STATUS_BADREQUEST,
4546 "osrfMethodException",
4548 "No active transaction -- required for UPDATE"
4554 // The following test is harmless but redundant. If a class is
4555 // readonly, we don't register an update method for it.
4556 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4557 osrfAppSessionStatus(
4559 OSRF_STATUS_BADREQUEST,
4560 "osrfMethodException",
4562 "Cannot UPDATE readonly class"
4568 dbhandle = writehandle;
4570 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4572 // Set the last_xact_id
4573 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4575 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
4576 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4579 char* pkey = osrfHashGet(meta, "primarykey");
4580 osrfHash* fields = osrfHashGet(meta, "fields");
4582 char* id = oilsFMGetString( target, pkey );
4586 "%s updating %s object with %s = %s",
4588 osrfHashGet(meta, "fieldmapper"),
4593 growing_buffer* sql = buffer_init(128);
4594 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4599 osrfStringArray* field_list = osrfHashKeys( fields );
4600 while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
4602 osrfHash* field = osrfHashGet( fields, field_name );
4604 if(!( strcmp( field_name, pkey ) )) continue;
4605 if( str_is_true( osrfHashGet(osrfHashGet(fields,field_name), "virtual") ) )
4608 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4610 int value_is_numeric = 0; // boolean
4612 if (field_object && field_object->classname) {
4613 value = oilsFMGetString(
4615 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4618 value = jsonObjectToSimpleString( field_object );
4619 if( field_object && JSON_NUMBER == field_object->type )
4620 value_is_numeric = 1;
4623 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s", osrfHashGet(meta, "fieldmapper"), field_name, value);
4625 if (!field_object || field_object->type == JSON_NULL) {
4626 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) ) && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
4627 if (first) first = 0;
4628 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4629 buffer_fadd( sql, " %s = NULL", field_name );
4632 } else if ( value_is_numeric || !strcmp( get_primitive( field ), "number") ) {
4633 if (first) first = 0;
4634 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4636 const char* numtype = get_datatype( field );
4637 if ( !strncmp( numtype, "INT", 3 ) ) {
4638 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
4639 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
4640 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
4642 // Must really be intended as a string, so quote it
4643 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4644 buffer_fadd( sql, " %s = %s", field_name, value );
4646 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4647 osrfAppSessionStatus(
4649 OSRF_STATUS_INTERNALSERVERERROR,
4650 "osrfMethodException",
4652 "Error quoting string -- please see the error log for more details"
4662 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
4665 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4666 if (first) first = 0;
4667 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4668 buffer_fadd( sql, " %s = %s", field_name, value );
4671 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4672 osrfAppSessionStatus(
4674 OSRF_STATUS_INTERNALSERVERERROR,
4675 "osrfMethodException",
4677 "Error quoting string -- please see the error log for more details"
4691 jsonObject* obj = jsonNewObject(id);
4693 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4694 dbi_conn_quote_string(dbhandle, &id);
4696 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
4698 char* query = buffer_release(sql);
4699 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
4701 dbi_result result = dbi_conn_query(dbhandle, query);
4705 jsonObjectFree(obj);
4706 obj = jsonNewObject(NULL);
4709 "%s ERROR updating %s object with %s = %s",
4711 osrfHashGet(meta, "fieldmapper"),
4722 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4724 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4726 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4727 osrfAppSessionStatus(
4729 OSRF_STATUS_BADREQUEST,
4730 "osrfMethodException",
4732 "No active transaction -- required for DELETE"
4738 // The following test is harmless but redundant. If a class is
4739 // readonly, we don't register a delete method for it.
4740 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4741 osrfAppSessionStatus(
4743 OSRF_STATUS_BADREQUEST,
4744 "osrfMethodException",
4746 "Cannot DELETE readonly class"
4752 dbhandle = writehandle;
4756 char* pkey = osrfHashGet(meta, "primarykey");
4764 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4765 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4770 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4773 if (!verifyObjectPCRUD( ctx, NULL )) {
4778 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4783 "%s deleting %s object with %s = %s",
4785 osrfHashGet(meta, "fieldmapper"),
4790 obj = jsonNewObject(id);
4792 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4793 dbi_conn_quote_string(writehandle, &id);
4795 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4798 jsonObjectFree(obj);
4799 obj = jsonNewObject(NULL);
4802 "%s ERROR deleting %s object with %s = %s",
4804 osrfHashGet(meta, "fieldmapper"),
4817 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4818 if(!(result && meta)) return jsonNULL;
4820 jsonObject* object = jsonNewObject(NULL);
4821 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4823 osrfHash* fields = osrfHashGet(meta, "fields");
4825 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4829 char dt_string[256];
4833 int columnIndex = 1;
4835 unsigned short type;
4836 const char* columnName;
4838 /* cycle through the column list */
4839 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
4841 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4843 fmIndex = -1; // reset the position
4845 /* determine the field type and storage attributes */
4846 type = dbi_result_get_field_type_idx(result, columnIndex);
4847 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
4849 /* fetch the fieldmapper index */
4850 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4852 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4855 const char* pos = (char*)osrfHashGet(_f, "array_position");
4856 if ( !pos ) continue;
4858 fmIndex = atoi( pos );
4859 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
4864 if (dbi_result_field_is_null_idx(result, columnIndex)) {
4865 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
4870 case DBI_TYPE_INTEGER :
4872 if( attr & DBI_INTEGER_SIZE8 )
4873 jsonObjectSetIndex( object, fmIndex,
4874 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
4876 jsonObjectSetIndex( object, fmIndex,
4877 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
4881 case DBI_TYPE_DECIMAL :
4882 jsonObjectSetIndex( object, fmIndex,
4883 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
4886 case DBI_TYPE_STRING :
4892 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
4897 case DBI_TYPE_DATETIME :
4899 memset(dt_string, '\0', sizeof(dt_string));
4900 memset(&gmdt, '\0', sizeof(gmdt));
4902 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
4905 if (!(attr & DBI_DATETIME_DATE)) {
4906 gmtime_r( &_tmp_dt, &gmdt );
4907 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4908 } else if (!(attr & DBI_DATETIME_TIME)) {
4909 localtime_r( &_tmp_dt, &gmdt );
4910 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4912 localtime_r( &_tmp_dt, &gmdt );
4913 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4916 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
4920 case DBI_TYPE_BINARY :
4921 osrfLogError( OSRF_LOG_MARK,
4922 "Can't do binary at column %s : index %d", columnName, columnIndex);
4931 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
4932 if(!result) return jsonNULL;
4934 jsonObject* object = jsonNewObject(NULL);
4937 char dt_string[256];
4941 int columnIndex = 1;
4943 unsigned short type;
4944 const char* columnName;
4946 /* cycle through the column list */
4947 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
4949 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4951 fmIndex = -1; // reset the position
4953 /* determine the field type and storage attributes */
4954 type = dbi_result_get_field_type_idx(result, columnIndex);
4955 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
4957 if (dbi_result_field_is_null_idx(result, columnIndex)) {
4958 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
4963 case DBI_TYPE_INTEGER :
4965 if( attr & DBI_INTEGER_SIZE8 )
4966 jsonObjectSetKey( object, columnName,
4967 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)) );
4969 jsonObjectSetKey( object, columnName,
4970 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
4973 case DBI_TYPE_DECIMAL :
4974 jsonObjectSetKey( object, columnName,
4975 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
4978 case DBI_TYPE_STRING :
4979 jsonObjectSetKey( object, columnName,
4980 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
4983 case DBI_TYPE_DATETIME :
4985 memset(dt_string, '\0', sizeof(dt_string));
4986 memset(&gmdt, '\0', sizeof(gmdt));
4988 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
4991 if (!(attr & DBI_DATETIME_DATE)) {
4992 gmtime_r( &_tmp_dt, &gmdt );
4993 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4994 } else if (!(attr & DBI_DATETIME_TIME)) {
4995 localtime_r( &_tmp_dt, &gmdt );
4996 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4998 localtime_r( &_tmp_dt, &gmdt );
4999 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5002 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
5005 case DBI_TYPE_BINARY :
5006 osrfLogError( OSRF_LOG_MARK,
5007 "Can't do binary at column %s : index %d", columnName, columnIndex );
5011 } // end while loop traversing result
5016 // Interpret a string as true or false
5017 static int str_is_true( const char* str ) {
5018 if( NULL == str || strcasecmp( str, "true" ) )
5024 // Interpret a jsonObject as true or false
5025 static int obj_is_true( const jsonObject* obj ) {
5028 else switch( obj->type )
5036 if( strcasecmp( obj->value.s, "true" ) )
5040 case JSON_NUMBER : // Support 1/0 for perl's sake
5041 if( jsonObjectGetNumber( obj ) == 1.0 )
5050 // Translate a numeric code into a text string identifying a type of
5051 // jsonObject. To be used for building error messages.
5052 static const char* json_type( int code ) {
5058 return "JSON_ARRAY";
5060 return "JSON_STRING";
5062 return "JSON_NUMBER";
5068 return "(unrecognized)";
5072 // Extract the "primitive" attribute from an IDL field definition.
5073 // If we haven't initialized the app, then we must be running in
5074 // some kind of testbed. In that case, default to "string".
5075 static const char* get_primitive( osrfHash* field ) {
5076 const char* s = osrfHashGet( field, "primitive" );
5078 if( child_initialized )
5081 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5083 osrfHashGet( field, "name" )
5091 // Extract the "datatype" attribute from an IDL field definition.
5092 // If we haven't initialized the app, then we must be running in
5093 // some kind of testbed. In that case, default to to NUMERIC,
5094 // since we look at the datatype only for numbers.
5095 static const char* get_datatype( osrfHash* field ) {
5096 const char* s = osrfHashGet( field, "datatype" );
5098 if( child_initialized )
5101 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5103 osrfHashGet( field, "name" )
5112 If the input string is potentially a valid SQL identifier, return 1.
5115 Purpose: to prevent certain kinds of SQL injection. To that end we
5116 don't necessarily need to follow all the rules exactly, such as requiring
5117 that the first character not be a digit.
5119 We allow leading and trailing white space. In between, we do not allow
5120 punctuation (except for underscores and dollar signs), control
5121 characters, or embedded white space.
5123 More pedantically we should allow quoted identifiers containing arbitrary
5124 characters, but for the foreseeable future such quoted identifiers are not
5125 likely to be an issue.
5127 static int is_identifier( const char* s) {
5131 // Skip leading white space
5132 while( isspace( (unsigned char) *s ) )
5136 return 0; // Nothing but white space? Not okay.
5138 // Check each character until we reach white space or
5139 // end-of-string. Letters, digits, underscores, and
5140 // dollar signs are okay. With the exception of periods
5141 // (as in schema.identifier), control characters and other
5142 // punctuation characters are not okay. Anything else
5143 // is okay -- it could for example be part of a multibyte
5144 // UTF8 character such as a letter with diacritical marks,
5145 // and those are allowed.
5147 if( isalnum( (unsigned char) *s )
5151 ; // Fine; keep going
5152 else if( ispunct( (unsigned char) *s )
5153 || iscntrl( (unsigned char) *s ) )
5156 } while( *s && ! isspace( (unsigned char) *s ) );
5158 // If we found any white space in the above loop,
5159 // the rest had better be all white space.
5161 while( isspace( (unsigned char) *s ) )
5165 return 0; // White space was embedded within non-white space
5171 Determine whether to accept a character string as a comparison operator.
5172 Return 1 if it's good, or 0 if it's bad.
5174 We don't validate it for real. We just make sure that it doesn't contain
5175 any semicolons or white space (with a special exception for the
5176 "SIMILAR TO" operator). The idea is to block certain kinds of SQL
5177 injection. If it has no semicolons or white space but it's still not a
5178 valid operator, then the database will complain.
5180 Another approach would be to compare the string against a short list of
5181 approved operators. We don't do that because we want to allow custom
5182 operators like ">100*", which would be difficult or impossible to
5183 express otherwise in a JSON query.
5185 static int is_good_operator( const char* op ) {
5186 if( !op ) return 0; // Sanity check
5190 if( isspace( (unsigned char) *s ) ) {
5191 // Special exception for SIMILAR TO. Someday we might make
5192 // exceptions for IS DISTINCT FROM and IS NOT DISTINCT FROM.
5193 if( !strcasecmp( op, "similar to" ) )
5198 else if( ';' == *s )
5205 /* ----------------------------------------------------------------------------------
5206 The following machinery supports a stack of query frames for use by SELECT().
5208 A query frame caches information about one level of a SELECT query. When we enter
5209 a subquery, we push another query frame onto the stack, and pop it off when we leave.
5211 The query frame stores information about the core class, and about any joined classes
5214 The main purpose is to map table aliases to classes and tables, so that a query can
5215 join to the same table more than once. A secondary goal is to reduce the number of
5216 lookups in the IDL by caching the results.
5217 ----------------------------------------------------------------------------------*/
5219 #define STATIC_CLASS_INFO_COUNT 3
5221 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
5223 /* ---------------------------------------------------------------------------
5224 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5226 ---------------------------------------------------------------------------*/
5227 static ClassInfo* allocate_class_info( void ) {
5228 // In order to reduce the number of mallocs and frees, we return a static
5229 // instance of ClassInfo, if we can find one that we're not already using.
5230 // We rely on the fact that the compiler will implicitly initialize the
5231 // static instances so that in_use == 0.
5234 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5235 if( ! static_class_info[ i ].in_use ) {
5236 static_class_info[ i ].in_use = 1;
5237 return static_class_info + i;
5241 // The static ones are all in use. Malloc one.
5243 return safe_malloc( sizeof( ClassInfo ) );
5246 /* --------------------------------------------------------------------------
5247 Free any malloc'd memory owned by a ClassInfo; return it to a pristine state
5248 ---------------------------------------------------------------------------*/
5249 static void clear_class_info( ClassInfo* info ) {
5254 // Free any malloc'd strings
5256 if( info->alias != info->alias_store )
5257 free( info->alias );
5259 if( info->class_name != info->class_name_store )
5260 free( info->class_name );
5262 free( info->source_def );
5264 info->alias = info->class_name = info->source_def = NULL;
5268 /* --------------------------------------------------------------------------
5269 Deallocate a ClassInfo and everything it owns
5270 ---------------------------------------------------------------------------*/
5271 static void free_class_info( ClassInfo* info ) {
5276 clear_class_info( info );
5278 // If it's one of the static instances, just mark it as not in use
5281 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5282 if( info == static_class_info + i ) {
5283 static_class_info[ i ].in_use = 0;
5288 // Otherwise it must have been malloc'd, so free it
5293 /* --------------------------------------------------------------------------
5294 Populate an already-allocated ClassInfo. Return 0 if successful, 1 if not.
5295 ---------------------------------------------------------------------------*/
5296 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
5299 osrfLogError( OSRF_LOG_MARK,
5300 "%s ERROR: No ClassInfo available to populate", MODULENAME );
5301 info->alias = info->class_name = info->source_def = NULL;
5302 info->class_def = info->fields = info->links = NULL;
5307 osrfLogError( OSRF_LOG_MARK,
5308 "%s ERROR: No class name provided for lookup", MODULENAME );
5309 info->alias = info->class_name = info->source_def = NULL;
5310 info->class_def = info->fields = info->links = NULL;
5314 // Alias defaults to class name if not supplied
5315 if( ! alias || ! alias[ 0 ] )
5318 // Look up class info in the IDL
5319 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
5321 osrfLogError( OSRF_LOG_MARK,
5322 "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
5323 info->alias = info->class_name = info->source_def = NULL;
5324 info->class_def = info->fields = info->links = NULL;
5326 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
5327 osrfLogError( OSRF_LOG_MARK,
5328 "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
5329 info->alias = info->class_name = info->source_def = NULL;
5330 info->class_def = info->fields = info->links = NULL;
5334 osrfHash* links = osrfHashGet( class_def, "links" );
5336 osrfLogError( OSRF_LOG_MARK,
5337 "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
5338 info->alias = info->class_name = info->source_def = NULL;
5339 info->class_def = info->fields = info->links = NULL;
5343 osrfHash* fields = osrfHashGet( class_def, "fields" );
5345 osrfLogError( OSRF_LOG_MARK,
5346 "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
5347 info->alias = info->class_name = info->source_def = NULL;
5348 info->class_def = info->fields = info->links = NULL;
5352 char* source_def = getSourceDefinition( class_def );
5356 // We got everything we need, so populate the ClassInfo
5357 if( strlen( alias ) > ALIAS_STORE_SIZE )
5358 info->alias = strdup( alias );
5360 strcpy( info->alias_store, alias );
5361 info->alias = info->alias_store;
5364 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
5365 info->class_name = strdup( class );
5367 strcpy( info->class_name_store, class );
5368 info->class_name = info->class_name_store;
5371 info->source_def = source_def;
5373 info->class_def = class_def;
5374 info->links = links;
5375 info->fields = fields;
5380 #define STATIC_FRAME_COUNT 3
5382 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
5384 /* ---------------------------------------------------------------------------
5385 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5387 ---------------------------------------------------------------------------*/
5388 static QueryFrame* allocate_frame( void ) {
5389 // In order to reduce the number of mallocs and frees, we return a static
5390 // instance of QueryFrame, if we can find one that we're not already using.
5391 // We rely on the fact that the compiler will implicitly initialize the
5392 // static instances so that in_use == 0.
5395 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5396 if( ! static_frame[ i ].in_use ) {
5397 static_frame[ i ].in_use = 1;
5398 return static_frame + i;
5402 // The static ones are all in use. Malloc one.
5404 return safe_malloc( sizeof( QueryFrame ) );
5407 /* --------------------------------------------------------------------------
5408 Free a QueryFrame, and all the memory it owns.
5409 ---------------------------------------------------------------------------*/
5410 static void free_query_frame( QueryFrame* frame ) {
5415 clear_class_info( &frame->core );
5417 // Free the join list
5419 ClassInfo* info = frame->join_list;
5422 free_class_info( info );
5426 frame->join_list = NULL;
5429 // If the frame is a static instance, just mark it as unused
5431 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5432 if( frame == static_frame + i ) {
5433 static_frame[ i ].in_use = 0;
5438 // Otherwise it must have been malloc'd, so free it
5443 /* --------------------------------------------------------------------------
5444 Search a given QueryFrame for a specified alias. If you find it, return
5445 a pointer to the corresponding ClassInfo. Otherwise return NULL.
5446 ---------------------------------------------------------------------------*/
5447 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
5448 if( ! frame || ! target ) return NULL;
5450 ClassInfo* found_class = NULL;
5452 if( !strcmp( target, frame->core.alias ) )
5453 return &(frame->core);
5455 ClassInfo* curr_class = frame->join_list;
5456 while( curr_class ) {
5457 if( strcmp( target, curr_class->alias ) )
5458 curr_class = curr_class->next;
5460 found_class = curr_class;
5469 /* --------------------------------------------------------------------------
5470 Push a new (blank) QueryFrame onto the stack.
5471 ---------------------------------------------------------------------------*/
5472 static void push_query_frame( void ) {
5473 QueryFrame* frame = allocate_frame();
5474 frame->join_list = NULL;
5475 frame->next = curr_query;
5477 // Initialize the ClassInfo for the core class
5478 ClassInfo* core = &frame->core;
5479 core->alias = core->class_name = core->source_def = NULL;
5480 core->class_def = core->fields = core->links = NULL;
5485 /* --------------------------------------------------------------------------
5486 Pop a QueryFrame off the stack and destroy it
5487 ---------------------------------------------------------------------------*/
5488 static void pop_query_frame( void ) {
5493 QueryFrame* popped = curr_query;
5494 curr_query = popped->next;
5496 free_query_frame( popped );
5499 /* --------------------------------------------------------------------------
5500 Populate the ClassInfo for the core class. Return 0 if successful, 1 if not.
5501 ---------------------------------------------------------------------------*/
5502 static int add_query_core( const char* alias, const char* class_name ) {
5505 if( ! curr_query ) {
5506 osrfLogError( OSRF_LOG_MARK,
5507 "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
5509 } else if( curr_query->core.alias ) {
5510 osrfLogError( OSRF_LOG_MARK,
5511 "%s ERROR: Core class %s already populated as %s",
5512 MODULENAME, curr_query->core.class_name, curr_query->core.alias );
5516 build_class_info( &curr_query->core, alias, class_name );
5517 if( curr_query->core.alias )
5520 osrfLogError( OSRF_LOG_MARK,
5521 "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
5526 /* --------------------------------------------------------------------------
5527 Search the current QueryFrame for a specified alias. If you find it,
5528 return a pointer to the corresponding ClassInfo. Otherwise return NULL.
5529 ---------------------------------------------------------------------------*/
5530 static ClassInfo* search_alias( const char* target ) {
5531 return search_alias_in_frame( curr_query, target );
5534 /* --------------------------------------------------------------------------
5535 Search all levels of query for a specified alias, starting with the
5536 current query. If you find it, return a pointer to the corresponding
5537 ClassInfo. Otherwise return NULL.
5538 ---------------------------------------------------------------------------*/
5539 static ClassInfo* search_all_alias( const char* target ) {
5540 ClassInfo* found_class = NULL;
5541 QueryFrame* curr_frame = curr_query;
5542 while( curr_frame ) {
5543 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
5546 curr_frame = curr_frame->next;
5552 /* --------------------------------------------------------------------------
5553 Add a class to the list of classes joined to the current query.
5554 ---------------------------------------------------------------------------*/
5555 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
5557 if( ! classname || ! *classname ) { // sanity check
5558 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
5565 const ClassInfo* conflict = search_alias( alias );
5567 osrfLogError( OSRF_LOG_MARK,
5568 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
5569 MODULENAME, alias, conflict->class_name );
5573 ClassInfo* info = allocate_class_info();
5575 if( build_class_info( info, alias, classname ) ) {
5576 free_class_info( info );
5580 // Add the new ClassInfo to the join list of the current QueryFrame
5581 info->next = curr_query->join_list;
5582 curr_query->join_list = info;
5587 /* --------------------------------------------------------------------------
5588 Destroy all nodes on the query stack.
5589 ---------------------------------------------------------------------------*/
5590 static void clear_query_stack( void ) {