2 #include "opensrf/osrf_application.h"
3 #include "opensrf/osrf_settings.h"
4 #include "opensrf/osrf_message.h"
5 #include "opensrf/utils.h"
6 #include "opensrf/osrf_json.h"
7 #include "opensrf/log.h"
8 #include "openils/oils_utils.h"
17 # define MODULENAME "open-ils.reporter-store"
20 # define MODULENAME "open-ils.pcrud"
22 # define MODULENAME "open-ils.cstore"
27 #define DISABLE_I18N 2
28 #define SELECT_DISTINCT 1
32 struct ClassInfoStruct;
33 typedef struct ClassInfoStruct ClassInfo;
35 #define ALIAS_STORE_SIZE 16
36 #define CLASS_NAME_STORE_SIZE 16
38 struct ClassInfoStruct {
42 osrfHash* class_def; // Points into IDL
43 osrfHash* fields; // Points into IDL
44 osrfHash* links; // Points into IDL
46 // The remaining members are private and internal. Client code should not
47 // access them directly.
49 ClassInfo* next; // Supports linked list of joined classes
50 int in_use; // boolean
52 // We usually store the alias and class name in the following arrays, and
53 // point the corresponding pointers at them. When the string is too big
54 // for the array (which will probably never happen in practice), we strdup it.
56 char alias_store[ ALIAS_STORE_SIZE + 1 ];
57 char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
60 struct QueryFrameStruct;
61 typedef struct QueryFrameStruct QueryFrame;
63 struct QueryFrameStruct {
65 ClassInfo* join_list; // linked list of classes joined to the core class
66 QueryFrame* next; // implements stack as linked list
67 int in_use; // boolean
70 int osrfAppChildInit();
71 int osrfAppInitialize();
72 void osrfAppChildExit();
74 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
76 int beginTransaction ( osrfMethodContext* );
77 int commitTransaction ( osrfMethodContext* );
78 int rollbackTransaction ( osrfMethodContext* );
80 int setSavepoint ( osrfMethodContext* );
81 int releaseSavepoint ( osrfMethodContext* );
82 int rollbackSavepoint ( osrfMethodContext* );
84 int doJSONSearch ( osrfMethodContext* );
86 int dispatchCRUDMethod ( osrfMethodContext* );
87 static jsonObject* doCreate ( osrfMethodContext*, int* );
88 static jsonObject* doRetrieve ( osrfMethodContext*, int* );
89 static jsonObject* doUpdate ( osrfMethodContext*, int* );
90 static jsonObject* doDelete ( osrfMethodContext*, int* );
91 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
92 jsonObject* where_hash, jsonObject* query_hash, int* err );
93 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
94 static jsonObject* oilsMakeJSONFromResult( dbi_result );
96 static char* searchSimplePredicate ( const char* op, const char* class_alias,
97 osrfHash* field, const jsonObject* node );
98 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
99 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject*);
100 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*, const char* );
101 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
102 static char* searchINPredicate ( const char*, osrfHash*,
103 jsonObject*, const char*, osrfMethodContext* );
104 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
105 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
106 static char* searchWHERE ( const jsonObject*, const ClassInfo*, int, osrfMethodContext* );
107 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
109 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
111 void userDataFree( void* );
112 static void sessionDataFree( char*, void* );
113 static char* getSourceDefinition( osrfHash* );
114 static int str_is_true( const char* str );
115 static int obj_is_true( const jsonObject* obj );
116 static const char* json_type( int code );
117 static const char* get_primitive( osrfHash* field );
118 static const char* get_datatype( osrfHash* field );
119 static int is_identifier( const char* s);
120 static int is_good_operator( const char* op );
121 static void pop_query_frame( void );
122 static void push_query_frame( void );
123 static int add_query_core( const char* alias, const char* class_name );
124 static ClassInfo* search_alias( const char* target );
125 static ClassInfo* search_all_alias( const char* target );
126 static ClassInfo* add_joined_class( const char* alias, const char* classname );
127 static void clear_query_stack( void );
130 static jsonObject* verifyUserPCRUD( osrfMethodContext* );
131 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
132 static char* org_tree_root( osrfMethodContext* ctx );
133 static jsonObject* single_hash( const char* key, const char* value );
136 static int child_initialized = 0; /* boolean */
138 static dbi_conn writehandle; /* our MASTER db connection */
139 static dbi_conn dbhandle; /* our CURRENT db connection */
140 //static osrfHash * readHandles;
141 static jsonObject* const jsonNULL = NULL; //
142 static int max_flesh_depth = 100;
144 // The following points the top of a stack of QueryFrames. It's a little
145 // confusing because the top level of the query is at the bottom of the stack.
146 static QueryFrame* curr_query = NULL;
148 /* called when this process is about to exit */
149 void osrfAppChildExit() {
150 osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
153 if (writehandle == dbhandle) same = 1;
155 dbi_conn_query(writehandle, "ROLLBACK;");
156 dbi_conn_close(writehandle);
159 if (dbhandle && !same)
160 dbi_conn_close(dbhandle);
162 // XXX add cleanup of readHandles whenever that gets used
167 int osrfAppInitialize() {
169 osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
170 osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
172 if (!oilsIDLInit( osrf_settings_host_value("/IDL") )) return 1; /* return non-zero to indicate error */
174 growing_buffer* method_name = buffer_init(64);
176 // Generic search thingy
177 buffer_add(method_name, MODULENAME);
178 buffer_add(method_name, ".json_query");
179 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
180 "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
183 // first we register all the transaction and savepoint methods
184 buffer_reset(method_name);
185 OSRF_BUFFER_ADD(method_name, MODULENAME);
186 OSRF_BUFFER_ADD(method_name, ".transaction.begin");
187 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
188 "beginTransaction", "", 0, 0 );
190 buffer_reset(method_name);
191 OSRF_BUFFER_ADD(method_name, MODULENAME);
192 OSRF_BUFFER_ADD(method_name, ".transaction.commit");
193 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
194 "commitTransaction", "", 0, 0 );
196 buffer_reset(method_name);
197 OSRF_BUFFER_ADD(method_name, MODULENAME);
198 OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
199 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
200 "rollbackTransaction", "", 0, 0 );
202 buffer_reset(method_name);
203 OSRF_BUFFER_ADD(method_name, MODULENAME);
204 OSRF_BUFFER_ADD(method_name, ".savepoint.set");
205 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
206 "setSavepoint", "", 1, 0 );
208 buffer_reset(method_name);
209 OSRF_BUFFER_ADD(method_name, MODULENAME);
210 OSRF_BUFFER_ADD(method_name, ".savepoint.release");
211 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
212 "releaseSavepoint", "", 1, 0 );
214 buffer_reset(method_name);
215 OSRF_BUFFER_ADD(method_name, MODULENAME);
216 OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
217 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
218 "rollbackSavepoint", "", 1, 0 );
220 static const char* global_method[] = {
228 const int global_method_count
229 = sizeof( global_method ) / sizeof ( global_method[0] );
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_alias, osrfHash* field,
1750 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1751 growing_buffer* sql_buf = buffer_init(32);
1757 osrfHashGet(field, "name")
1761 buffer_add(sql_buf, "IN (");
1762 } else if (!(strcasecmp(op,"not in"))) {
1763 buffer_add(sql_buf, "NOT IN (");
1765 buffer_add(sql_buf, "IN (");
1768 if (node->type == JSON_HASH) {
1769 // subquery predicate
1770 char* subpred = SELECT(
1772 jsonObjectGetKey( node, "select" ),
1773 jsonObjectGetKey( node, "from" ),
1774 jsonObjectGetKey( node, "where" ),
1775 jsonObjectGetKey( node, "having" ),
1776 jsonObjectGetKey( node, "order_by" ),
1777 jsonObjectGetKey( node, "limit" ),
1778 jsonObjectGetKey( node, "offset" ),
1784 buffer_add(sql_buf, subpred);
1787 buffer_free( sql_buf );
1791 } else if (node->type == JSON_ARRAY) {
1792 // literal value list
1793 int in_item_index = 0;
1794 int in_item_first = 1;
1795 const jsonObject* in_item;
1796 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1801 buffer_add(sql_buf, ", ");
1804 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1805 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1806 MODULENAME, json_type( in_item->type ) );
1807 buffer_free(sql_buf);
1811 // Append the literal value -- quoted if not a number
1812 if ( JSON_NUMBER == in_item->type ) {
1813 char* val = jsonNumberToDBString( field, in_item );
1814 OSRF_BUFFER_ADD( sql_buf, val );
1817 } else if ( !strcmp( get_primitive( field ), "number") ) {
1818 char* val = jsonNumberToDBString( field, in_item );
1819 OSRF_BUFFER_ADD( sql_buf, val );
1823 char* key_string = jsonObjectToSimpleString(in_item);
1824 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1825 OSRF_BUFFER_ADD( sql_buf, key_string );
1828 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1830 buffer_free(sql_buf);
1836 if( in_item_first ) {
1837 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1838 buffer_free( sql_buf );
1842 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1843 MODULENAME, json_type( node->type ) );
1844 buffer_free(sql_buf);
1848 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1850 return buffer_release(sql_buf);
1853 // Receive a JSON_ARRAY representing a function call. The first
1854 // entry in the array is the function name. The rest are parameters.
1855 static char* searchValueTransform( const jsonObject* array ) {
1857 if( array->size < 1 ) {
1858 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
1862 // Get the function name
1863 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
1864 if( func_item->type != JSON_STRING ) {
1865 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
1866 MODULENAME, json_type( func_item->type ) );
1870 growing_buffer* sql_buf = buffer_init(32);
1872 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1873 OSRF_BUFFER_ADD( sql_buf, "( " );
1875 // Get the parameters
1876 int func_item_index = 1; // We already grabbed the zeroth entry
1877 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1879 // Add a separator comma, if we need one
1880 if( func_item_index > 2 )
1881 buffer_add( sql_buf, ", " );
1883 // Add the current parameter
1884 if (func_item->type == JSON_NULL) {
1885 buffer_add( sql_buf, "NULL" );
1887 char* val = jsonObjectToSimpleString(func_item);
1888 if ( dbi_conn_quote_string(dbhandle, &val) ) {
1889 OSRF_BUFFER_ADD( sql_buf, val );
1892 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1893 buffer_free(sql_buf);
1900 buffer_add( sql_buf, " )" );
1902 return buffer_release(sql_buf);
1905 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
1906 const jsonObject* node, const char* op) {
1908 if( ! is_good_operator( op ) ) {
1909 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
1913 char* val = searchValueTransform(node);
1917 growing_buffer* sql_buf = buffer_init(32);
1922 osrfHashGet(field, "name"),
1929 return buffer_release(sql_buf);
1932 // class_alias is a class name or other table alias
1933 // field is a field definition as stored in the IDL
1934 // node comes from the method parameter, and may represent an entry in the SELECT list
1935 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
1936 growing_buffer* sql_buf = buffer_init(32);
1938 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
1939 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
1941 if(transform_subcolumn) {
1942 if( ! is_identifier( transform_subcolumn ) ) {
1943 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
1944 MODULENAME, transform_subcolumn );
1945 buffer_free( sql_buf );
1948 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
1951 if (field_transform) {
1953 if( ! is_identifier( field_transform ) ) {
1954 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
1955 MODULENAME, field_transform );
1956 buffer_free( sql_buf );
1960 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class_alias, osrfHashGet(field, "name"));
1961 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
1964 if( array->type != JSON_ARRAY ) {
1965 osrfLogError( OSRF_LOG_MARK,
1966 "%s: Expected JSON_ARRAY for function params; found %s",
1967 MODULENAME, json_type( array->type ) );
1968 buffer_free( sql_buf );
1971 int func_item_index = 0;
1972 jsonObject* func_item;
1973 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1975 char* val = jsonObjectToSimpleString(func_item);
1978 buffer_add( sql_buf, ",NULL" );
1979 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
1980 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
1981 OSRF_BUFFER_ADD( sql_buf, val );
1983 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1985 buffer_free(sql_buf);
1992 buffer_add( sql_buf, " )" );
1995 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
1998 if (transform_subcolumn)
1999 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2001 return buffer_release(sql_buf);
2004 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2005 const jsonObject* node, const char* op ) {
2007 if( ! is_good_operator( op ) ) {
2008 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2012 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2013 if( ! field_transform )
2016 int extra_parens = 0; // boolean
2018 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2019 if ( ! value_obj ) {
2020 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2022 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
2023 free(field_transform);
2027 } else if ( value_obj->type == JSON_ARRAY ) {
2028 value = searchValueTransform( value_obj );
2030 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
2031 free( field_transform );
2034 } else if ( value_obj->type == JSON_HASH ) {
2035 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2037 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
2038 free(field_transform);
2042 } else if ( value_obj->type == JSON_NUMBER ) {
2043 value = jsonNumberToDBString( field, value_obj );
2044 } else if ( value_obj->type == JSON_NULL ) {
2045 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
2046 free(field_transform);
2048 } else if ( value_obj->type == JSON_BOOL ) {
2049 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
2050 free(field_transform);
2053 if ( !strcmp( get_primitive( field ), "number") ) {
2054 value = jsonNumberToDBString( field, value_obj );
2056 value = jsonObjectToSimpleString( value_obj );
2057 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2058 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
2060 free(field_transform);
2066 const char* left_parens = "";
2067 const char* right_parens = "";
2069 if( extra_parens ) {
2074 growing_buffer* sql_buf = buffer_init(32);
2078 "%s%s %s %s %s %s%s",
2089 free(field_transform);
2091 return buffer_release(sql_buf);
2094 static char* searchSimplePredicate (const char* op, const char* class_alias,
2095 osrfHash* field, const jsonObject* node) {
2097 if( ! is_good_operator( op ) ) {
2098 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2104 // Get the value to which we are comparing the specified column
2105 if (node->type != JSON_NULL) {
2106 if ( node->type == JSON_NUMBER ) {
2107 val = jsonNumberToDBString( field, node );
2108 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2109 val = jsonNumberToDBString( field, node );
2111 val = jsonObjectToSimpleString(node);
2116 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2117 // Value is not numeric; enclose it in quotes
2118 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2119 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2125 // Compare to a null value
2126 val = strdup( "NULL" );
2127 if (strcmp( op, "=" ))
2133 growing_buffer* sql_buf = buffer_init(32);
2134 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2135 char* pred = buffer_release( sql_buf );
2142 static char* searchBETWEENPredicate (const char* class_alias,
2143 osrfHash* field, const jsonObject* node) {
2145 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2146 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2148 if( NULL == y_node ) {
2149 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2152 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2153 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2160 if ( !strcmp( get_primitive( field ), "number") ) {
2161 x_string = jsonNumberToDBString(field, x_node);
2162 y_string = jsonNumberToDBString(field, y_node);
2165 x_string = jsonObjectToSimpleString(x_node);
2166 y_string = jsonObjectToSimpleString(y_node);
2167 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2168 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2169 MODULENAME, x_string, y_string);
2176 growing_buffer* sql_buf = buffer_init(32);
2177 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2178 class_alias, osrfHashGet(field, "name"), x_string, y_string );
2182 return buffer_release(sql_buf);
2185 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2186 jsonObject* node, osrfMethodContext* ctx ) {
2189 if (node->type == JSON_ARRAY) { // equality IN search
2190 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2191 } else if (node->type == JSON_HASH) { // other search
2192 jsonIterator* pred_itr = jsonNewIterator( node );
2193 if( !jsonIteratorHasNext( pred_itr ) ) {
2194 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2195 MODULENAME, osrfHashGet(field, "name") );
2197 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2199 // Verify that there are no additional predicates
2200 if( jsonIteratorHasNext( pred_itr ) ) {
2201 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2202 MODULENAME, osrfHashGet(field, "name") );
2203 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2204 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2205 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2206 pred = searchINPredicate( class_info->alias, field, pred_node, pred_itr->key, ctx );
2207 else if ( pred_node->type == JSON_ARRAY )
2208 pred = searchFunctionPredicate( class_info->alias, field, pred_node, pred_itr->key );
2209 else if ( pred_node->type == JSON_HASH )
2210 pred = searchFieldTransformPredicate( class_info, field, pred_node, pred_itr->key );
2212 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2214 jsonIteratorFree(pred_itr);
2216 } else if (node->type == JSON_NULL) { // IS NULL search
2217 growing_buffer* _p = buffer_init(64);
2220 "\"%s\".%s IS NULL",
2221 class_info->class_name,
2222 osrfHashGet(field, "name")
2224 pred = buffer_release(_p);
2225 } else { // equality search
2226 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2245 field : call_number,
2261 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2263 const jsonObject* working_hash;
2264 jsonObject* freeable_hash = NULL;
2266 if (join_hash->type == JSON_HASH) {
2267 working_hash = join_hash;
2268 } else if (join_hash->type == JSON_STRING) {
2269 // turn it into a JSON_HASH by creating a wrapper
2270 // around a copy of the original
2271 const char* _tmp = jsonObjectGetString( join_hash );
2272 freeable_hash = jsonNewObjectType(JSON_HASH);
2273 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2274 working_hash = freeable_hash;
2278 "%s: JOIN failed; expected JSON object type not found",
2284 growing_buffer* join_buf = buffer_init(128);
2285 const char* leftclass = left_info->class_name;
2287 jsonObject* snode = NULL;
2288 jsonIterator* search_itr = jsonNewIterator( working_hash );
2290 while ( (snode = jsonIteratorNext( search_itr )) ) {
2291 const char* right_alias = search_itr->key;
2293 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2295 class = right_alias;
2297 const ClassInfo* right_info = add_joined_class( right_alias, class );
2301 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2305 jsonIteratorFree( search_itr );
2306 buffer_free( join_buf );
2308 jsonObjectFree( freeable_hash );
2311 osrfHash* links = right_info->links;
2312 const char* table = right_info->source_def;
2314 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2315 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2317 if (field && !fkey) {
2318 // Look up the corresponding join column in the IDL.
2319 // The link must be defined in the child table,
2320 // and point to the right parent table.
2321 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2322 const char* reltype = NULL;
2323 const char* other_class = NULL;
2324 reltype = osrfHashGet( idl_link, "reltype" );
2325 if( reltype && strcmp( reltype, "has_many" ) )
2326 other_class = osrfHashGet( idl_link, "class" );
2327 if( other_class && !strcmp( other_class, leftclass ) )
2328 fkey = osrfHashGet( idl_link, "key" );
2332 "%s: JOIN failed. No link defined from %s.%s to %s",
2338 buffer_free(join_buf);
2340 jsonObjectFree(freeable_hash);
2341 jsonIteratorFree(search_itr);
2345 } else if (!field && fkey) {
2346 // Look up the corresponding join column in the IDL.
2347 // The link must be defined in the child table,
2348 // and point to the right parent table.
2349 osrfHash* left_links = left_info->links;
2350 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2351 const char* reltype = NULL;
2352 const char* other_class = NULL;
2353 reltype = osrfHashGet( idl_link, "reltype" );
2354 if( reltype && strcmp( reltype, "has_many" ) )
2355 other_class = osrfHashGet( idl_link, "class" );
2356 if( other_class && !strcmp( other_class, class ) )
2357 field = osrfHashGet( idl_link, "key" );
2361 "%s: JOIN failed. No link defined from %s.%s to %s",
2367 buffer_free(join_buf);
2369 jsonObjectFree(freeable_hash);
2370 jsonIteratorFree(search_itr);
2374 } else if (!field && !fkey) {
2375 osrfHash* left_links = left_info->links;
2377 // For each link defined for the left class:
2378 // see if the link references the joined class
2379 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2380 osrfHash* curr_link = NULL;
2381 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2382 const char* other_class = osrfHashGet( curr_link, "class" );
2383 if( other_class && !strcmp( other_class, class ) ) {
2385 // In the IDL, the parent class doesn't know then names of the child
2386 // columns that are pointing to it, so don't use that end of the link
2387 const char* reltype = osrfHashGet( curr_link, "reltype" );
2388 if( reltype && strcmp( reltype, "has_many" ) ) {
2389 // Found a link between the classes
2390 fkey = osrfHashIteratorKey( itr );
2391 field = osrfHashGet( curr_link, "key" );
2396 osrfHashIteratorFree( itr );
2398 if (!field || !fkey) {
2399 // Do another such search, with the classes reversed
2401 // For each link defined for the joined class:
2402 // see if the link references the left class
2403 osrfHashIterator* itr = osrfNewHashIterator( links );
2404 osrfHash* curr_link = NULL;
2405 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2406 const char* other_class = osrfHashGet( curr_link, "class" );
2407 if( other_class && !strcmp( other_class, leftclass ) ) {
2409 // In the IDL, the parent class doesn't know then names of the child
2410 // columns that are pointing to it, so don't use that end of the link
2411 const char* reltype = osrfHashGet( curr_link, "reltype" );
2412 if( reltype && strcmp( reltype, "has_many" ) ) {
2413 // Found a link between the classes
2414 field = osrfHashIteratorKey( itr );
2415 fkey = osrfHashGet( curr_link, "key" );
2420 osrfHashIteratorFree( itr );
2423 if (!field || !fkey) {
2426 "%s: JOIN failed. No link defined between %s and %s",
2431 buffer_free(join_buf);
2433 jsonObjectFree(freeable_hash);
2434 jsonIteratorFree(search_itr);
2440 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2442 if ( !strcasecmp(type,"left") ) {
2443 buffer_add(join_buf, " LEFT JOIN");
2444 } else if ( !strcasecmp(type,"right") ) {
2445 buffer_add(join_buf, " RIGHT JOIN");
2446 } else if ( !strcasecmp(type,"full") ) {
2447 buffer_add(join_buf, " FULL JOIN");
2449 buffer_add(join_buf, " INNER JOIN");
2452 buffer_add(join_buf, " INNER JOIN");
2455 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2456 table, right_alias, right_alias, field, left_info->alias, fkey);
2458 // Add any other join conditions as specified by "filter"
2459 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2461 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2462 if ( filter_op && !strcasecmp("or",filter_op) ) {
2463 buffer_add( join_buf, " OR " );
2465 buffer_add( join_buf, " AND " );
2468 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2470 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2471 OSRF_BUFFER_ADD( join_buf, jpred );
2476 "%s: JOIN failed. Invalid conditional expression.",
2479 jsonIteratorFree( search_itr );
2480 buffer_free( join_buf );
2482 jsonObjectFree( freeable_hash );
2487 buffer_add(join_buf, " ) ");
2489 // Recursively add a nested join, if one is present
2490 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2492 char* jpred = searchJOIN( join_filter, right_info );
2494 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2495 OSRF_BUFFER_ADD( join_buf, jpred );
2498 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2499 jsonIteratorFree( search_itr );
2500 buffer_free( join_buf );
2502 jsonObjectFree( freeable_hash );
2509 jsonObjectFree(freeable_hash);
2510 jsonIteratorFree(search_itr);
2512 return buffer_release(join_buf);
2517 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2518 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2519 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2521 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2523 search_hash is the JSON expression of the conditions.
2524 meta is the class definition from the IDL, for the relevant table.
2525 opjoin_type indicates whether multiple conditions, if present, should be
2526 connected by AND or OR.
2527 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2528 to pass it to other functions -- and all they do with it is to use the session
2529 and request members to send error messages back to the client.
2533 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
2534 int opjoin_type, osrfMethodContext* ctx ) {
2538 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2541 class_info->class_def,
2546 growing_buffer* sql_buf = buffer_init(128);
2548 jsonObject* node = NULL;
2551 if ( search_hash->type == JSON_ARRAY ) {
2552 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2553 jsonIterator* search_itr = jsonNewIterator( search_hash );
2554 if( !jsonIteratorHasNext( search_itr ) ) {
2557 "%s: Invalid predicate structure: empty JSON array",
2560 jsonIteratorFree( search_itr );
2561 buffer_free( sql_buf );
2565 while ( (node = jsonIteratorNext( search_itr )) ) {
2569 if (opjoin_type == OR_OP_JOIN)
2570 buffer_add(sql_buf, " OR ");
2572 buffer_add(sql_buf, " AND ");
2575 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2577 jsonIteratorFree( search_itr );
2578 buffer_free( sql_buf );
2582 buffer_fadd(sql_buf, "( %s )", subpred);
2585 jsonIteratorFree(search_itr);
2587 } else if ( search_hash->type == JSON_HASH ) {
2588 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2589 jsonIterator* search_itr = jsonNewIterator( search_hash );
2590 if( !jsonIteratorHasNext( search_itr ) ) {
2593 "%s: Invalid predicate structure: empty JSON object",
2596 jsonIteratorFree( search_itr );
2597 buffer_free( sql_buf );
2601 while ( (node = jsonIteratorNext( search_itr )) ) {
2606 if (opjoin_type == OR_OP_JOIN)
2607 buffer_add(sql_buf, " OR ");
2609 buffer_add(sql_buf, " AND ");
2612 if ( '+' == search_itr->key[ 0 ] ) {
2614 // This plus sign prefixes a class name or other table alias;
2615 // make sure the table alias is in scope
2616 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
2617 if( ! alias_info ) {
2620 "%s: Invalid table alias \"%s\" in WHERE clause",
2624 jsonIteratorFree( search_itr );
2625 buffer_free( sql_buf );
2629 if ( node->type == JSON_STRING ) {
2630 // It's the name of a column; make sure it belongs to the class
2631 const char* fieldname = jsonObjectGetString( node );
2632 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
2635 "%s: Invalid column name \"%s\" in WHERE clause for table alias \"%s\"",
2640 jsonIteratorFree( search_itr );
2641 buffer_free( sql_buf );
2645 buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
2647 // It's something more complicated
2648 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
2650 jsonIteratorFree( search_itr );
2651 buffer_free( sql_buf );
2655 buffer_fadd(sql_buf, "( %s )", subpred);
2658 } else if ( '-' == search_itr->key[ 0 ] ) {
2659 if ( !strcasecmp("-or",search_itr->key) ) {
2660 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
2662 jsonIteratorFree( search_itr );
2663 buffer_free( sql_buf );
2667 buffer_fadd(sql_buf, "( %s )", subpred);
2669 } else if ( !strcasecmp("-and",search_itr->key) ) {
2670 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2672 jsonIteratorFree( search_itr );
2673 buffer_free( sql_buf );
2677 buffer_fadd(sql_buf, "( %s )", subpred);
2679 } else if ( !strcasecmp("-not",search_itr->key) ) {
2680 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2682 jsonIteratorFree( search_itr );
2683 buffer_free( sql_buf );
2687 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2689 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2690 char* subpred = SELECT(
2692 jsonObjectGetKey( node, "select" ),
2693 jsonObjectGetKey( node, "from" ),
2694 jsonObjectGetKey( node, "where" ),
2695 jsonObjectGetKey( node, "having" ),
2696 jsonObjectGetKey( node, "order_by" ),
2697 jsonObjectGetKey( node, "limit" ),
2698 jsonObjectGetKey( node, "offset" ),
2704 jsonIteratorFree( search_itr );
2705 buffer_free( sql_buf );
2709 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2711 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2712 char* subpred = SELECT(
2714 jsonObjectGetKey( node, "select" ),
2715 jsonObjectGetKey( node, "from" ),
2716 jsonObjectGetKey( node, "where" ),
2717 jsonObjectGetKey( node, "having" ),
2718 jsonObjectGetKey( node, "order_by" ),
2719 jsonObjectGetKey( node, "limit" ),
2720 jsonObjectGetKey( node, "offset" ),
2726 jsonIteratorFree( search_itr );
2727 buffer_free( sql_buf );
2731 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2733 } else { // Invalid "minus" operator
2736 "%s: Invalid operator \"%s\" in WHERE clause",
2740 jsonIteratorFree( search_itr );
2741 buffer_free( sql_buf );
2747 const char* class = class_info->class_name;
2748 osrfHash* fields = class_info->fields;
2749 osrfHash* field = osrfHashGet( fields, search_itr->key );
2752 const char* table = class_info->source_def;
2755 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2758 table ? table : "?",
2761 jsonIteratorFree(search_itr);
2762 buffer_free(sql_buf);
2766 char* subpred = searchPredicate( class_info, field, node, ctx );
2768 buffer_free(sql_buf);
2769 jsonIteratorFree(search_itr);
2773 buffer_add( sql_buf, subpred );
2777 jsonIteratorFree(search_itr);
2780 // ERROR ... only hash and array allowed at this level
2781 char* predicate_string = jsonObjectToJSON( search_hash );
2784 "%s: Invalid predicate structure: %s",
2788 buffer_free(sql_buf);
2789 free(predicate_string);
2793 return buffer_release(sql_buf);
2797 /* method context */ osrfMethodContext* ctx,
2799 /* SELECT */ jsonObject* selhash,
2800 /* FROM */ jsonObject* join_hash,
2801 /* WHERE */ jsonObject* search_hash,
2802 /* HAVING */ jsonObject* having_hash,
2803 /* ORDER BY */ jsonObject* order_hash,
2804 /* LIMIT */ jsonObject* limit,
2805 /* OFFSET */ jsonObject* offset,
2806 /* flags */ int flags
2808 const char* locale = osrf_message_get_last_locale();
2810 // general tmp objects
2811 const jsonObject* tmp_const;
2812 jsonObject* selclass = NULL;
2813 jsonObject* selfield = NULL;
2814 jsonObject* snode = NULL;
2815 jsonObject* onode = NULL;
2817 char* string = NULL;
2818 int from_function = 0;
2823 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale);
2825 // punt if there's no FROM clause
2826 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2829 "%s: FROM clause is missing or empty",
2833 osrfAppSessionStatus(
2835 OSRF_STATUS_INTERNALSERVERERROR,
2836 "osrfMethodException",
2838 "FROM clause is missing or empty in JSON query"
2843 // Push a node onto the stack for the current query. Every level of
2844 // subquery gets its own QueryFrame on the Stack.
2847 // the core search class
2848 const char* core_class = NULL;
2850 // get the core class -- the only key of the top level FROM clause, or a string
2851 if (join_hash->type == JSON_HASH) {
2852 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2853 snode = jsonIteratorNext( tmp_itr );
2855 // Populate the current QueryFrame with information
2856 // about the core class
2857 if( add_query_core( NULL, tmp_itr->key ) ) {
2859 osrfAppSessionStatus(
2861 OSRF_STATUS_INTERNALSERVERERROR,
2862 "osrfMethodException",
2864 "Unable to look up core class"
2868 core_class = curr_query->core.class_name;
2871 jsonObject* extra = jsonIteratorNext( tmp_itr );
2873 jsonIteratorFree( tmp_itr );
2876 // There shouldn't be more than one entry in join_hash
2880 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2884 osrfAppSessionStatus(
2886 OSRF_STATUS_INTERNALSERVERERROR,
2887 "osrfMethodException",
2889 "Malformed FROM clause in JSON query"
2891 return NULL; // Malformed join_hash; extra entry
2893 } else if (join_hash->type == JSON_ARRAY) {
2894 // We're selecting from a function, not from a table
2896 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
2899 } else if (join_hash->type == JSON_STRING) {
2900 // Populate the current QueryFrame with information
2901 // about the core class
2902 core_class = jsonObjectGetString( join_hash );
2904 if( add_query_core( NULL, core_class ) ) {
2906 osrfAppSessionStatus(
2908 OSRF_STATUS_INTERNALSERVERERROR,
2909 "osrfMethodException",
2911 "Unable to look up core class"
2919 "%s: FROM clause is unexpected JSON type: %s",
2921 json_type( join_hash->type )
2924 osrfAppSessionStatus(
2926 OSRF_STATUS_INTERNALSERVERERROR,
2927 "osrfMethodException",
2929 "Ill-formed FROM clause in JSON query"
2934 // Build the join clause, if any, while filling out the list
2935 // of joined classes in the current QueryFrame.
2936 char* join_clause = NULL;
2937 if( join_hash && ! from_function ) {
2939 join_clause = searchJOIN( join_hash, &curr_query->core );
2940 if( ! join_clause ) {
2942 osrfAppSessionStatus(
2944 OSRF_STATUS_INTERNALSERVERERROR,
2945 "osrfMethodException",
2947 "Unable to construct JOIN clause(s)"
2953 // For in case we don't get a select list
2954 jsonObject* defaultselhash = NULL;
2956 // if the select list is empty, or the core class field list is '*',
2957 // build the default select list ...
2959 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
2960 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2961 } else if( selhash->type != JSON_HASH ) {
2964 "%s: Expected JSON_HASH for SELECT clause; found %s",
2966 json_type( selhash->type )
2970 osrfAppSessionStatus(
2972 OSRF_STATUS_INTERNALSERVERERROR,
2973 "osrfMethodException",
2975 "Malformed SELECT clause in JSON query"
2977 free( join_clause );
2979 } else if ( (tmp_const = jsonObjectGetKeyConst( selhash, core_class )) && tmp_const->type == JSON_STRING ) {
2980 const char* _x = jsonObjectGetString( tmp_const );
2981 if (!strncmp( "*", _x, 1 )) {
2982 jsonObjectRemoveKey( selhash, core_class );
2983 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2987 // temp buffers for the SELECT list and GROUP BY clause
2988 growing_buffer* select_buf = buffer_init(128);
2989 growing_buffer* group_buf = buffer_init(128);
2991 int aggregate_found = 0; // boolean
2993 // Build a select list
2994 if(from_function) // From a function we select everything
2995 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
2998 // If we need to build a default list, prepare to do so
2999 jsonObject* _tmp = jsonObjectGetKey( selhash, core_class );
3000 if ( _tmp && !_tmp->size ) {
3002 osrfHash* core_fields = curr_query->core.fields;
3004 osrfHashIterator* field_itr = osrfNewHashIterator( core_fields );
3005 osrfHash* field_def;
3006 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3007 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3008 // This field is not virtual, so add it to the list
3009 jsonObjectPush( _tmp, jsonNewObject( osrfHashIteratorKey( field_itr ) ) );
3012 osrfHashIteratorFree( field_itr );
3015 // Now build the actual select list
3019 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3020 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3022 const char* cname = selclass_itr->key;
3024 // Make sure the target relation is in the FROM clause.
3026 // At this point join_hash is a step down from the join_hash we
3027 // received as a parameter. If the original was a JSON_STRING,
3028 // then json_hash is now NULL. If the original was a JSON_HASH,
3029 // then json_hash is now the first (and only) entry in it,
3030 // denoting the core class. We've already excluded the
3031 // possibility that the original was a JSON_ARRAY, because in
3032 // that case from_function would be non-NULL, and we wouldn't
3035 // If the current class isn't the core class
3036 // and it isn't in the join tree, bail out
3037 ClassInfo* class_info = search_alias( cname );
3038 if( ! class_info ) {
3041 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3046 osrfAppSessionStatus(
3048 OSRF_STATUS_INTERNALSERVERERROR,
3049 "osrfMethodException",
3051 "Selected class not in FROM clause in JSON query"
3053 jsonIteratorFree( selclass_itr );
3054 buffer_free( select_buf );
3055 buffer_free( group_buf );
3056 if( defaultselhash ) jsonObjectFree( defaultselhash );
3057 free( join_clause );
3061 // Look up some attributes of the current class
3062 osrfHash* idlClass = class_info->class_def;
3063 osrfHash* class_field_set = class_info->fields;
3064 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3065 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3067 if( 0 == selclass->size ) {
3070 "%s: No columns selected from \"%s\"",
3076 // stitch together the column list for the current table alias...
3077 jsonIterator* select_itr = jsonNewIterator( selclass );
3078 while ( (selfield = jsonIteratorNext( select_itr )) ) { // for each SELECT column
3080 // If we need a separator comma, add one
3084 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3087 // ... if it's a string, just toss it on the pile
3088 if (selfield->type == JSON_STRING) {
3090 // Look up the field in the IDL
3091 const char* col_name = jsonObjectGetString( selfield );
3092 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3094 // No such field in current class
3097 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3103 osrfAppSessionStatus(
3105 OSRF_STATUS_INTERNALSERVERERROR,
3106 "osrfMethodException",
3108 "Selected column not defined in JSON query"
3110 jsonIteratorFree( select_itr );
3111 jsonIteratorFree( selclass_itr );
3112 buffer_free( select_buf );
3113 buffer_free( group_buf );
3114 if( defaultselhash ) jsonObjectFree( defaultselhash );
3115 free( join_clause );
3117 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3118 // Virtual field not allowed
3121 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3127 osrfAppSessionStatus(
3129 OSRF_STATUS_INTERNALSERVERERROR,
3130 "osrfMethodException",
3132 "Selected column may not be virtual in JSON query"
3134 jsonIteratorFree( select_itr );
3135 jsonIteratorFree( selclass_itr );
3136 buffer_free( select_buf );
3137 buffer_free( group_buf );
3138 if( defaultselhash ) jsonObjectFree( defaultselhash );
3139 free( join_clause );
3145 if (flags & DISABLE_I18N)
3148 i18n = osrfHashGet(field_def, "i18n");
3150 if( str_is_true( i18n ) ) {
3151 buffer_fadd( select_buf,
3152 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3153 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3155 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3158 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3161 // ... but it could be an object, in which case we check for a Field Transform
3162 } else if (selfield->type == JSON_HASH) {
3164 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3166 // Get the field definition from the IDL
3167 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3169 // No such field in current class
3172 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3178 osrfAppSessionStatus(
3180 OSRF_STATUS_INTERNALSERVERERROR,
3181 "osrfMethodException",
3183 "Selected column is not defined in JSON query"
3185 jsonIteratorFree( select_itr );
3186 jsonIteratorFree( selclass_itr );
3187 buffer_free( select_buf );
3188 buffer_free( group_buf );
3189 if( defaultselhash ) jsonObjectFree( defaultselhash );
3190 free( join_clause );
3192 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3193 // No such field in current class
3196 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3202 osrfAppSessionStatus(
3204 OSRF_STATUS_INTERNALSERVERERROR,
3205 "osrfMethodException",
3207 "Selected column is virtual in JSON query"
3209 jsonIteratorFree( select_itr );
3210 jsonIteratorFree( selclass_itr );
3211 buffer_free( select_buf );
3212 buffer_free( group_buf );
3213 if( defaultselhash ) jsonObjectFree( defaultselhash );
3214 free( join_clause );
3218 // Decide what to use as a column alias
3220 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3221 _alias = jsonObjectGetString( tmp_const );
3222 } else { // Use field name as the alias
3226 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3227 char* transform_str = searchFieldTransform(class_info->alias, field_def, selfield);
3228 if( transform_str ) {
3229 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3230 free(transform_str);
3233 osrfAppSessionStatus(
3235 OSRF_STATUS_INTERNALSERVERERROR,
3236 "osrfMethodException",
3238 "Unable to generate transform function in JSON query"
3240 jsonIteratorFree( select_itr );
3241 jsonIteratorFree( selclass_itr );
3242 buffer_free( select_buf );
3243 buffer_free( group_buf );
3244 if( defaultselhash ) jsonObjectFree( defaultselhash );
3245 free( join_clause );
3252 if (flags & DISABLE_I18N)
3255 i18n = osrfHashGet(field_def, "i18n");
3257 if( str_is_true( i18n ) ) {
3258 buffer_fadd( select_buf,
3259 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3260 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3262 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3265 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3272 "%s: Selected item is unexpected JSON type: %s",
3274 json_type( selfield->type )
3277 osrfAppSessionStatus(
3279 OSRF_STATUS_INTERNALSERVERERROR,
3280 "osrfMethodException",
3282 "Ill-formed SELECT item in JSON query"
3284 jsonIteratorFree( select_itr );
3285 jsonIteratorFree( selclass_itr );
3286 buffer_free( select_buf );
3287 buffer_free( group_buf );
3288 if( defaultselhash ) jsonObjectFree( defaultselhash );
3289 free( join_clause );
3293 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3294 if( obj_is_true( agg_obj ) )
3295 aggregate_found = 1;
3297 // Append a comma (except for the first one)
3298 // and add the column to a GROUP BY clause
3302 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3304 buffer_fadd(group_buf, " %d", sel_pos);
3308 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3310 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3311 if ( ! obj_is_true( aggregate_obj ) ) {
3315 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3318 buffer_fadd(group_buf, " %d", sel_pos);
3321 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3325 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3328 _column = searchFieldTransform(class_info->alias, field, selfield);
3329 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3330 OSRF_BUFFER_ADD(group_buf, _column);
3331 _column = searchFieldTransform(class_info->alias, field, selfield);
3338 } // end while -- iterating across SELECT columns
3340 jsonIteratorFree(select_itr);
3341 } // end while -- iterating across classes
3343 jsonIteratorFree(selclass_itr);
3347 char* col_list = buffer_release(select_buf);
3349 // Make sure the SELECT list isn't empty. This can happen if we try to
3350 // build a default SELECT clause from a non-core table.
3354 osrfAppSessionStatus(
3356 OSRF_STATUS_INTERNALSERVERERROR,
3357 "osrfMethodException",
3359 "SELECT list is empty"
3362 buffer_free( group_buf );
3363 if( defaultselhash ) jsonObjectFree( defaultselhash );
3364 free( join_clause );
3369 if (from_function) table = searchValueTransform(join_hash);
3370 else table = strdup( curr_query->core.source_def );
3374 osrfAppSessionStatus(
3376 OSRF_STATUS_INTERNALSERVERERROR,
3377 "osrfMethodException",
3379 "Unable to identify table for core class"
3382 buffer_free( group_buf );
3383 if( defaultselhash ) jsonObjectFree( defaultselhash );
3384 free( join_clause );
3388 // Put it all together
3389 growing_buffer* sql_buf = buffer_init(128);
3390 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3394 // Append the join clause, if any
3396 buffer_add(sql_buf, join_clause);
3400 char* order_by_list = NULL;
3401 char* having_buf = NULL;
3403 if (!from_function) {
3405 // Build a WHERE clause, if there is one
3406 if ( search_hash ) {
3407 buffer_add(sql_buf, " WHERE ");
3409 // and it's on the WHERE clause
3410 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
3413 osrfAppSessionStatus(
3415 OSRF_STATUS_INTERNALSERVERERROR,
3416 "osrfMethodException",
3418 "Severe query error in WHERE predicate -- see error log for more details"
3421 buffer_free(group_buf);
3422 buffer_free(sql_buf);
3423 if (defaultselhash) jsonObjectFree(defaultselhash);
3427 buffer_add(sql_buf, pred);
3431 // Build a HAVING clause, if there is one
3432 if ( having_hash ) {
3434 // and it's on the the WHERE clause
3435 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
3437 if( ! having_buf ) {
3439 osrfAppSessionStatus(
3441 OSRF_STATUS_INTERNALSERVERERROR,
3442 "osrfMethodException",
3444 "Severe query error in HAVING predicate -- see error log for more details"
3447 buffer_free(group_buf);
3448 buffer_free(sql_buf);
3449 if (defaultselhash) jsonObjectFree(defaultselhash);
3454 growing_buffer* order_buf = NULL; // to collect ORDER BY list
3456 // Build an ORDER BY clause, if there is one
3457 if( NULL == order_hash )
3458 ; // No ORDER BY? do nothing
3459 else if( JSON_ARRAY == order_hash->type ) {
3460 // Array of field specifications, each specification being a
3461 // hash to define the class, field, and other details
3463 jsonObject* order_spec;
3464 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
3466 if( JSON_HASH != order_spec->type ) {
3467 osrfLogError(OSRF_LOG_MARK,
3468 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
3469 MODULENAME, json_type( order_spec->type ) );
3471 osrfAppSessionStatus(
3473 OSRF_STATUS_INTERNALSERVERERROR,
3474 "osrfMethodException",
3476 "Malformed ORDER BY clause -- see error log for more details"
3478 buffer_free( order_buf );
3480 buffer_free(group_buf);
3481 buffer_free(sql_buf);
3482 if (defaultselhash) jsonObjectFree(defaultselhash);
3486 const char* class_alias =
3487 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
3489 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
3492 OSRF_BUFFER_ADD(order_buf, ", ");
3494 order_buf = buffer_init(128);
3496 if( !field || !class_alias ) {
3497 osrfLogError(OSRF_LOG_MARK,
3498 "%s: Missing class or field name in field specification of ORDER BY clause",
3501 osrfAppSessionStatus(
3503 OSRF_STATUS_INTERNALSERVERERROR,
3504 "osrfMethodException",
3506 "Malformed ORDER BY clause -- see error log for more details"
3508 buffer_free( order_buf );
3510 buffer_free(group_buf);
3511 buffer_free(sql_buf);
3512 if (defaultselhash) jsonObjectFree(defaultselhash);
3516 ClassInfo* order_class_info = search_alias( class_alias );
3517 if( ! order_class_info ) {
3518 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
3519 "not in FROM clause", MODULENAME, class_alias );
3521 osrfAppSessionStatus(
3523 OSRF_STATUS_INTERNALSERVERERROR,
3524 "osrfMethodException",
3526 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3529 buffer_free(group_buf);
3530 buffer_free(sql_buf);
3531 if (defaultselhash) jsonObjectFree(defaultselhash);
3535 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
3537 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
3538 MODULENAME, class_alias, field );
3540 osrfAppSessionStatus(
3542 OSRF_STATUS_INTERNALSERVERERROR,
3543 "osrfMethodException",
3545 "Invalid field referenced in ORDER BY clause -- see error log for more details"
3548 buffer_free(group_buf);
3549 buffer_free(sql_buf);
3550 if (defaultselhash) jsonObjectFree(defaultselhash);
3552 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3553 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3554 MODULENAME, field );
3556 osrfAppSessionStatus(
3558 OSRF_STATUS_INTERNALSERVERERROR,
3559 "osrfMethodException",
3561 "Virtual field in ORDER BY clause -- see error log for more details"
3563 buffer_free( order_buf );
3565 buffer_free(group_buf);
3566 buffer_free(sql_buf);
3567 if (defaultselhash) jsonObjectFree(defaultselhash);
3571 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
3572 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
3573 if( ! transform_str ) {
3575 osrfAppSessionStatus(
3577 OSRF_STATUS_INTERNALSERVERERROR,
3578 "osrfMethodException",
3580 "Severe query error in ORDER BY clause -- see error log for more details"
3582 buffer_free( order_buf );
3584 buffer_free(group_buf);
3585 buffer_free(sql_buf);
3586 if (defaultselhash) jsonObjectFree(defaultselhash);
3590 OSRF_BUFFER_ADD( order_buf, transform_str );
3591 free( transform_str );
3594 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
3596 const char* direction =
3597 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
3599 if( direction[ 0 ] || 'D' == direction[ 0 ] )
3600 OSRF_BUFFER_ADD( order_buf, " DESC" );
3602 OSRF_BUFFER_ADD( order_buf, " ASC" );
3605 } else if( JSON_HASH == order_hash->type ) {
3606 // This hash is keyed on class alias. Each class has either
3607 // an array of field names or a hash keyed on field name.
3608 jsonIterator* class_itr = jsonNewIterator( order_hash );
3609 while ( (snode = jsonIteratorNext( class_itr )) ) {
3611 ClassInfo* order_class_info = search_alias( class_itr->key );
3612 if( ! order_class_info ) {
3613 osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
3614 MODULENAME, class_itr->key );
3616 osrfAppSessionStatus(
3618 OSRF_STATUS_INTERNALSERVERERROR,
3619 "osrfMethodException",
3621 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3623 jsonIteratorFree( class_itr );
3624 buffer_free( order_buf );
3626 buffer_free(group_buf);
3627 buffer_free(sql_buf);
3628 if (defaultselhash) jsonObjectFree(defaultselhash);
3632 osrfHash* field_list_def = order_class_info->fields;
3634 if ( snode->type == JSON_HASH ) {
3636 // Hash is keyed on field names from the current class. For each field
3637 // there is another layer of hash to define the sorting details, if any,
3638 // or a string to indicate direction of sorting.
3639 jsonIterator* order_itr = jsonNewIterator( snode );
3640 while ( (onode = jsonIteratorNext( order_itr )) ) {
3642 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
3644 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3645 MODULENAME, order_itr->key );
3647 osrfAppSessionStatus(
3649 OSRF_STATUS_INTERNALSERVERERROR,
3650 "osrfMethodException",
3652 "Invalid field in ORDER BY clause -- see error log for more details"
3654 jsonIteratorFree( order_itr );
3655 jsonIteratorFree( class_itr );
3656 buffer_free( order_buf );
3658 buffer_free(group_buf);
3659 buffer_free(sql_buf);
3660 if (defaultselhash) jsonObjectFree(defaultselhash);
3662 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3663 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3664 MODULENAME, order_itr->key );
3666 osrfAppSessionStatus(
3668 OSRF_STATUS_INTERNALSERVERERROR,
3669 "osrfMethodException",
3671 "Virtual field in ORDER BY clause -- see error log for more details"
3673 jsonIteratorFree( order_itr );
3674 jsonIteratorFree( class_itr );
3675 buffer_free( order_buf );
3677 buffer_free(group_buf);
3678 buffer_free(sql_buf);
3679 if (defaultselhash) jsonObjectFree(defaultselhash);
3683 const char* direction = NULL;
3684 if ( onode->type == JSON_HASH ) {
3685 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3686 string = searchFieldTransform(
3688 osrfHashGet( field_list_def, order_itr->key ),
3692 if( ctx ) osrfAppSessionStatus(
3694 OSRF_STATUS_INTERNALSERVERERROR,
3695 "osrfMethodException",
3697 "Severe query error in ORDER BY clause -- see error log for more details"
3699 jsonIteratorFree( order_itr );
3700 jsonIteratorFree( class_itr );
3702 buffer_free(group_buf);
3703 buffer_free(order_buf);
3704 buffer_free(sql_buf);
3705 if (defaultselhash) jsonObjectFree(defaultselhash);
3709 growing_buffer* field_buf = buffer_init(16);
3710 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3711 string = buffer_release(field_buf);
3714 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3715 const char* dir = jsonObjectGetString(tmp_const);
3716 if (!strncasecmp(dir, "d", 1)) {
3717 direction = " DESC";
3723 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
3724 osrfLogError( OSRF_LOG_MARK,
3725 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
3726 MODULENAME, json_type( onode->type ) );
3728 osrfAppSessionStatus(
3730 OSRF_STATUS_INTERNALSERVERERROR,
3731 "osrfMethodException",
3733 "Malformed ORDER BY clause -- see error log for more details"
3735 jsonIteratorFree( order_itr );
3736 jsonIteratorFree( class_itr );
3738 buffer_free(group_buf);
3739 buffer_free(order_buf);
3740 buffer_free(sql_buf);
3741 if (defaultselhash) jsonObjectFree(defaultselhash);
3745 string = strdup(order_itr->key);
3746 const char* dir = jsonObjectGetString(onode);
3747 if (!strncasecmp(dir, "d", 1)) {
3748 direction = " DESC";
3755 OSRF_BUFFER_ADD(order_buf, ", ");
3757 order_buf = buffer_init(128);
3759 OSRF_BUFFER_ADD(order_buf, string);
3763 OSRF_BUFFER_ADD(order_buf, direction);
3767 jsonIteratorFree(order_itr);
3769 } else if ( snode->type == JSON_ARRAY ) {
3771 // Array is a list of fields from the current class
3772 unsigned long order_idx = 0;
3773 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
3775 const char* _f = jsonObjectGetString( onode );
3777 osrfHash* field_def = osrfHashGet( field_list_def, _f );
3779 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3782 osrfAppSessionStatus(
3784 OSRF_STATUS_INTERNALSERVERERROR,
3785 "osrfMethodException",
3787 "Invalid field in ORDER BY clause -- see error log for more details"
3789 jsonIteratorFree( class_itr );
3790 buffer_free( order_buf );
3792 buffer_free(group_buf);
3793 buffer_free(sql_buf);
3794 if (defaultselhash) jsonObjectFree(defaultselhash);
3796 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3797 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3800 osrfAppSessionStatus(
3802 OSRF_STATUS_INTERNALSERVERERROR,
3803 "osrfMethodException",
3805 "Virtual field in ORDER BY clause -- see error log for more details"
3807 jsonIteratorFree( class_itr );
3808 buffer_free( order_buf );
3810 buffer_free(group_buf);
3811 buffer_free(sql_buf);
3812 if (defaultselhash) jsonObjectFree(defaultselhash);
3817 OSRF_BUFFER_ADD(order_buf, ", ");
3819 order_buf = buffer_init(128);
3821 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
3825 // IT'S THE OOOOOOOOOOOLD STYLE!
3827 osrfLogError(OSRF_LOG_MARK,
3828 "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3830 osrfAppSessionStatus(
3832 OSRF_STATUS_INTERNALSERVERERROR,
3833 "osrfMethodException",
3835 "Severe query error -- see error log for more details"
3840 buffer_free(group_buf);
3841 buffer_free(order_buf);
3842 buffer_free(sql_buf);
3843 if (defaultselhash) jsonObjectFree(defaultselhash);
3844 jsonIteratorFree(class_itr);
3848 jsonIteratorFree( class_itr );
3850 osrfLogError(OSRF_LOG_MARK,
3851 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
3852 MODULENAME, json_type( order_hash->type ) );
3854 osrfAppSessionStatus(
3856 OSRF_STATUS_INTERNALSERVERERROR,
3857 "osrfMethodException",
3859 "Malformed ORDER BY clause -- see error log for more details"
3861 buffer_free( order_buf );
3863 buffer_free(group_buf);
3864 buffer_free(sql_buf);
3865 if (defaultselhash) jsonObjectFree(defaultselhash);
3870 order_by_list = buffer_release( order_buf );
3874 string = buffer_release(group_buf);
3876 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
3877 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3878 OSRF_BUFFER_ADD( sql_buf, string );
3883 if( having_buf && *having_buf ) {
3884 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3885 OSRF_BUFFER_ADD( sql_buf, having_buf );
3889 if( order_by_list ) {
3891 if ( *order_by_list ) {
3892 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3893 OSRF_BUFFER_ADD( sql_buf, order_by_list );
3896 free( order_by_list );
3900 const char* str = jsonObjectGetString(limit);
3901 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
3905 const char* str = jsonObjectGetString(offset);
3906 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
3909 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3911 if (defaultselhash) jsonObjectFree(defaultselhash);
3913 return buffer_release(sql_buf);
3915 } // end of SELECT()
3917 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
3919 const char* locale = osrf_message_get_last_locale();
3921 osrfHash* fields = osrfHashGet(meta, "fields");
3922 char* core_class = osrfHashGet(meta, "classname");
3924 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
3926 jsonObject* node = NULL;
3927 jsonObject* snode = NULL;
3928 jsonObject* onode = NULL;
3929 const jsonObject* _tmp = NULL;
3930 jsonObject* selhash = NULL;
3931 jsonObject* defaultselhash = NULL;
3933 growing_buffer* sql_buf = buffer_init(128);
3934 growing_buffer* select_buf = buffer_init(128);
3936 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
3937 defaultselhash = jsonNewObjectType(JSON_HASH);
3938 selhash = defaultselhash;
3941 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
3942 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
3943 jsonObject* flist = jsonObjectGetKey( selhash, core_class );
3948 osrfStringArray* keys = osrfHashKeys( fields );
3949 while ( (field = osrfStringArrayGetString(keys, i++)) ) {
3950 if( ! str_is_true( osrfHashGet( osrfHashGet( fields, field ), "virtual" ) ) )
3951 jsonObjectPush( flist, jsonNewObject( field ) );
3953 osrfStringArrayFree(keys);
3957 jsonIterator* class_itr = jsonNewIterator( selhash );
3958 while ( (snode = jsonIteratorNext( class_itr )) ) {
3960 char* cname = class_itr->key;
3961 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
3962 if (!idlClass) continue;
3964 if (strcmp(core_class,class_itr->key)) {
3965 if (!join_hash) continue;
3967 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
3969 jsonObjectFree(found);
3973 jsonObjectFree(found);
3976 jsonIterator* select_itr = jsonNewIterator( snode );
3977 while ( (node = jsonIteratorNext( select_itr )) ) {
3978 const char* item_str = jsonObjectGetString( node );
3979 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
3980 char* fname = osrfHashGet(field, "name");
3982 if (!field) continue;
3987 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
3992 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
3993 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
3996 i18n = osrfHashGet(field, "i18n");
3998 if( str_is_true( i18n ) ) {
3999 char* pkey = osrfHashGet(idlClass, "primarykey");
4000 char* tname = osrfHashGet(idlClass, "tablename");
4002 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);
4004 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4007 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4011 jsonIteratorFree(select_itr);
4014 jsonIteratorFree(class_itr);
4016 char* col_list = buffer_release(select_buf);
4017 char* table = getSourceDefinition(meta);
4019 table = strdup( "(null)" );
4021 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4025 // Clear the query stack (as a fail-safe precaution against possible
4026 // leftover garbage); then push the first query frame onto the stack.
4027 clear_query_stack();
4029 if( add_query_core( NULL, core_class ) ) {
4031 osrfAppSessionStatus(
4033 OSRF_STATUS_INTERNALSERVERERROR,
4034 "osrfMethodException",
4036 "Unable to build query frame for core class"
4042 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4043 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
4044 OSRF_BUFFER_ADD(sql_buf, join_clause);
4048 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4049 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
4051 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
4053 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4055 osrfAppSessionStatus(
4057 OSRF_STATUS_INTERNALSERVERERROR,
4058 "osrfMethodException",
4060 "Severe query error -- see error log for more details"
4062 buffer_free(sql_buf);
4063 if(defaultselhash) jsonObjectFree(defaultselhash);
4064 clear_query_stack();
4067 buffer_add(sql_buf, pred);
4072 char* string = NULL;
4073 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4075 growing_buffer* order_buf = buffer_init(128);
4078 jsonIterator* class_itr = jsonNewIterator( _tmp );
4079 while ( (snode = jsonIteratorNext( class_itr )) ) {
4081 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4084 if ( snode->type == JSON_HASH ) {
4086 jsonIterator* order_itr = jsonNewIterator( snode );
4087 while ( (onode = jsonIteratorNext( order_itr )) ) {
4089 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4090 class_itr->key, order_itr->key );
4094 char* direction = NULL;
4095 if ( onode->type == JSON_HASH ) {
4096 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4097 string = searchFieldTransform( class_itr->key, field_def, onode );
4099 osrfAppSessionStatus(
4101 OSRF_STATUS_INTERNALSERVERERROR,
4102 "osrfMethodException",
4104 "Severe query error in ORDER BY clause -- see error log for more details"
4106 jsonIteratorFree( order_itr );
4107 jsonIteratorFree( class_itr );
4108 buffer_free( order_buf );
4109 buffer_free( sql_buf );
4110 if( defaultselhash ) jsonObjectFree( defaultselhash );
4111 clear_query_stack();
4115 growing_buffer* field_buf = buffer_init(16);
4116 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4117 string = buffer_release(field_buf);
4120 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4121 const char* dir = jsonObjectGetString(_tmp);
4122 if (!strncasecmp(dir, "d", 1)) {
4123 direction = " DESC";
4130 string = strdup(order_itr->key);
4131 const char* dir = jsonObjectGetString(onode);
4132 if (!strncasecmp(dir, "d", 1)) {
4133 direction = " DESC";
4142 buffer_add(order_buf, ", ");
4145 buffer_add(order_buf, string);
4149 buffer_add(order_buf, direction);
4154 jsonIteratorFree(order_itr);
4157 const char* str = jsonObjectGetString(snode);
4158 buffer_add(order_buf, str);
4164 jsonIteratorFree(class_itr);
4166 string = buffer_release(order_buf);
4169 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4170 OSRF_BUFFER_ADD( sql_buf, string );
4176 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4177 const char* str = jsonObjectGetString(_tmp);
4185 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4187 const char* str = jsonObjectGetString(_tmp);
4196 if (defaultselhash) jsonObjectFree(defaultselhash);
4197 clear_query_stack();
4199 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4200 return buffer_release(sql_buf);
4203 int doJSONSearch ( osrfMethodContext* ctx ) {
4204 if(osrfMethodVerifyContext( ctx )) {
4205 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4209 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
4214 dbhandle = writehandle;
4216 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4220 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4221 flags |= SELECT_DISTINCT;
4223 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4224 flags |= DISABLE_I18N;
4226 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4229 jsonObjectGetKey( hash, "select" ),
4230 jsonObjectGetKey( hash, "from" ),
4231 jsonObjectGetKey( hash, "where" ),
4232 jsonObjectGetKey( hash, "having" ),
4233 jsonObjectGetKey( hash, "order_by" ),
4234 jsonObjectGetKey( hash, "limit" ),
4235 jsonObjectGetKey( hash, "offset" ),
4238 clear_query_stack();
4245 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4246 dbi_result result = dbi_conn_query(dbhandle, sql);
4249 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4251 if (dbi_result_first_row(result)) {
4252 /* JSONify the result */
4253 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4256 jsonObject* return_val = oilsMakeJSONFromResult( result );
4257 osrfAppRespond( ctx, return_val );
4258 jsonObjectFree( return_val );
4259 } while (dbi_result_next_row(result));
4262 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4265 osrfAppRespondComplete( ctx, NULL );
4267 /* clean up the query */
4268 dbi_result_free(result);
4272 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4273 osrfAppSessionStatus(
4275 OSRF_STATUS_INTERNALSERVERERROR,
4276 "osrfMethodException",
4278 "Severe query error -- see error log for more details"
4286 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4287 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4290 dbhandle = writehandle;
4292 osrfHash* links = osrfHashGet(meta, "links");
4293 osrfHash* fields = osrfHashGet(meta, "fields");
4294 char* core_class = osrfHashGet(meta, "classname");
4295 char* pkey = osrfHashGet(meta, "primarykey");
4297 const jsonObject* _tmp;
4300 char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4302 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4307 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4309 dbi_result result = dbi_conn_query(dbhandle, sql);
4310 if( NULL == result ) {
4311 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4312 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4313 osrfAppSessionStatus(
4315 OSRF_STATUS_INTERNALSERVERERROR,
4316 "osrfMethodException",
4318 "Severe query error -- see error log for more details"
4325 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4328 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
4329 osrfHash* dedup = osrfNewHash();
4331 if (dbi_result_first_row(result)) {
4332 /* JSONify the result */
4333 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4335 obj = oilsMakeFieldmapperFromResult( result, meta );
4336 char* pkey_val = oilsFMGetString( obj, pkey );
4337 if ( osrfHashGet( dedup, pkey_val ) ) {
4338 jsonObjectFree(obj);
4341 osrfHashSet( dedup, pkey_val, pkey_val );
4342 jsonObjectPush(res_list, obj);
4344 } while (dbi_result_next_row(result));
4346 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
4350 osrfHashFree(dedup);
4351 /* clean up the query */
4352 dbi_result_free(result);
4355 if (res_list->size && query_hash) {
4356 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
4358 int x = (int)jsonObjectGetNumber(_tmp);
4359 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
4361 const jsonObject* temp_blob;
4362 if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
4364 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
4365 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
4367 osrfStringArray* link_fields = NULL;
4370 if (flesh_fields->size == 1) {
4371 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
4372 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
4377 link_fields = osrfNewStringArray(1);
4378 jsonIterator* _i = jsonNewIterator( flesh_fields );
4379 while ((_f = jsonIteratorNext( _i ))) {
4380 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4382 jsonIteratorFree(_i);
4387 unsigned long res_idx = 0;
4388 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
4393 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4395 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4397 osrfHash* kid_link = osrfHashGet(links, link_field);
4398 if (!kid_link) continue;
4400 osrfHash* field = osrfHashGet(fields, link_field);
4401 if (!field) continue;
4403 osrfHash* value_field = field;
4405 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4406 if (!kid_idl) continue;
4408 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4409 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4412 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4413 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4416 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4418 if (link_map->size > 0) {
4419 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4422 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4427 osrfHashGet(kid_link, "class"),
4434 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4435 osrfHashGet(kid_link, "field"),
4436 osrfHashGet(kid_link, "class"),
4437 osrfHashGet(kid_link, "key"),
4438 osrfHashGet(kid_link, "reltype")
4441 const char* search_key = jsonObjectGetString(
4444 atoi( osrfHashGet(value_field, "array_position") )
4449 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4453 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
4455 // construct WHERE clause
4456 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
4459 osrfHashGet(kid_link, "key"),
4460 jsonNewObject( search_key )
4463 // construct the rest of the query
4464 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
4465 jsonObjectSetKey( rest_of_query, "flesh",
4466 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4470 jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
4472 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
4473 jsonObjectSetKey( rest_of_query, "order_by",
4474 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
4478 if (jsonObjectGetKeyConst(query_hash, "select")) {
4479 jsonObjectSetKey( rest_of_query, "select",
4480 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
4484 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
4485 where_clause, rest_of_query, err);
4487 jsonObjectFree( where_clause );
4488 jsonObjectFree( rest_of_query );
4491 osrfStringArrayFree(link_fields);
4492 jsonObjectFree(res_list);
4493 jsonObjectFree(flesh_blob);
4497 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4499 jsonObject* X = NULL;
4500 if ( link_map->size > 0 && kids->size > 0 ) {
4502 kids = jsonNewObjectType(JSON_ARRAY);
4504 jsonObject* _k_node;
4505 unsigned long res_idx = 0;
4506 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
4512 (unsigned long)atoi(
4518 osrfHashGet(kid_link, "class")
4522 osrfStringArrayGetString( link_map, 0 )
4530 } // end while loop traversing X
4533 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4534 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4537 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4538 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4542 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4543 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4546 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4547 jsonObjectClone( kids )
4552 jsonObjectFree(kids);
4556 jsonObjectFree( kids );
4558 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4559 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4562 } // end while loop traversing res_list
4563 jsonObjectFree( flesh_blob );
4564 osrfStringArrayFree(link_fields);
4573 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
4575 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4577 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
4579 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
4582 if (!verifyObjectClass(ctx, target)) {
4587 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4588 osrfAppSessionStatus(
4590 OSRF_STATUS_BADREQUEST,
4591 "osrfMethodException",
4593 "No active transaction -- required for UPDATE"
4599 // The following test is harmless but redundant. If a class is
4600 // readonly, we don't register an update method for it.
4601 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4602 osrfAppSessionStatus(
4604 OSRF_STATUS_BADREQUEST,
4605 "osrfMethodException",
4607 "Cannot UPDATE readonly class"
4613 dbhandle = writehandle;
4615 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4617 // Set the last_xact_id
4618 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4620 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
4621 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4624 char* pkey = osrfHashGet(meta, "primarykey");
4625 osrfHash* fields = osrfHashGet(meta, "fields");
4627 char* id = oilsFMGetString( target, pkey );
4631 "%s updating %s object with %s = %s",
4633 osrfHashGet(meta, "fieldmapper"),
4638 growing_buffer* sql = buffer_init(128);
4639 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4644 osrfStringArray* field_list = osrfHashKeys( fields );
4645 while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
4647 osrfHash* field = osrfHashGet( fields, field_name );
4649 if(!( strcmp( field_name, pkey ) )) continue;
4650 if( str_is_true( osrfHashGet(osrfHashGet(fields,field_name), "virtual") ) )
4653 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4655 int value_is_numeric = 0; // boolean
4657 if (field_object && field_object->classname) {
4658 value = oilsFMGetString(
4660 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4663 value = jsonObjectToSimpleString( field_object );
4664 if( field_object && JSON_NUMBER == field_object->type )
4665 value_is_numeric = 1;
4668 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s", osrfHashGet(meta, "fieldmapper"), field_name, value);
4670 if (!field_object || field_object->type == JSON_NULL) {
4671 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) ) && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
4672 if (first) first = 0;
4673 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4674 buffer_fadd( sql, " %s = NULL", field_name );
4677 } else if ( value_is_numeric || !strcmp( get_primitive( field ), "number") ) {
4678 if (first) first = 0;
4679 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4681 const char* numtype = get_datatype( field );
4682 if ( !strncmp( numtype, "INT", 3 ) ) {
4683 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
4684 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
4685 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
4687 // Must really be intended as a string, so quote it
4688 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4689 buffer_fadd( sql, " %s = %s", field_name, value );
4691 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4692 osrfAppSessionStatus(
4694 OSRF_STATUS_INTERNALSERVERERROR,
4695 "osrfMethodException",
4697 "Error quoting string -- please see the error log for more details"
4707 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
4710 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4711 if (first) first = 0;
4712 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4713 buffer_fadd( sql, " %s = %s", field_name, value );
4716 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4717 osrfAppSessionStatus(
4719 OSRF_STATUS_INTERNALSERVERERROR,
4720 "osrfMethodException",
4722 "Error quoting string -- please see the error log for more details"
4736 jsonObject* obj = jsonNewObject(id);
4738 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4739 dbi_conn_quote_string(dbhandle, &id);
4741 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
4743 char* query = buffer_release(sql);
4744 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
4746 dbi_result result = dbi_conn_query(dbhandle, query);
4750 jsonObjectFree(obj);
4751 obj = jsonNewObject(NULL);
4754 "%s ERROR updating %s object with %s = %s",
4756 osrfHashGet(meta, "fieldmapper"),
4767 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4769 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4771 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4772 osrfAppSessionStatus(
4774 OSRF_STATUS_BADREQUEST,
4775 "osrfMethodException",
4777 "No active transaction -- required for DELETE"
4783 // The following test is harmless but redundant. If a class is
4784 // readonly, we don't register a delete method for it.
4785 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4786 osrfAppSessionStatus(
4788 OSRF_STATUS_BADREQUEST,
4789 "osrfMethodException",
4791 "Cannot DELETE readonly class"
4797 dbhandle = writehandle;
4801 char* pkey = osrfHashGet(meta, "primarykey");
4809 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4810 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4815 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4818 if (!verifyObjectPCRUD( ctx, NULL )) {
4823 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4828 "%s deleting %s object with %s = %s",
4830 osrfHashGet(meta, "fieldmapper"),
4835 obj = jsonNewObject(id);
4837 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4838 dbi_conn_quote_string(writehandle, &id);
4840 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4843 jsonObjectFree(obj);
4844 obj = jsonNewObject(NULL);
4847 "%s ERROR deleting %s object with %s = %s",
4849 osrfHashGet(meta, "fieldmapper"),
4862 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4863 if(!(result && meta)) return jsonNULL;
4865 jsonObject* object = jsonNewObject(NULL);
4866 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4868 osrfHash* fields = osrfHashGet(meta, "fields");
4870 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4874 char dt_string[256];
4878 int columnIndex = 1;
4880 unsigned short type;
4881 const char* columnName;
4883 /* cycle through the column list */
4884 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
4886 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4888 fmIndex = -1; // reset the position
4890 /* determine the field type and storage attributes */
4891 type = dbi_result_get_field_type_idx(result, columnIndex);
4892 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
4894 /* fetch the fieldmapper index */
4895 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4897 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4900 const char* pos = (char*)osrfHashGet(_f, "array_position");
4901 if ( !pos ) continue;
4903 fmIndex = atoi( pos );
4904 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
4909 if (dbi_result_field_is_null_idx(result, columnIndex)) {
4910 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
4915 case DBI_TYPE_INTEGER :
4917 if( attr & DBI_INTEGER_SIZE8 )
4918 jsonObjectSetIndex( object, fmIndex,
4919 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
4921 jsonObjectSetIndex( object, fmIndex,
4922 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
4926 case DBI_TYPE_DECIMAL :
4927 jsonObjectSetIndex( object, fmIndex,
4928 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
4931 case DBI_TYPE_STRING :
4937 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
4942 case DBI_TYPE_DATETIME :
4944 memset(dt_string, '\0', sizeof(dt_string));
4945 memset(&gmdt, '\0', sizeof(gmdt));
4947 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
4950 if (!(attr & DBI_DATETIME_DATE)) {
4951 gmtime_r( &_tmp_dt, &gmdt );
4952 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4953 } else if (!(attr & DBI_DATETIME_TIME)) {
4954 localtime_r( &_tmp_dt, &gmdt );
4955 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4957 localtime_r( &_tmp_dt, &gmdt );
4958 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4961 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
4965 case DBI_TYPE_BINARY :
4966 osrfLogError( OSRF_LOG_MARK,
4967 "Can't do binary at column %s : index %d", columnName, columnIndex);
4976 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
4977 if(!result) return jsonNULL;
4979 jsonObject* object = jsonNewObject(NULL);
4982 char dt_string[256];
4986 int columnIndex = 1;
4988 unsigned short type;
4989 const char* columnName;
4991 /* cycle through the column list */
4992 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
4994 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4996 fmIndex = -1; // reset the position
4998 /* determine the field type and storage attributes */
4999 type = dbi_result_get_field_type_idx(result, columnIndex);
5000 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5002 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5003 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
5008 case DBI_TYPE_INTEGER :
5010 if( attr & DBI_INTEGER_SIZE8 )
5011 jsonObjectSetKey( object, columnName,
5012 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)) );
5014 jsonObjectSetKey( object, columnName,
5015 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
5018 case DBI_TYPE_DECIMAL :
5019 jsonObjectSetKey( object, columnName,
5020 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
5023 case DBI_TYPE_STRING :
5024 jsonObjectSetKey( object, columnName,
5025 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
5028 case DBI_TYPE_DATETIME :
5030 memset(dt_string, '\0', sizeof(dt_string));
5031 memset(&gmdt, '\0', sizeof(gmdt));
5033 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5036 if (!(attr & DBI_DATETIME_DATE)) {
5037 gmtime_r( &_tmp_dt, &gmdt );
5038 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5039 } else if (!(attr & DBI_DATETIME_TIME)) {
5040 localtime_r( &_tmp_dt, &gmdt );
5041 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5043 localtime_r( &_tmp_dt, &gmdt );
5044 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5047 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
5050 case DBI_TYPE_BINARY :
5051 osrfLogError( OSRF_LOG_MARK,
5052 "Can't do binary at column %s : index %d", columnName, columnIndex );
5056 } // end while loop traversing result
5061 // Interpret a string as true or false
5062 static int str_is_true( const char* str ) {
5063 if( NULL == str || strcasecmp( str, "true" ) )
5069 // Interpret a jsonObject as true or false
5070 static int obj_is_true( const jsonObject* obj ) {
5073 else switch( obj->type )
5081 if( strcasecmp( obj->value.s, "true" ) )
5085 case JSON_NUMBER : // Support 1/0 for perl's sake
5086 if( jsonObjectGetNumber( obj ) == 1.0 )
5095 // Translate a numeric code into a text string identifying a type of
5096 // jsonObject. To be used for building error messages.
5097 static const char* json_type( int code ) {
5103 return "JSON_ARRAY";
5105 return "JSON_STRING";
5107 return "JSON_NUMBER";
5113 return "(unrecognized)";
5117 // Extract the "primitive" attribute from an IDL field definition.
5118 // If we haven't initialized the app, then we must be running in
5119 // some kind of testbed. In that case, default to "string".
5120 static const char* get_primitive( osrfHash* field ) {
5121 const char* s = osrfHashGet( field, "primitive" );
5123 if( child_initialized )
5126 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5128 osrfHashGet( field, "name" )
5136 // Extract the "datatype" attribute from an IDL field definition.
5137 // If we haven't initialized the app, then we must be running in
5138 // some kind of testbed. In that case, default to to NUMERIC,
5139 // since we look at the datatype only for numbers.
5140 static const char* get_datatype( osrfHash* field ) {
5141 const char* s = osrfHashGet( field, "datatype" );
5143 if( child_initialized )
5146 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5148 osrfHashGet( field, "name" )
5157 If the input string is potentially a valid SQL identifier, return 1.
5160 Purpose: to prevent certain kinds of SQL injection. To that end we
5161 don't necessarily need to follow all the rules exactly, such as requiring
5162 that the first character not be a digit.
5164 We allow leading and trailing white space. In between, we do not allow
5165 punctuation (except for underscores and dollar signs), control
5166 characters, or embedded white space.
5168 More pedantically we should allow quoted identifiers containing arbitrary
5169 characters, but for the foreseeable future such quoted identifiers are not
5170 likely to be an issue.
5172 static int is_identifier( const char* s) {
5176 // Skip leading white space
5177 while( isspace( (unsigned char) *s ) )
5181 return 0; // Nothing but white space? Not okay.
5183 // Check each character until we reach white space or
5184 // end-of-string. Letters, digits, underscores, and
5185 // dollar signs are okay. With the exception of periods
5186 // (as in schema.identifier), control characters and other
5187 // punctuation characters are not okay. Anything else
5188 // is okay -- it could for example be part of a multibyte
5189 // UTF8 character such as a letter with diacritical marks,
5190 // and those are allowed.
5192 if( isalnum( (unsigned char) *s )
5196 ; // Fine; keep going
5197 else if( ispunct( (unsigned char) *s )
5198 || iscntrl( (unsigned char) *s ) )
5201 } while( *s && ! isspace( (unsigned char) *s ) );
5203 // If we found any white space in the above loop,
5204 // the rest had better be all white space.
5206 while( isspace( (unsigned char) *s ) )
5210 return 0; // White space was embedded within non-white space
5216 Determine whether to accept a character string as a comparison operator.
5217 Return 1 if it's good, or 0 if it's bad.
5219 We don't validate it for real. We just make sure that it doesn't contain
5220 any semicolons or white space (with a special exception for the
5221 "SIMILAR TO" operator). The idea is to block certain kinds of SQL
5222 injection. If it has no semicolons or white space but it's still not a
5223 valid operator, then the database will complain.
5225 Another approach would be to compare the string against a short list of
5226 approved operators. We don't do that because we want to allow custom
5227 operators like ">100*", which would be difficult or impossible to
5228 express otherwise in a JSON query.
5230 static int is_good_operator( const char* op ) {
5231 if( !op ) return 0; // Sanity check
5235 if( isspace( (unsigned char) *s ) ) {
5236 // Special exception for SIMILAR TO. Someday we might make
5237 // exceptions for IS DISTINCT FROM and IS NOT DISTINCT FROM.
5238 if( !strcasecmp( op, "similar to" ) )
5243 else if( ';' == *s )
5250 /* ----------------------------------------------------------------------------------
5251 The following machinery supports a stack of query frames for use by SELECT().
5253 A query frame caches information about one level of a SELECT query. When we enter
5254 a subquery, we push another query frame onto the stack, and pop it off when we leave.
5256 The query frame stores information about the core class, and about any joined classes
5259 The main purpose is to map table aliases to classes and tables, so that a query can
5260 join to the same table more than once. A secondary goal is to reduce the number of
5261 lookups in the IDL by caching the results.
5262 ----------------------------------------------------------------------------------*/
5264 #define STATIC_CLASS_INFO_COUNT 3
5266 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
5268 /* ---------------------------------------------------------------------------
5269 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5271 ---------------------------------------------------------------------------*/
5272 static ClassInfo* allocate_class_info( void ) {
5273 // In order to reduce the number of mallocs and frees, we return a static
5274 // instance of ClassInfo, if we can find one that we're not already using.
5275 // We rely on the fact that the compiler will implicitly initialize the
5276 // static instances so that in_use == 0.
5279 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5280 if( ! static_class_info[ i ].in_use ) {
5281 static_class_info[ i ].in_use = 1;
5282 return static_class_info + i;
5286 // The static ones are all in use. Malloc one.
5288 return safe_malloc( sizeof( ClassInfo ) );
5291 /* --------------------------------------------------------------------------
5292 Free any malloc'd memory owned by a ClassInfo; return it to a pristine state
5293 ---------------------------------------------------------------------------*/
5294 static void clear_class_info( ClassInfo* info ) {
5299 // Free any malloc'd strings
5301 if( info->alias != info->alias_store )
5302 free( info->alias );
5304 if( info->class_name != info->class_name_store )
5305 free( info->class_name );
5307 free( info->source_def );
5309 info->alias = info->class_name = info->source_def = NULL;
5313 /* --------------------------------------------------------------------------
5314 Deallocate a ClassInfo and everything it owns
5315 ---------------------------------------------------------------------------*/
5316 static void free_class_info( ClassInfo* info ) {
5321 clear_class_info( info );
5323 // If it's one of the static instances, just mark it as not in use
5326 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5327 if( info == static_class_info + i ) {
5328 static_class_info[ i ].in_use = 0;
5333 // Otherwise it must have been malloc'd, so free it
5338 /* --------------------------------------------------------------------------
5339 Populate an already-allocated ClassInfo. Return 0 if successful, 1 if not.
5340 ---------------------------------------------------------------------------*/
5341 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
5344 osrfLogError( OSRF_LOG_MARK,
5345 "%s ERROR: No ClassInfo available to populate", MODULENAME );
5346 info->alias = info->class_name = info->source_def = NULL;
5347 info->class_def = info->fields = info->links = NULL;
5352 osrfLogError( OSRF_LOG_MARK,
5353 "%s ERROR: No class name provided for lookup", MODULENAME );
5354 info->alias = info->class_name = info->source_def = NULL;
5355 info->class_def = info->fields = info->links = NULL;
5359 // Alias defaults to class name if not supplied
5360 if( ! alias || ! alias[ 0 ] )
5363 // Look up class info in the IDL
5364 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
5366 osrfLogError( OSRF_LOG_MARK,
5367 "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
5368 info->alias = info->class_name = info->source_def = NULL;
5369 info->class_def = info->fields = info->links = NULL;
5371 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
5372 osrfLogError( OSRF_LOG_MARK,
5373 "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
5374 info->alias = info->class_name = info->source_def = NULL;
5375 info->class_def = info->fields = info->links = NULL;
5379 osrfHash* links = osrfHashGet( class_def, "links" );
5381 osrfLogError( OSRF_LOG_MARK,
5382 "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
5383 info->alias = info->class_name = info->source_def = NULL;
5384 info->class_def = info->fields = info->links = NULL;
5388 osrfHash* fields = osrfHashGet( class_def, "fields" );
5390 osrfLogError( OSRF_LOG_MARK,
5391 "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
5392 info->alias = info->class_name = info->source_def = NULL;
5393 info->class_def = info->fields = info->links = NULL;
5397 char* source_def = getSourceDefinition( class_def );
5401 // We got everything we need, so populate the ClassInfo
5402 if( strlen( alias ) > ALIAS_STORE_SIZE )
5403 info->alias = strdup( alias );
5405 strcpy( info->alias_store, alias );
5406 info->alias = info->alias_store;
5409 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
5410 info->class_name = strdup( class );
5412 strcpy( info->class_name_store, class );
5413 info->class_name = info->class_name_store;
5416 info->source_def = source_def;
5418 info->class_def = class_def;
5419 info->links = links;
5420 info->fields = fields;
5425 #define STATIC_FRAME_COUNT 3
5427 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
5429 /* ---------------------------------------------------------------------------
5430 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5432 ---------------------------------------------------------------------------*/
5433 static QueryFrame* allocate_frame( void ) {
5434 // In order to reduce the number of mallocs and frees, we return a static
5435 // instance of QueryFrame, if we can find one that we're not already using.
5436 // We rely on the fact that the compiler will implicitly initialize the
5437 // static instances so that in_use == 0.
5440 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5441 if( ! static_frame[ i ].in_use ) {
5442 static_frame[ i ].in_use = 1;
5443 return static_frame + i;
5447 // The static ones are all in use. Malloc one.
5449 return safe_malloc( sizeof( QueryFrame ) );
5452 /* --------------------------------------------------------------------------
5453 Free a QueryFrame, and all the memory it owns.
5454 ---------------------------------------------------------------------------*/
5455 static void free_query_frame( QueryFrame* frame ) {
5460 clear_class_info( &frame->core );
5462 // Free the join list
5464 ClassInfo* info = frame->join_list;
5467 free_class_info( info );
5471 frame->join_list = NULL;
5474 // If the frame is a static instance, just mark it as unused
5476 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5477 if( frame == static_frame + i ) {
5478 static_frame[ i ].in_use = 0;
5483 // Otherwise it must have been malloc'd, so free it
5488 /* --------------------------------------------------------------------------
5489 Search a given QueryFrame for a specified alias. If you find it, return
5490 a pointer to the corresponding ClassInfo. Otherwise return NULL.
5491 ---------------------------------------------------------------------------*/
5492 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
5493 if( ! frame || ! target ) {
5497 ClassInfo* found_class = NULL;
5499 if( !strcmp( target, frame->core.alias ) )
5500 return &(frame->core);
5502 ClassInfo* curr_class = frame->join_list;
5503 while( curr_class ) {
5504 if( strcmp( target, curr_class->alias ) )
5505 curr_class = curr_class->next;
5507 found_class = curr_class;
5516 /* --------------------------------------------------------------------------
5517 Push a new (blank) QueryFrame onto the stack.
5518 ---------------------------------------------------------------------------*/
5519 static void push_query_frame( void ) {
5520 QueryFrame* frame = allocate_frame();
5521 frame->join_list = NULL;
5522 frame->next = curr_query;
5524 // Initialize the ClassInfo for the core class
5525 ClassInfo* core = &frame->core;
5526 core->alias = core->class_name = core->source_def = NULL;
5527 core->class_def = core->fields = core->links = NULL;
5532 /* --------------------------------------------------------------------------
5533 Pop a QueryFrame off the stack and destroy it
5534 ---------------------------------------------------------------------------*/
5535 static void pop_query_frame( void ) {
5540 QueryFrame* popped = curr_query;
5541 curr_query = popped->next;
5543 free_query_frame( popped );
5546 /* --------------------------------------------------------------------------
5547 Populate the ClassInfo for the core class. Return 0 if successful, 1 if not.
5548 ---------------------------------------------------------------------------*/
5549 static int add_query_core( const char* alias, const char* class_name ) {
5552 if( ! curr_query ) {
5553 osrfLogError( OSRF_LOG_MARK,
5554 "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
5556 } else if( curr_query->core.alias ) {
5557 osrfLogError( OSRF_LOG_MARK,
5558 "%s ERROR: Core class %s already populated as %s",
5559 MODULENAME, curr_query->core.class_name, curr_query->core.alias );
5563 build_class_info( &curr_query->core, alias, class_name );
5564 if( curr_query->core.alias )
5567 osrfLogError( OSRF_LOG_MARK,
5568 "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
5573 /* --------------------------------------------------------------------------
5574 Search the current QueryFrame for a specified alias. If you find it,
5575 return a pointer to the corresponding ClassInfo. Otherwise return NULL.
5576 ---------------------------------------------------------------------------*/
5577 static ClassInfo* search_alias( const char* target ) {
5578 return search_alias_in_frame( curr_query, target );
5581 /* --------------------------------------------------------------------------
5582 Search all levels of query for a specified alias, starting with the
5583 current query. If you find it, return a pointer to the corresponding
5584 ClassInfo. Otherwise return NULL.
5585 ---------------------------------------------------------------------------*/
5586 static ClassInfo* search_all_alias( const char* target ) {
5587 ClassInfo* found_class = NULL;
5588 QueryFrame* curr_frame = curr_query;
5590 while( curr_frame ) {
5591 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
5594 curr_frame = curr_frame->next;
5600 /* --------------------------------------------------------------------------
5601 Add a class to the list of classes joined to the current query.
5602 ---------------------------------------------------------------------------*/
5603 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
5605 if( ! classname || ! *classname ) { // sanity check
5606 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
5613 const ClassInfo* conflict = search_alias( alias );
5615 osrfLogError( OSRF_LOG_MARK,
5616 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
5617 MODULENAME, alias, conflict->class_name );
5621 ClassInfo* info = allocate_class_info();
5623 if( build_class_info( info, alias, classname ) ) {
5624 free_class_info( info );
5628 // Add the new ClassInfo to the join list of the current QueryFrame
5629 info->next = curr_query->join_list;
5630 curr_query->join_list = info;
5635 /* --------------------------------------------------------------------------
5636 Destroy all nodes on the query stack.
5637 ---------------------------------------------------------------------------*/
5638 static void clear_query_stack( void ) {