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 int osrfAppChildInit();
33 int osrfAppInitialize();
34 void osrfAppChildExit();
36 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
38 int beginTransaction ( osrfMethodContext* );
39 int commitTransaction ( osrfMethodContext* );
40 int rollbackTransaction ( osrfMethodContext* );
42 int setSavepoint ( osrfMethodContext* );
43 int releaseSavepoint ( osrfMethodContext* );
44 int rollbackSavepoint ( osrfMethodContext* );
46 int doJSONSearch ( osrfMethodContext* );
48 int dispatchCRUDMethod ( osrfMethodContext* );
49 static jsonObject* doCreate ( osrfMethodContext*, int* );
50 static jsonObject* doRetrieve ( osrfMethodContext*, int* );
51 static jsonObject* doUpdate ( osrfMethodContext*, int* );
52 static jsonObject* doDelete ( osrfMethodContext*, int* );
53 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
54 jsonObject* where_hash, jsonObject* query_hash, int* err );
55 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
56 static jsonObject* oilsMakeJSONFromResult( dbi_result );
58 static char* searchSimplePredicate ( const char* op, const char* class,
59 osrfHash* field, const jsonObject* node );
60 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
61 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject*);
62 static char* searchFieldTransformPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
63 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
64 static char* searchINPredicate ( const char*, osrfHash*,
65 jsonObject*, const char*, osrfMethodContext* );
66 static char* searchPredicate ( const char*, osrfHash*, jsonObject*, osrfMethodContext* );
67 static char* searchJOIN ( const jsonObject*, osrfHash* );
68 static char* searchWHERE ( const jsonObject*, osrfHash*, int, osrfMethodContext* );
69 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
71 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
73 void userDataFree( void* );
74 static void sessionDataFree( char*, void* );
75 static char* getSourceDefinition( osrfHash* );
76 static int str_is_true( const char* str );
77 static int obj_is_true( const jsonObject* obj );
78 static const char* json_type( int code );
79 static const char* get_primitive( osrfHash* field );
80 static const char* get_datatype( osrfHash* field );
81 static int is_identifier( const char* s);
82 static int is_good_operator( const char* op );
85 static jsonObject* verifyUserPCRUD( osrfMethodContext* );
86 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
87 static char* org_tree_root( osrfMethodContext* ctx );
88 static jsonObject* single_hash( const char* key, const char* value );
91 static int child_initialized = 0; /* boolean */
93 static dbi_conn writehandle; /* our MASTER db connection */
94 static dbi_conn dbhandle; /* our CURRENT db connection */
95 //static osrfHash * readHandles;
96 static jsonObject* const jsonNULL = NULL; //
97 static int max_flesh_depth = 100;
99 /* called when this process is about to exit */
100 void osrfAppChildExit() {
101 osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
104 if (writehandle == dbhandle) same = 1;
106 dbi_conn_query(writehandle, "ROLLBACK;");
107 dbi_conn_close(writehandle);
110 if (dbhandle && !same)
111 dbi_conn_close(dbhandle);
113 // XXX add cleanup of readHandles whenever that gets used
118 int osrfAppInitialize() {
120 osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
121 osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
123 if (!oilsIDLInit( osrf_settings_host_value("/IDL") )) return 1; /* return non-zero to indicate error */
125 growing_buffer* method_name = buffer_init(64);
127 // Generic search thingy
128 buffer_add(method_name, MODULENAME);
129 buffer_add(method_name, ".json_query");
130 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
131 "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
134 // first we register all the transaction and savepoint methods
135 buffer_reset(method_name);
136 OSRF_BUFFER_ADD(method_name, MODULENAME);
137 OSRF_BUFFER_ADD(method_name, ".transaction.begin");
138 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
139 "beginTransaction", "", 0, 0 );
141 buffer_reset(method_name);
142 OSRF_BUFFER_ADD(method_name, MODULENAME);
143 OSRF_BUFFER_ADD(method_name, ".transaction.commit");
144 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
145 "commitTransaction", "", 0, 0 );
147 buffer_reset(method_name);
148 OSRF_BUFFER_ADD(method_name, MODULENAME);
149 OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
150 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
151 "rollbackTransaction", "", 0, 0 );
153 buffer_reset(method_name);
154 OSRF_BUFFER_ADD(method_name, MODULENAME);
155 OSRF_BUFFER_ADD(method_name, ".savepoint.set");
156 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
157 "setSavepoint", "", 1, 0 );
159 buffer_reset(method_name);
160 OSRF_BUFFER_ADD(method_name, MODULENAME);
161 OSRF_BUFFER_ADD(method_name, ".savepoint.release");
162 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
163 "releaseSavepoint", "", 1, 0 );
165 buffer_reset(method_name);
166 OSRF_BUFFER_ADD(method_name, MODULENAME);
167 OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
168 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
169 "rollbackSavepoint", "", 1, 0 );
171 static const char* global_method[] = {
179 const int global_method_count
180 = sizeof( global_method ) / sizeof ( global_method[0] );
184 osrfStringArray* classes = osrfHashKeys( oilsIDL() );
185 osrfLogDebug(OSRF_LOG_MARK, "%d classes loaded", classes->size );
186 osrfLogDebug(OSRF_LOG_MARK,
187 "At most %d methods will be generated", classes->size * global_method_count);
189 // For each class in IDL...
190 while ( (classname = osrfStringArrayGetString(classes, c_index++)) ) {
191 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
193 osrfHash* idlClass = osrfHashGet(oilsIDL(), classname);
195 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), MODULENAME )) {
196 osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on", MODULENAME, classname);
200 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
201 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
205 // Look up some other attributes of the current class
206 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
207 if( !idlClass_fieldmapper ) {
208 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL", classname );
213 osrfHash* idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
214 if (!idlClass_permacrud) {
215 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no permacrud in IDL", classname );
219 const char* readonly = osrfHashGet(idlClass, "readonly");
222 for( i = 0; i < global_method_count; ++i ) { // for each global method
223 const char* method_type = global_method[ i ];
224 osrfLogDebug(OSRF_LOG_MARK,
225 "Using files to build %s class methods for %s", method_type, classname);
228 const char* tmp_method = method_type;
229 if ( *tmp_method == 'i' || *tmp_method == 's') {
230 tmp_method = "retrieve";
232 if (!osrfHashGet( idlClass_permacrud, tmp_method )) continue;
235 if ( str_is_true( readonly ) &&
236 ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd')
239 buffer_reset( method_name );
241 buffer_fadd(method_name, "%s.%s.%s", MODULENAME, method_type, classname);
245 char* _fm = strdup( idlClass_fieldmapper );
246 part = strtok_r(_fm, ":", &st_tmp);
248 buffer_fadd(method_name, "%s.direct.%s", MODULENAME, part);
250 while ((part = strtok_r(NULL, ":", &st_tmp))) {
251 OSRF_BUFFER_ADD_CHAR(method_name, '.');
252 OSRF_BUFFER_ADD(method_name, part);
254 OSRF_BUFFER_ADD_CHAR(method_name, '.');
255 OSRF_BUFFER_ADD(method_name, method_type);
259 char* method = buffer_data(method_name);
262 if (*method_type == 'i' || *method_type == 's') {
263 flags = flags | OSRF_METHOD_STREAMING;
266 osrfHash* method_meta = osrfNewHash();
267 osrfHashSet( method_meta, idlClass, "class");
268 osrfHashSet( method_meta, method, "methodname" );
269 osrfHashSet( method_meta, strdup(method_type), "methodtype" );
271 osrfAppRegisterExtendedMethod(
274 "dispatchCRUDMethod",
282 } // end for each global method
283 } // end for each class in IDL
285 buffer_free( method_name );
286 osrfStringArrayFree( classes );
291 static char* getSourceDefinition( osrfHash* class ) {
293 char* tabledef = osrfHashGet(class, "tablename");
296 tabledef = strdup(tabledef);
298 tabledef = osrfHashGet(class, "source_definition");
300 growing_buffer* tablebuf = buffer_init(128);
301 buffer_fadd( tablebuf, "(%s)", tabledef );
302 tabledef = buffer_release(tablebuf);
304 const char* classname = osrfHashGet( class, "classname" );
309 "%s ERROR No tablename or source_definition for class \"%s\"",
320 * Connects to the database
322 int osrfAppChildInit() {
324 osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
325 dbi_initialize(NULL);
326 osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
328 char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
329 char* user = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
330 char* host = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
331 char* port = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
332 char* db = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
333 char* pw = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
334 char* md = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion", MODULENAME);
336 osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
337 writehandle = dbi_conn_new(driver);
340 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
343 osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
345 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
346 "port=%s, user=%s, pw=%s, db=%s", MODULENAME, host, port, user, pw, db );
348 if(host) dbi_conn_set_option(writehandle, "host", host );
349 if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
350 if(user) dbi_conn_set_option(writehandle, "username", user);
351 if(pw) dbi_conn_set_option(writehandle, "password", pw );
352 if(db) dbi_conn_set_option(writehandle, "dbname", db );
354 if(md) max_flesh_depth = atoi(md);
355 if(max_flesh_depth < 0) max_flesh_depth = 1;
356 if(max_flesh_depth > 1000) max_flesh_depth = 1000;
365 if (dbi_conn_connect(writehandle) < 0) {
367 if (dbi_conn_connect(writehandle) < 0) {
368 dbi_conn_error(writehandle, &err);
369 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
374 osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
378 osrfStringArray* classes = osrfHashKeys( oilsIDL() );
380 while ( (classname = osrfStringArrayGetString(classes, i++)) ) {
381 osrfHash* class = osrfHashGet( oilsIDL(), classname );
382 osrfHash* fields = osrfHashGet( class, "fields" );
384 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
385 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
389 char* tabledef = getSourceDefinition(class);
391 tabledef = strdup( "(null)" );
393 growing_buffer* sql_buf = buffer_init(32);
394 buffer_fadd( sql_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
398 char* sql = buffer_release(sql_buf);
399 osrfLogDebug(OSRF_LOG_MARK, "%s Investigatory SQL = %s", MODULENAME, sql);
401 dbi_result result = dbi_conn_query(writehandle, sql);
407 const char* columnName;
409 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
411 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
413 /* fetch the fieldmapper index */
414 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
416 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", (char*)columnName);
418 /* determine the field type and storage attributes */
420 switch( dbi_result_get_field_type_idx(result, columnIndex) ) {
422 case DBI_TYPE_INTEGER : {
424 if ( !osrfHashGet(_f, "primitive") )
425 osrfHashSet(_f,"number", "primitive");
427 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
428 if( attr & DBI_INTEGER_SIZE8 )
429 osrfHashSet(_f,"INT8", "datatype");
431 osrfHashSet(_f,"INT", "datatype");
434 case DBI_TYPE_DECIMAL :
435 if ( !osrfHashGet(_f, "primitive") )
436 osrfHashSet(_f,"number", "primitive");
438 osrfHashSet(_f,"NUMERIC", "datatype");
441 case DBI_TYPE_STRING :
442 if ( !osrfHashGet(_f, "primitive") )
443 osrfHashSet(_f,"string", "primitive");
444 osrfHashSet(_f,"TEXT", "datatype");
447 case DBI_TYPE_DATETIME :
448 if ( !osrfHashGet(_f, "primitive") )
449 osrfHashSet(_f,"string", "primitive");
451 osrfHashSet(_f,"TIMESTAMP", "datatype");
454 case DBI_TYPE_BINARY :
455 if ( !osrfHashGet(_f, "primitive") )
456 osrfHashSet(_f,"string", "primitive");
458 osrfHashSet(_f,"BYTEA", "datatype");
463 "Setting [%s] to primitive [%s] and datatype [%s]...",
465 osrfHashGet(_f, "primitive"),
466 osrfHashGet(_f, "datatype")
470 } // end while loop for traversing result
471 dbi_result_free(result);
473 osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", (char*)classname);
477 osrfStringArrayFree(classes);
479 child_initialized = 1;
484 This function is a sleazy hack intended *only* for testing and
485 debugging. Any real server process should initialize the
486 database connection by calling osrfAppChildInit().
488 void set_cstore_dbi_conn( dbi_conn conn ) {
489 dbhandle = writehandle = conn;
492 void userDataFree( void* blob ) {
493 osrfHashFree( (osrfHash*)blob );
497 static void sessionDataFree( char* key, void* item ) {
498 if (!(strcmp(key,"xact_id"))) {
500 dbi_conn_query(writehandle, "ROLLBACK;");
507 int beginTransaction ( osrfMethodContext* ctx ) {
508 if(osrfMethodVerifyContext( ctx )) {
509 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
514 jsonObject* user = verifyUserPCRUD( ctx );
515 if (!user) return -1;
516 jsonObjectFree(user);
519 dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
521 osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
522 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error starting transaction" );
525 jsonObject* ret = jsonNewObject(ctx->session->session_id);
526 osrfAppRespondComplete( ctx, ret );
529 if (!ctx->session->userData) {
530 ctx->session->userData = osrfNewHash();
531 osrfHashSetCallback((osrfHash*)ctx->session->userData, &sessionDataFree);
534 osrfHashSet( (osrfHash*)ctx->session->userData, strdup( ctx->session->session_id ), "xact_id" );
535 ctx->session->userDataFree = &userDataFree;
541 int setSavepoint ( osrfMethodContext* ctx ) {
542 if(osrfMethodVerifyContext( ctx )) {
543 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
550 jsonObject* user = verifyUserPCRUD( ctx );
551 if (!user) return -1;
552 jsonObjectFree(user);
555 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
556 osrfAppSessionStatus(
558 OSRF_STATUS_INTERNALSERVERERROR,
559 "osrfMethodException",
561 "No active transaction -- required for savepoints"
566 const char* spName = jsonObjectGetString(jsonObjectGetIndex(ctx->params, spNamePos));
568 dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
572 "%s: Error creating savepoint %s in transaction %s",
575 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
577 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
578 "osrfMethodException", ctx->request, "Error creating savepoint" );
581 jsonObject* ret = jsonNewObject(spName);
582 osrfAppRespondComplete( ctx, ret );
588 int releaseSavepoint ( osrfMethodContext* ctx ) {
589 if(osrfMethodVerifyContext( ctx )) {
590 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
597 jsonObject* user = verifyUserPCRUD( ctx );
598 if (!user) return -1;
599 jsonObjectFree(user);
602 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
603 osrfAppSessionStatus(
605 OSRF_STATUS_INTERNALSERVERERROR,
606 "osrfMethodException",
608 "No active transaction -- required for savepoints"
613 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
615 dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
619 "%s: Error releasing savepoint %s in transaction %s",
622 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
624 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
625 "osrfMethodException", ctx->request, "Error releasing savepoint" );
628 jsonObject* ret = jsonNewObject(spName);
629 osrfAppRespondComplete( ctx, ret );
635 int rollbackSavepoint ( osrfMethodContext* ctx ) {
636 if(osrfMethodVerifyContext( ctx )) {
637 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
644 jsonObject* user = verifyUserPCRUD( ctx );
645 if (!user) return -1;
646 jsonObjectFree(user);
649 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
650 osrfAppSessionStatus(
652 OSRF_STATUS_INTERNALSERVERERROR,
653 "osrfMethodException",
655 "No active transaction -- required for savepoints"
660 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
662 dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
666 "%s: Error rolling back savepoint %s in transaction %s",
669 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
671 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
672 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
675 jsonObject* ret = jsonNewObject(spName);
676 osrfAppRespondComplete( ctx, ret );
682 int commitTransaction ( osrfMethodContext* ctx ) {
683 if(osrfMethodVerifyContext( ctx )) {
684 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
689 jsonObject* user = verifyUserPCRUD( ctx );
690 if (!user) return -1;
691 jsonObjectFree(user);
694 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
695 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to commit" );
699 dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
701 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
702 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error committing transaction" );
705 osrfHashRemove(ctx->session->userData, "xact_id");
706 jsonObject* ret = jsonNewObject(ctx->session->session_id);
707 osrfAppRespondComplete( ctx, ret );
713 int rollbackTransaction ( osrfMethodContext* ctx ) {
714 if(osrfMethodVerifyContext( ctx )) {
715 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
720 jsonObject* user = verifyUserPCRUD( ctx );
721 if (!user) return -1;
722 jsonObjectFree(user);
725 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
726 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to roll back" );
730 dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
732 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
733 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error rolling back transaction" );
736 osrfHashRemove(ctx->session->userData, "xact_id");
737 jsonObject* ret = jsonNewObject(ctx->session->session_id);
738 osrfAppRespondComplete( ctx, ret );
744 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
745 if(osrfMethodVerifyContext( ctx )) {
746 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
750 osrfHash* meta = (osrfHash*) ctx->method->userData;
751 osrfHash* class_obj = osrfHashGet( meta, "class" );
755 const char* methodtype = osrfHashGet(meta, "methodtype");
756 jsonObject * obj = NULL;
758 if (!strcmp(methodtype, "create")) {
759 obj = doCreate(ctx, &err);
760 osrfAppRespondComplete( ctx, obj );
762 else if (!strcmp(methodtype, "retrieve")) {
763 obj = doRetrieve(ctx, &err);
764 osrfAppRespondComplete( ctx, obj );
766 else if (!strcmp(methodtype, "update")) {
767 obj = doUpdate(ctx, &err);
768 osrfAppRespondComplete( ctx, obj );
770 else if (!strcmp(methodtype, "delete")) {
771 obj = doDelete(ctx, &err);
772 osrfAppRespondComplete( ctx, obj );
774 else if (!strcmp(methodtype, "search")) {
776 jsonObject* where_clause;
777 jsonObject* rest_of_query;
780 where_clause = jsonObjectGetIndex( ctx->params, 1 );
781 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
783 where_clause = jsonObjectGetIndex( ctx->params, 0 );
784 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
787 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
792 unsigned long res_idx = 0;
793 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
795 if(!verifyObjectPCRUD(ctx, cur)) continue;
797 osrfAppRespond( ctx, cur );
799 osrfAppRespondComplete( ctx, NULL );
801 } else if (!strcmp(methodtype, "id_list")) {
803 jsonObject* where_clause;
804 jsonObject* rest_of_query;
806 // We use the where clause without change. But we need
807 // to massage the rest of the query, so we work with a copy
808 // of it instead of modifying the original.
810 where_clause = jsonObjectGetIndex( ctx->params, 1 );
811 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
813 where_clause = jsonObjectGetIndex( ctx->params, 0 );
814 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
817 if ( rest_of_query ) {
818 jsonObjectRemoveKey( rest_of_query, "select" );
819 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
820 jsonObjectRemoveKey( rest_of_query, "flesh" );
821 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
823 rest_of_query = jsonNewObjectType( JSON_HASH );
826 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
828 // Build a SELECT list containing just the primary key,
829 // i.e. like { "classname":["keyname"] }
830 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
831 jsonObjectPush( col_list_obj, // Load array with name of primary key
832 jsonNewObject( osrfHashGet( class_obj, "primarykey" ) ) );
833 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
834 jsonObjectSetKey( select_clause, osrfHashGet( class_obj, "classname" ), col_list_obj );
836 jsonObjectSetKey( rest_of_query, "select", select_clause );
838 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
840 jsonObjectFree( rest_of_query );
844 unsigned long res_idx = 0;
845 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
847 if(!verifyObjectPCRUD(ctx, cur)) continue;
851 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
854 osrfAppRespondComplete( ctx, NULL );
857 osrfAppRespondComplete( ctx, obj );
865 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
868 osrfHash* meta = (osrfHash*) ctx->method->userData;
869 osrfHash* class = osrfHashGet( meta, "class" );
871 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
873 const char* temp_classname = param->classname;
874 if( ! temp_classname )
875 temp_classname = "(null)";
877 growing_buffer* msg = buffer_init(128);
880 "%s: %s method for type %s was passed a %s",
882 osrfHashGet(meta, "methodtype"),
883 osrfHashGet(class, "classname"),
887 char* m = buffer_release(msg);
888 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException", ctx->request, m );
896 ret = verifyObjectPCRUD( ctx, param );
904 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
905 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
906 jsonObject* auth_object = jsonNewObject(auth);
907 jsonObject* user = oilsUtilsQuickReq("open-ils.auth","open-ils.auth.session.retrieve", auth_object);
908 jsonObjectFree(auth_object);
910 if (!user->classname || strcmp(user->classname, "au")) {
912 growing_buffer* msg = buffer_init(128);
915 "%s: permacrud received a bad auth token: %s",
920 char* m = buffer_release(msg);
921 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException", ctx->request, m );
924 jsonObjectFree(user);
932 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
934 dbhandle = writehandle;
936 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
937 osrfHash* class = osrfHashGet( method_metadata, "class" );
938 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
941 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
942 method_type = "retrieve"; // search and id_list are equivalant to retrieve for this
943 } else if ( *method_type == 'u' || *method_type == 'd' ) {
944 fetch = 1; // MUST go to the db for the object for update and delete
947 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
950 // No permacrud for this method type on this class
952 growing_buffer* msg = buffer_init(128);
955 "%s: %s on class %s has no permacrud IDL entry",
957 osrfHashGet(method_metadata, "methodtype"),
958 osrfHashGet(class, "classname")
961 char* m = buffer_release(msg);
962 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN, "osrfMethodException", ctx->request, m );
969 jsonObject* user = verifyUserPCRUD( ctx );
972 int userid = atoi( oilsFMGetString( user, "id" ) );
973 jsonObjectFree(user);
975 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
976 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
977 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
979 osrfStringArray* context_org_array = osrfNewStringArray(1);
982 char* pkey_value = NULL;
983 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
984 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions required, fetching top of the org tree" );
986 // check for perm at top of org tree
987 char* org_tree_root_id = org_tree_root( ctx );
988 if( org_tree_root_id ) {
989 osrfStringArrayAdd( context_org_array, org_tree_root_id );
990 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
992 osrfStringArrayFree( context_org_array );
997 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, fetching context org ids" );
998 const char* pkey = osrfHashGet(class, "primarykey");
999 jsonObject *param = NULL;
1001 if (obj->classname) {
1002 pkey_value = oilsFMGetString( obj, pkey );
1003 if (!fetch) param = jsonObjectClone(obj);
1004 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s", pkey_value );
1006 pkey_value = jsonObjectToSimpleString( obj );
1008 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value of %s and retrieving from the database", pkey_value );
1012 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1013 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1014 jsonObjectFree(_tmp_params);
1016 param = jsonObjectExtractIndex(_list, 0);
1017 jsonObjectFree(_list);
1021 osrfLogDebug( OSRF_LOG_MARK, "Object not found in the database with primary key %s of %s", pkey, pkey_value );
1023 growing_buffer* msg = buffer_init(128);
1026 "%s: no object found with primary key %s of %s",
1032 char* m = buffer_release(msg);
1033 osrfAppSessionStatus(
1035 OSRF_STATUS_INTERNALSERVERERROR,
1036 "osrfMethodException",
1042 if (pkey_value) free(pkey_value);
1047 if (local_context->size > 0) {
1048 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified", local_context->size);
1050 char* lcontext = NULL;
1051 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1052 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1055 "adding class-local field %s (value: %s) to the context org list",
1057 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1062 osrfStringArray* class_list;
1064 if (foreign_context) {
1065 class_list = osrfHashKeys( foreign_context );
1066 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_list->size);
1068 if (class_list->size > 0) {
1071 char* class_name = NULL;
1072 while ( (class_name = osrfStringArrayGetString(class_list, i++)) ) {
1073 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1077 "%d foreign context fields(s) specified for class %s",
1078 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1082 char* foreign_pkey = osrfHashGet(fcontext, "field");
1083 char* foreign_pkey_value = oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1085 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1087 jsonObject* _list = doFieldmapperSearch(
1088 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1090 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1091 jsonObjectFree(_tmp_params);
1092 jsonObjectFree(_list);
1094 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1096 if (_fparam && jump_list) {
1099 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1100 free(foreign_pkey_value);
1102 osrfHash* foreign_link_hash = oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1104 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1105 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1107 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1109 _list = doFieldmapperSearch(
1111 osrfHashGet( oilsIDL(), osrfHashGet( foreign_link_hash, "class" ) ),
1117 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1118 jsonObjectFree(_tmp_params);
1119 jsonObjectFree(_list);
1126 growing_buffer* msg = buffer_init(128);
1129 "%s: no object found with primary key %s of %s",
1135 char* m = buffer_release(msg);
1136 osrfAppSessionStatus(
1138 OSRF_STATUS_INTERNALSERVERERROR,
1139 "osrfMethodException",
1145 osrfStringArrayFree(class_list);
1146 free(foreign_pkey_value);
1147 jsonObjectFree(param);
1152 free(foreign_pkey_value);
1155 char* foreign_field = NULL;
1156 while ( (foreign_field = osrfStringArrayGetString(osrfHashGet(fcontext,"context"), j++)) ) {
1157 osrfStringArrayAdd( context_org_array, oilsFMGetString( _fparam, foreign_field ) );
1160 "adding foreign class %s field %s (value: %s) to the context org list",
1163 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1167 jsonObjectFree(_fparam);
1170 osrfStringArrayFree(class_list);
1174 jsonObjectFree(param);
1177 char* context_org = NULL;
1181 if (permission->size == 0) {
1182 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1187 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1189 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1195 "Checking object permission [%s] for user %d on object %s (class %s) at org %d",
1199 osrfHashGet(class, "classname"),
1203 result = dbi_conn_queryf(
1205 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1208 osrfHashGet(class, "classname"),
1216 "Recieved a result for object permission [%s] for user %d on object %s (class %s) at org %d",
1220 osrfHashGet(class, "classname"),
1224 if (dbi_result_first_row(result)) {
1225 jsonObject* return_val = oilsMakeJSONFromResult( result );
1226 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1230 "Status of object permission [%s] for user %d on object %s (class %s) at org %d is %s",
1234 osrfHashGet(class, "classname"),
1239 if ( *has_perm == 't' ) OK = 1;
1240 jsonObjectFree(return_val);
1243 dbi_result_free(result);
1248 osrfLogDebug( OSRF_LOG_MARK, "Checking non-object permission [%s] for user %d at org %d", perm, userid, atoi(context_org) );
1249 result = dbi_conn_queryf(
1251 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1258 osrfLogDebug( OSRF_LOG_MARK, "Received a result for permission [%s] for user %d at org %d",
1259 perm, userid, atoi(context_org) );
1260 if ( dbi_result_first_row(result) ) {
1261 jsonObject* return_val = oilsMakeJSONFromResult( result );
1262 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1263 osrfLogDebug( OSRF_LOG_MARK, "Status of permission [%s] for user %d at org %d is [%s]",
1264 perm, userid, atoi(context_org), has_perm );
1265 if ( *has_perm == 't' ) OK = 1;
1266 jsonObjectFree(return_val);
1269 dbi_result_free(result);
1277 if (pkey_value) free(pkey_value);
1278 osrfStringArrayFree(context_org_array);
1284 * Look up the root of the org_unit tree. If you find it, return
1285 * a string containing the id, which the caller is responsible for freeing.
1286 * Otherwise return NULL.
1288 static char* org_tree_root( osrfMethodContext* ctx ) {
1290 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1291 static time_t last_lookup_time = 0;
1292 time_t current_time = time( NULL );
1294 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1295 // We successfully looked this up less than an hour ago.
1296 // It's not likely to have changed since then.
1297 return strdup( cached_root_id );
1299 last_lookup_time = current_time;
1302 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1303 jsonObject* result = doFieldmapperSearch(
1304 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1305 jsonObjectFree( where_clause );
1307 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1310 jsonObjectFree( result );
1312 growing_buffer* msg = buffer_init(128);
1313 OSRF_BUFFER_ADD( msg, MODULENAME );
1314 OSRF_BUFFER_ADD( msg,
1315 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1317 char* m = buffer_release(msg);
1318 osrfAppSessionStatus( ctx->session,
1319 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1322 cached_root_id[ 0 ] = '\0';
1326 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1327 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1329 jsonObjectFree( result );
1331 strcpy( cached_root_id, root_org_unit_id );
1332 return root_org_unit_id;
1336 Utility function: create a JSON_HASH with a single key/value pair.
1337 This function is equivalent to:
1339 jsonParseStringFmt( "{\"%s\":\"%s\"}", key, value )
1341 or, if value is NULL:
1343 jsonParseStringFmt( "{\"%s\":null}", key )
1345 ...but faster because it doesn't create and parse a JSON string.
1347 static jsonObject* single_hash( const char* key, const char* value ) {
1349 if( ! key ) key = "";
1351 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1352 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1358 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1360 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1362 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1363 jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1365 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1366 jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1369 if (!verifyObjectClass(ctx, target)) {
1374 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1376 char* trans_id = NULL;
1377 if( ctx->session && ctx->session->userData )
1378 trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
1381 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1383 osrfAppSessionStatus(
1385 OSRF_STATUS_BADREQUEST,
1386 "osrfMethodException",
1388 "No active transaction -- required for CREATE"
1394 // The following test is harmless but redundant. If a class is
1395 // readonly, we don't register a create method for it.
1396 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1397 osrfAppSessionStatus(
1399 OSRF_STATUS_BADREQUEST,
1400 "osrfMethodException",
1402 "Cannot INSERT readonly class"
1408 // Set the last_xact_id
1409 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1411 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
1412 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1415 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1417 dbhandle = writehandle;
1419 osrfHash* fields = osrfHashGet(meta, "fields");
1420 char* pkey = osrfHashGet(meta, "primarykey");
1421 char* seq = osrfHashGet(meta, "sequence");
1423 growing_buffer* table_buf = buffer_init(128);
1424 growing_buffer* col_buf = buffer_init(128);
1425 growing_buffer* val_buf = buffer_init(128);
1427 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1428 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1429 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1430 buffer_add(val_buf,"VALUES (");
1436 osrfStringArray* field_list = osrfHashKeys( fields );
1437 while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
1439 osrfHash* field = osrfHashGet( fields, field_name );
1441 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1444 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1447 if (field_object && field_object->classname) {
1448 value = oilsFMGetString(
1450 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1453 value = jsonObjectToSimpleString( field_object );
1460 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1461 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1464 buffer_add(col_buf, field_name);
1466 if (!field_object || field_object->type == JSON_NULL) {
1467 buffer_add( val_buf, "DEFAULT" );
1469 } else if ( !strcmp(get_primitive( field ), "number") ) {
1470 const char* numtype = get_datatype( field );
1471 if ( !strcmp( numtype, "INT8") ) {
1472 buffer_fadd( val_buf, "%lld", atoll(value) );
1474 } else if ( !strcmp( numtype, "INT") ) {
1475 buffer_fadd( val_buf, "%d", atoi(value) );
1477 } else if ( !strcmp( numtype, "NUMERIC") ) {
1478 buffer_fadd( val_buf, "%f", atof(value) );
1481 if ( dbi_conn_quote_string(writehandle, &value) ) {
1482 OSRF_BUFFER_ADD( val_buf, value );
1485 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1486 osrfAppSessionStatus(
1488 OSRF_STATUS_INTERNALSERVERERROR,
1489 "osrfMethodException",
1491 "Error quoting string -- please see the error log for more details"
1494 buffer_free(table_buf);
1495 buffer_free(col_buf);
1496 buffer_free(val_buf);
1507 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1508 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1510 char* table_str = buffer_release(table_buf);
1511 char* col_str = buffer_release(col_buf);
1512 char* val_str = buffer_release(val_buf);
1513 growing_buffer* sql = buffer_init(128);
1514 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1519 char* query = buffer_release(sql);
1521 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1524 dbi_result result = dbi_conn_query(writehandle, query);
1526 jsonObject* obj = NULL;
1529 obj = jsonNewObject(NULL);
1532 "%s ERROR inserting %s object using query [%s]",
1534 osrfHashGet(meta, "fieldmapper"),
1537 osrfAppSessionStatus(
1539 OSRF_STATUS_INTERNALSERVERERROR,
1540 "osrfMethodException",
1542 "INSERT error -- please see the error log for more details"
1547 char* id = oilsFMGetString(target, pkey);
1549 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1550 growing_buffer* _id = buffer_init(10);
1551 buffer_fadd(_id, "%lld", new_id);
1552 id = buffer_release(_id);
1555 // Find quietness specification, if present
1556 const char* quiet_str = NULL;
1558 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1560 quiet_str = jsonObjectGetString( quiet_obj );
1563 if( str_is_true( quiet_str ) ) { // if quietness is specified
1564 obj = jsonNewObject(id);
1568 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1569 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1571 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1573 jsonObjectFree( where_clause );
1578 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1581 jsonObjectFree( list );
1594 * Fetch one row from a specified table, using a specified value
1595 * for the primary key
1597 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1607 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1609 const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos); // key value
1613 "%s retrieving %s object with primary key value of %s",
1615 osrfHashGet( class_def, "fieldmapper" ),
1616 jsonObjectGetString( id_obj )
1619 // Build a WHERE clause based on the key value
1620 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1623 osrfHashGet( class_def, "primarykey" ),
1624 jsonObjectClone( id_obj )
1627 jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
1629 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
1631 jsonObjectFree( where_clause );
1635 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
1636 jsonObjectFree( list );
1639 if(!verifyObjectPCRUD(ctx, obj)) {
1640 jsonObjectFree(obj);
1643 growing_buffer* msg = buffer_init(128);
1644 OSRF_BUFFER_ADD( msg, MODULENAME );
1645 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1647 char* m = buffer_release(msg);
1648 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1659 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1660 growing_buffer* val_buf = buffer_init(32);
1661 const char* numtype = get_datatype( field );
1663 if ( !strncmp( numtype, "INT", 3 ) ) {
1664 if (value->type == JSON_NUMBER)
1665 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1666 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1668 //const char* val_str = jsonObjectGetString( value );
1669 //buffer_fadd( val_buf, "%ld", atol(val_str) );
1670 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1673 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
1674 if (value->type == JSON_NUMBER)
1675 //buffer_fadd( val_buf, "%f", jsonObjectGetNumber(value) );
1676 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1678 //const char* val_str = jsonObjectGetString( value );
1679 //buffer_fadd( val_buf, "%f", atof(val_str) );
1680 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1684 // Presumably this was really intended ot be a string, so quote it
1685 char* str = jsonObjectToSimpleString( value );
1686 if ( dbi_conn_quote_string(dbhandle, &str) ) {
1687 OSRF_BUFFER_ADD( val_buf, str );
1690 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
1692 buffer_free(val_buf);
1697 return buffer_release(val_buf);
1700 static char* searchINPredicate (const char* class, osrfHash* field,
1701 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1702 growing_buffer* sql_buf = buffer_init(32);
1708 osrfHashGet(field, "name")
1712 buffer_add(sql_buf, "IN (");
1713 } else if (!(strcasecmp(op,"not in"))) {
1714 buffer_add(sql_buf, "NOT IN (");
1716 buffer_add(sql_buf, "IN (");
1719 if (node->type == JSON_HASH) {
1720 // subquery predicate
1721 char* subpred = SELECT(
1723 jsonObjectGetKey( node, "select" ),
1724 jsonObjectGetKey( node, "from" ),
1725 jsonObjectGetKey( node, "where" ),
1726 jsonObjectGetKey( node, "having" ),
1727 jsonObjectGetKey( node, "order_by" ),
1728 jsonObjectGetKey( node, "limit" ),
1729 jsonObjectGetKey( node, "offset" ),
1734 buffer_add(sql_buf, subpred);
1737 buffer_free( sql_buf );
1741 } else if (node->type == JSON_ARRAY) {
1742 // literal value list
1743 int in_item_index = 0;
1744 int in_item_first = 1;
1745 const jsonObject* in_item;
1746 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1751 buffer_add(sql_buf, ", ");
1754 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1755 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1756 MODULENAME, json_type( in_item->type ) );
1757 buffer_free(sql_buf);
1761 // Append the literal value -- quoted if not a number
1762 if ( JSON_NUMBER == in_item->type ) {
1763 char* val = jsonNumberToDBString( field, in_item );
1764 OSRF_BUFFER_ADD( sql_buf, val );
1767 } else if ( !strcmp( get_primitive( field ), "number") ) {
1768 char* val = jsonNumberToDBString( field, in_item );
1769 OSRF_BUFFER_ADD( sql_buf, val );
1773 char* key_string = jsonObjectToSimpleString(in_item);
1774 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1775 OSRF_BUFFER_ADD( sql_buf, key_string );
1778 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1780 buffer_free(sql_buf);
1786 if( in_item_first ) {
1787 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1788 buffer_free( sql_buf );
1792 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1793 MODULENAME, json_type( node->type ) );
1794 buffer_free(sql_buf);
1798 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1800 return buffer_release(sql_buf);
1803 // Receive a JSON_ARRAY representing a function call. The first
1804 // entry in the array is the function name. The rest are parameters.
1805 static char* searchValueTransform( const jsonObject* array ) {
1807 if( array->size < 1 ) {
1808 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
1812 // Get the function name
1813 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
1814 if( func_item->type != JSON_STRING ) {
1815 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
1816 MODULENAME, json_type( func_item->type ) );
1820 growing_buffer* sql_buf = buffer_init(32);
1822 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1823 OSRF_BUFFER_ADD( sql_buf, "( " );
1825 // Get the parameters
1826 int func_item_index = 1; // We already grabbed the zeroth entry
1827 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1829 // Add a separator comma, if we need one
1830 if( func_item_index > 2 )
1831 buffer_add( sql_buf, ", " );
1833 // Add the current parameter
1834 if (func_item->type == JSON_NULL) {
1835 buffer_add( sql_buf, "NULL" );
1837 char* val = jsonObjectToSimpleString(func_item);
1838 if ( dbi_conn_quote_string(dbhandle, &val) ) {
1839 OSRF_BUFFER_ADD( sql_buf, val );
1842 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1843 buffer_free(sql_buf);
1850 buffer_add( sql_buf, " )" );
1852 return buffer_release(sql_buf);
1855 static char* searchFunctionPredicate (const char* class, osrfHash* field,
1856 const jsonObject* node, const char* op) {
1858 if( ! is_good_operator( op ) ) {
1859 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
1863 char* val = searchValueTransform(node);
1867 growing_buffer* sql_buf = buffer_init(32);
1872 osrfHashGet(field, "name"),
1879 return buffer_release(sql_buf);
1882 // class is a class name
1883 // field is a field definition as stored in the IDL
1884 // node comes from the method parameter, and may represent an entry in the SELECT list
1885 static char* searchFieldTransform (const char* class, osrfHash* field, const jsonObject* node) {
1886 growing_buffer* sql_buf = buffer_init(32);
1888 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
1889 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
1891 if(transform_subcolumn) {
1892 if( ! is_identifier( transform_subcolumn ) ) {
1893 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
1894 MODULENAME, transform_subcolumn );
1895 buffer_free( sql_buf );
1898 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
1901 if (field_transform) {
1903 if( ! is_identifier( field_transform ) ) {
1904 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
1905 MODULENAME, field_transform );
1906 buffer_free( sql_buf );
1910 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class, osrfHashGet(field, "name"));
1911 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
1914 if( array->type != JSON_ARRAY ) {
1915 osrfLogError( OSRF_LOG_MARK,
1916 "%s: Expected JSON_ARRAY for function params; found %s",
1917 MODULENAME, json_type( array->type ) );
1918 buffer_free( sql_buf );
1921 int func_item_index = 0;
1922 jsonObject* func_item;
1923 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1925 char* val = jsonObjectToSimpleString(func_item);
1928 buffer_add( sql_buf, ",NULL" );
1929 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
1930 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
1931 OSRF_BUFFER_ADD( sql_buf, val );
1933 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1935 buffer_free(sql_buf);
1942 buffer_add( sql_buf, " )" );
1945 buffer_fadd( sql_buf, "\"%s\".%s", class, osrfHashGet(field, "name"));
1948 if (transform_subcolumn)
1949 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
1951 return buffer_release(sql_buf);
1954 static char* searchFieldTransformPredicate (const char* class, osrfHash* field,
1955 const jsonObject* node, const char* op ) {
1957 if( ! is_good_operator( op ) ) {
1958 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
1962 char* field_transform = searchFieldTransform( class, field, node );
1963 if( ! field_transform )
1966 int extra_parens = 0; // boolean
1968 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
1969 if ( ! value_obj ) {
1970 value = searchWHERE( node, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1972 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
1973 free(field_transform);
1977 } else if ( value_obj->type == JSON_ARRAY ) {
1978 value = searchValueTransform( value_obj );
1980 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
1981 free( field_transform );
1984 } else if ( value_obj->type == JSON_HASH ) {
1985 value = searchWHERE( value_obj, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1987 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
1988 free(field_transform);
1992 } else if ( value_obj->type == JSON_NUMBER ) {
1993 value = jsonNumberToDBString( field, value_obj );
1994 } else if ( value_obj->type == JSON_NULL ) {
1995 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
1996 free(field_transform);
1998 } else if ( value_obj->type == JSON_BOOL ) {
1999 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
2000 free(field_transform);
2003 if ( !strcmp( get_primitive( field ), "number") ) {
2004 value = jsonNumberToDBString( field, value_obj );
2006 value = jsonObjectToSimpleString( value_obj );
2007 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2008 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
2010 free(field_transform);
2016 const char* left_parens = "";
2017 const char* right_parens = "";
2019 if( extra_parens ) {
2024 growing_buffer* sql_buf = buffer_init(32);
2028 "%s%s %s %s %s %s%s",
2039 free(field_transform);
2041 return buffer_release(sql_buf);
2044 static char* searchSimplePredicate (const char* op, const char* class,
2045 osrfHash* field, const jsonObject* node) {
2047 if( ! is_good_operator( op ) ) {
2048 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2054 // Get the value to which we are comparing the specified column
2055 if (node->type != JSON_NULL) {
2056 if ( node->type == JSON_NUMBER ) {
2057 val = jsonNumberToDBString( field, node );
2058 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2059 val = jsonNumberToDBString( field, node );
2061 val = jsonObjectToSimpleString(node);
2066 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2067 // Value is not numeric; enclose it in quotes
2068 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2069 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2075 // Compare to a null value
2076 val = strdup( "NULL" );
2077 if (strcmp( op, "=" ))
2083 growing_buffer* sql_buf = buffer_init(32);
2084 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class, osrfHashGet(field, "name"), op, val );
2085 char* pred = buffer_release( sql_buf );
2092 static char* searchBETWEENPredicate (const char* class, osrfHash* field, const jsonObject* node) {
2094 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2095 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2097 if( NULL == y_node ) {
2098 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2101 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2102 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2109 if ( !strcmp( get_primitive( field ), "number") ) {
2110 x_string = jsonNumberToDBString(field, x_node);
2111 y_string = jsonNumberToDBString(field, y_node);
2114 x_string = jsonObjectToSimpleString(x_node);
2115 y_string = jsonObjectToSimpleString(y_node);
2116 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2117 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2118 MODULENAME, x_string, y_string);
2125 growing_buffer* sql_buf = buffer_init(32);
2126 buffer_fadd( sql_buf, "%s BETWEEN %s AND %s", osrfHashGet(field, "name"), x_string, y_string );
2130 return buffer_release(sql_buf);
2133 static char* searchPredicate ( const char* class, osrfHash* field,
2134 jsonObject* node, osrfMethodContext* ctx ) {
2137 if (node->type == JSON_ARRAY) { // equality IN search
2138 pred = searchINPredicate( class, field, node, NULL, ctx );
2139 } else if (node->type == JSON_HASH) { // other search
2140 jsonIterator* pred_itr = jsonNewIterator( node );
2141 if( !jsonIteratorHasNext( pred_itr ) ) {
2142 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2143 MODULENAME, osrfHashGet(field, "name") );
2145 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2147 // Verify that there are no additional predicates
2148 if( jsonIteratorHasNext( pred_itr ) ) {
2149 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2150 MODULENAME, osrfHashGet(field, "name") );
2151 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2152 pred = searchBETWEENPredicate( class, field, pred_node );
2153 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2154 pred = searchINPredicate( class, field, pred_node, pred_itr->key, ctx );
2155 else if ( pred_node->type == JSON_ARRAY )
2156 pred = searchFunctionPredicate( class, field, pred_node, pred_itr->key );
2157 else if ( pred_node->type == JSON_HASH )
2158 pred = searchFieldTransformPredicate( class, field, pred_node, pred_itr->key );
2160 pred = searchSimplePredicate( pred_itr->key, class, field, pred_node );
2162 jsonIteratorFree(pred_itr);
2164 } else if (node->type == JSON_NULL) { // IS NULL search
2165 growing_buffer* _p = buffer_init(64);
2168 "\"%s\".%s IS NULL",
2170 osrfHashGet(field, "name")
2172 pred = buffer_release(_p);
2173 } else { // equality search
2174 pred = searchSimplePredicate( "=", class, field, node );
2193 field : call_number,
2209 static char* searchJOIN ( const jsonObject* join_hash, osrfHash* leftmeta ) {
2211 const jsonObject* working_hash;
2212 jsonObject* freeable_hash = NULL;
2214 if (join_hash->type == JSON_STRING) {
2215 // create a wrapper around a copy of the original
2216 const char* _tmp = jsonObjectGetString( join_hash );
2217 freeable_hash = jsonNewObjectType(JSON_HASH);
2218 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2219 working_hash = freeable_hash;
2222 if( join_hash->type != JSON_HASH ) {
2225 "%s: JOIN failed; expected JSON object type not found",
2230 working_hash = join_hash;
2233 growing_buffer* join_buf = buffer_init(128);
2234 const char* leftclass = osrfHashGet(leftmeta, "classname");
2236 jsonObject* snode = NULL;
2237 jsonIterator* search_itr = jsonNewIterator( working_hash );
2239 while ( (snode = jsonIteratorNext( search_itr )) ) {
2240 const char* class = search_itr->key;
2241 osrfHash* idlClass = osrfHashGet( oilsIDL(), class );
2245 "%s: JOIN failed. No class \"%s\" defined in IDL",
2249 jsonIteratorFree( search_itr );
2250 buffer_free( join_buf );
2252 jsonObjectFree( freeable_hash );
2256 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2257 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2259 if (field && !fkey) {
2260 // Look up the corresponding join column in the IDL.
2261 // The link must be defined in the child table,
2262 // and point to the right parent table.
2263 osrfHash* idl_link = (osrfHash*) oilsIDLFindPath( "/%s/links/%s", class, field );
2264 const char* reltype = NULL;
2265 const char* other_class = NULL;
2266 reltype = osrfHashGet( idl_link, "reltype" );
2267 if( reltype && strcmp( reltype, "has_many" ) )
2268 other_class = osrfHashGet( idl_link, "class" );
2269 if( other_class && !strcmp( other_class, leftclass ) )
2270 fkey = osrfHashGet( idl_link, "key" );
2274 "%s: JOIN failed. No link defined from %s.%s to %s",
2280 buffer_free(join_buf);
2282 jsonObjectFree(freeable_hash);
2283 jsonIteratorFree(search_itr);
2287 } else if (!field && fkey) {
2288 // Look up the corresponding join column in the IDL.
2289 // The link must be defined in the child table,
2290 // and point to the right parent table.
2291 osrfHash* idl_link = (osrfHash*) oilsIDLFindPath( "/%s/links/%s", leftclass, fkey );
2292 const char* reltype = NULL;
2293 const char* other_class = NULL;
2294 reltype = osrfHashGet( idl_link, "reltype" );
2295 if( reltype && strcmp( reltype, "has_many" ) )
2296 other_class = osrfHashGet( idl_link, "class" );
2297 if( other_class && !strcmp( other_class, class ) )
2298 field = osrfHashGet( idl_link, "key" );
2302 "%s: JOIN failed. No link defined from %s.%s to %s",
2308 buffer_free(join_buf);
2310 jsonObjectFree(freeable_hash);
2311 jsonIteratorFree(search_itr);
2315 } else if (!field && !fkey) {
2316 osrfHash* _links = oilsIDL_links( leftclass );
2318 // For each link defined for the left class:
2319 // see if the link references the joined class
2320 osrfHashIterator* itr = osrfNewHashIterator( _links );
2321 osrfHash* curr_link = NULL;
2322 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2323 const char* other_class = osrfHashGet( curr_link, "class" );
2324 if( other_class && !strcmp( other_class, class ) ) {
2326 // In the IDL, the parent class doesn't know then names of the child
2327 // columns that are pointing to it, so don't use that end of the link
2328 const char* reltype = osrfHashGet( curr_link, "reltype" );
2329 if( reltype && strcmp( reltype, "has_many" ) ) {
2330 // Found a link between the classes
2331 fkey = osrfHashIteratorKey( itr );
2332 field = osrfHashGet( curr_link, "key" );
2337 osrfHashIteratorFree( itr );
2339 if (!field || !fkey) {
2340 // Do another such search, with the classes reversed
2341 _links = oilsIDL_links( class );
2343 // For each link defined for the joined class:
2344 // see if the link references the left class
2345 osrfHashIterator* itr = osrfNewHashIterator( _links );
2346 osrfHash* curr_link = NULL;
2347 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2348 const char* other_class = osrfHashGet( curr_link, "class" );
2349 if( other_class && !strcmp( other_class, leftclass ) ) {
2351 // In the IDL, the parent class doesn't know then names of the child
2352 // columns that are pointing to it, so don't use that end of the link
2353 const char* reltype = osrfHashGet( curr_link, "reltype" );
2354 if( reltype && strcmp( reltype, "has_many" ) ) {
2355 // Found a link between the classes
2356 field = osrfHashIteratorKey( itr );
2357 fkey = osrfHashGet( curr_link, "key" );
2362 osrfHashIteratorFree( itr );
2365 if (!field || !fkey) {
2368 "%s: JOIN failed. No link defined between %s and %s",
2373 buffer_free(join_buf);
2375 jsonObjectFree(freeable_hash);
2376 jsonIteratorFree(search_itr);
2382 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2384 if ( !strcasecmp(type,"left") ) {
2385 buffer_add(join_buf, " LEFT JOIN");
2386 } else if ( !strcasecmp(type,"right") ) {
2387 buffer_add(join_buf, " RIGHT JOIN");
2388 } else if ( !strcasecmp(type,"full") ) {
2389 buffer_add(join_buf, " FULL JOIN");
2391 buffer_add(join_buf, " INNER JOIN");
2394 buffer_add(join_buf, " INNER JOIN");
2397 char* table = getSourceDefinition(idlClass);
2399 jsonIteratorFree( search_itr );
2400 buffer_free( join_buf );
2402 jsonObjectFree( freeable_hash );
2406 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2407 table, class, class, field, leftclass, fkey);
2410 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2412 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2413 if ( filter_op && !strcasecmp("or",filter_op) ) {
2414 buffer_add( join_buf, " OR " );
2416 buffer_add( join_buf, " AND " );
2419 char* jpred = searchWHERE( filter, idlClass, AND_OP_JOIN, NULL );
2421 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2422 OSRF_BUFFER_ADD( join_buf, jpred );
2427 "%s: JOIN failed. Invalid conditional expression.",
2430 jsonIteratorFree( search_itr );
2431 buffer_free( join_buf );
2433 jsonObjectFree( freeable_hash );
2438 buffer_add(join_buf, " ) ");
2440 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2442 char* jpred = searchJOIN( join_filter, idlClass );
2444 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2445 OSRF_BUFFER_ADD( join_buf, jpred );
2448 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2449 jsonIteratorFree( search_itr );
2450 buffer_free( join_buf );
2452 jsonObjectFree( freeable_hash );
2459 jsonObjectFree(freeable_hash);
2460 jsonIteratorFree(search_itr);
2462 return buffer_release(join_buf);
2467 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2468 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2469 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2471 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2473 search_hash is the JSON expression of the conditions.
2474 meta is the class definition from the IDL, for the relevant table.
2475 opjoin_type indicates whether multiple conditions, if present, should be
2476 connected by AND or OR.
2477 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2478 to pass it to other functions -- and all they do with it is to use the session
2479 and request members to send error messages back to the client.
2483 static char* searchWHERE ( const jsonObject* search_hash, osrfHash* meta, int opjoin_type, osrfMethodContext* ctx ) {
2487 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2495 growing_buffer* sql_buf = buffer_init(128);
2497 jsonObject* node = NULL;
2500 if ( search_hash->type == JSON_ARRAY ) {
2501 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2502 jsonIterator* search_itr = jsonNewIterator( search_hash );
2503 if( !jsonIteratorHasNext( search_itr ) ) {
2506 "%s: Invalid predicate structure: empty JSON array",
2509 jsonIteratorFree( search_itr );
2510 buffer_free( sql_buf );
2514 while ( (node = jsonIteratorNext( search_itr )) ) {
2518 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2519 else buffer_add(sql_buf, " AND ");
2522 char* subpred = searchWHERE( node, meta, opjoin_type, ctx );
2524 buffer_fadd(sql_buf, "( %s )", subpred);
2527 jsonIteratorFree( search_itr );
2528 buffer_free( sql_buf );
2532 jsonIteratorFree(search_itr);
2534 } else if ( search_hash->type == JSON_HASH ) {
2535 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2536 jsonIterator* search_itr = jsonNewIterator( search_hash );
2537 if( !jsonIteratorHasNext( search_itr ) ) {
2540 "%s: Invalid predicate structure: empty JSON object",
2543 jsonIteratorFree( search_itr );
2544 buffer_free( sql_buf );
2548 while ( (node = jsonIteratorNext( search_itr )) ) {
2553 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2554 else buffer_add(sql_buf, " AND ");
2557 if ( '+' == search_itr->key[ 0 ] ) {
2558 if ( node->type == JSON_STRING ) {
2559 // Intended purpose; to allow reference to a Boolean column
2561 // Verify that the class alias is not empty
2562 if( '\0' == search_itr->key[ 1 ] ) {
2565 "%s: Table alias is empty",
2568 jsonIteratorFree( search_itr );
2569 buffer_free( sql_buf );
2573 // Verify that the string looks like an identifier.
2574 const char* subpred = jsonObjectGetString( node );
2575 if( ! is_identifier( subpred ) ) {
2578 "%s: Invalid boolean identifier in WHERE clause: \"%s\"",
2582 jsonIteratorFree( search_itr );
2583 buffer_free( sql_buf );
2587 buffer_fadd(sql_buf, " \"%s\".%s ", search_itr->key + 1, subpred);
2589 char* subpred = searchWHERE( node, osrfHashGet( oilsIDL(), search_itr->key + 1 ), AND_OP_JOIN, ctx );
2591 buffer_fadd(sql_buf, "( %s )", subpred);
2594 jsonIteratorFree( search_itr );
2595 buffer_free( sql_buf );
2599 } else if ( !strcasecmp("-or",search_itr->key) ) {
2600 char* subpred = searchWHERE( node, meta, OR_OP_JOIN, ctx );
2602 buffer_fadd(sql_buf, "( %s )", subpred);
2605 buffer_free( sql_buf );
2608 } else if ( !strcasecmp("-and",search_itr->key) ) {
2609 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2611 buffer_fadd(sql_buf, "( %s )", subpred);
2614 buffer_free( sql_buf );
2617 } else if ( !strcasecmp("-not",search_itr->key) ) {
2618 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2620 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2623 buffer_free( sql_buf );
2626 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2627 char* subpred = SELECT(
2629 jsonObjectGetKey( node, "select" ),
2630 jsonObjectGetKey( node, "from" ),
2631 jsonObjectGetKey( node, "where" ),
2632 jsonObjectGetKey( node, "having" ),
2633 jsonObjectGetKey( node, "order_by" ),
2634 jsonObjectGetKey( node, "limit" ),
2635 jsonObjectGetKey( node, "offset" ),
2640 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2643 buffer_free( sql_buf );
2646 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2647 char* subpred = SELECT(
2649 jsonObjectGetKey( node, "select" ),
2650 jsonObjectGetKey( node, "from" ),
2651 jsonObjectGetKey( node, "where" ),
2652 jsonObjectGetKey( node, "having" ),
2653 jsonObjectGetKey( node, "order_by" ),
2654 jsonObjectGetKey( node, "limit" ),
2655 jsonObjectGetKey( node, "offset" ),
2660 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2663 buffer_free( sql_buf );
2669 char* class = osrfHashGet(meta, "classname");
2670 osrfHash* fields = osrfHashGet(meta, "fields");
2671 osrfHash* field = osrfHashGet( fields, search_itr->key );
2675 char* table = getSourceDefinition(meta);
2677 table = strdup( "(?)" );
2680 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2686 buffer_free(sql_buf);
2688 jsonIteratorFree(search_itr);
2692 char* subpred = searchPredicate( class, field, node, ctx );
2694 buffer_add( sql_buf, subpred );
2697 buffer_free(sql_buf);
2698 jsonIteratorFree(search_itr);
2703 jsonIteratorFree(search_itr);
2706 // ERROR ... only hash and array allowed at this level
2707 char* predicate_string = jsonObjectToJSON( search_hash );
2710 "%s: Invalid predicate structure: %s",
2714 buffer_free(sql_buf);
2715 free(predicate_string);
2719 return buffer_release(sql_buf);
2722 // Return 1 if the class is in the FROM clause, or 0 if not
2723 static int is_joined( jsonObject* from_clause, const char* class ) {
2727 else if( from_clause->type == JSON_STRING ) {
2728 if( strcmp( class, jsonObjectGetString( from_clause ) ) )
2732 } else if( from_clause->type != JSON_HASH ) {
2734 } else { // Look at any subjoins
2735 jsonIterator* class_itr = jsonNewIterator( from_clause );
2736 jsonObject* curr_class;
2737 int rc = 0; // return code
2738 while ( ( curr_class = jsonIteratorNext( class_itr ) ) ) {
2739 if( ! strcmp( class_itr->key, class ) ) {
2743 jsonObject* subjoin = jsonObjectGetKey( curr_class, "join" );
2744 if( subjoin && is_joined( subjoin, class ) ) {
2750 jsonIteratorFree( class_itr );
2757 /* method context */ osrfMethodContext* ctx,
2759 /* SELECT */ jsonObject* selhash,
2760 /* FROM */ jsonObject* join_hash,
2761 /* WHERE */ jsonObject* search_hash,
2762 /* HAVING */ jsonObject* having_hash,
2763 /* ORDER BY */ jsonObject* order_hash,
2764 /* LIMIT */ jsonObject* limit,
2765 /* OFFSET */ jsonObject* offset,
2766 /* flags */ int flags
2768 const char* locale = osrf_message_get_last_locale();
2770 // in case we don't get a select list
2771 jsonObject* defaultselhash = NULL;
2773 // general tmp objects
2774 const jsonObject* tmp_const;
2775 jsonObject* selclass = NULL;
2776 jsonObject* selfield = NULL;
2777 jsonObject* snode = NULL;
2778 jsonObject* onode = NULL;
2780 char* string = NULL;
2781 int from_function = 0;
2786 // the core search class
2787 char* core_class = NULL;
2789 // metadata about the core search class
2790 osrfHash* core_meta = NULL;
2792 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale);
2794 // punt if there's no core class
2795 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2798 "%s: FROM clause is missing or empty",
2802 osrfAppSessionStatus(
2804 OSRF_STATUS_INTERNALSERVERERROR,
2805 "osrfMethodException",
2807 "FROM clause is missing or empty in JSON query"
2812 // get the core class -- the only key of the top level FROM clause, or a string
2813 if (join_hash->type == JSON_HASH) {
2814 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2815 snode = jsonIteratorNext( tmp_itr );
2817 core_class = strdup( tmp_itr->key );
2820 jsonObject* extra = jsonIteratorNext( tmp_itr );
2822 jsonIteratorFree( tmp_itr );
2825 // There shouldn't be more than one entry in join_hash
2829 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2833 osrfAppSessionStatus(
2835 OSRF_STATUS_INTERNALSERVERERROR,
2836 "osrfMethodException",
2838 "Malformed FROM clause in JSON query"
2841 return NULL; // Malformed join_hash; extra entry
2843 } else if (join_hash->type == JSON_ARRAY) {
2845 core_class = jsonObjectToSimpleString( jsonObjectGetIndex(join_hash, 0) );
2848 } else if (join_hash->type == JSON_STRING) {
2849 core_class = jsonObjectToSimpleString( join_hash );
2855 "%s: FROM clause is unexpected JSON type: %s",
2857 json_type( join_hash->type )
2860 osrfAppSessionStatus(
2862 OSRF_STATUS_INTERNALSERVERERROR,
2863 "osrfMethodException",
2865 "Ill-formed FROM clause in JSON query"
2871 if (!from_function) {
2872 // Get the IDL class definition for the core class
2873 core_meta = osrfHashGet( oilsIDL(), core_class );
2874 if( !core_meta ) { // Didn't find it?
2877 "%s: SELECT clause references undefined class: \"%s\"",
2882 osrfAppSessionStatus(
2884 OSRF_STATUS_INTERNALSERVERERROR,
2885 "osrfMethodException",
2887 "SELECT clause references undefined class in JSON query"
2893 // Make sure the class isn't virtual
2894 if( str_is_true( osrfHashGet( core_meta, "virtual" ) ) ) {
2897 "%s: Core class is virtual: \"%s\"",
2902 osrfAppSessionStatus(
2904 OSRF_STATUS_INTERNALSERVERERROR,
2905 "osrfMethodException",
2907 "FROM clause references virtual class in JSON query"
2914 // if the select list is empty, or the core class field list is '*',
2915 // build the default select list ...
2917 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
2918 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2919 } else if( selhash->type != JSON_HASH ) {
2922 "%s: Expected JSON_HASH for SELECT clause; found %s",
2924 json_type( selhash->type )
2928 osrfAppSessionStatus(
2930 OSRF_STATUS_INTERNALSERVERERROR,
2931 "osrfMethodException",
2933 "Malformed SELECT clause in JSON query"
2937 } else if ( (tmp_const = jsonObjectGetKeyConst( selhash, core_class )) && tmp_const->type == JSON_STRING ) {
2938 const char* _x = jsonObjectGetString( tmp_const );
2939 if (!strncmp( "*", _x, 1 )) {
2940 jsonObjectRemoveKey( selhash, core_class );
2941 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2946 growing_buffer* sql_buf = buffer_init(128);
2948 // temp buffers for the SELECT list and GROUP BY clause
2949 growing_buffer* select_buf = buffer_init(128);
2950 growing_buffer* group_buf = buffer_init(128);
2952 int aggregate_found = 0; // boolean
2954 // Build a select list
2955 if(from_function) // From a function we select everything
2956 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
2959 // If we need to build a default list, prepare to do so
2960 jsonObject* _tmp = jsonObjectGetKey( selhash, core_class );
2961 if ( _tmp && !_tmp->size ) {
2963 osrfHash* core_fields = osrfHashGet( core_meta, "fields" );
2965 osrfHashIterator* field_itr = osrfNewHashIterator( core_fields );
2966 osrfHash* field_def;
2967 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2968 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2969 // This field is not virtual, so add it to the list
2970 jsonObjectPush( _tmp, jsonNewObject( osrfHashIteratorKey( field_itr ) ) );
2973 osrfHashIteratorFree( field_itr );
2976 // Now build the actual select list
2980 jsonIterator* selclass_itr = jsonNewIterator( selhash );
2981 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
2983 const char* cname = selclass_itr->key;
2985 // Make sure the target relation is in the FROM clause.
2987 // At this point join_hash is a step down from the join_hash we
2988 // received as a parameter. If the original was a JSON_STRING,
2989 // then json_hash is now NULL. If the original was a JSON_HASH,
2990 // then json_hash is now the first (and only) entry in it,
2991 // denoting the core class. We've already excluded the
2992 // possibility that the original was a JSON_ARRAY, because in
2993 // that case from_function would be non-NULL, and we wouldn't
2996 // If the current class isn't the core class
2997 // and it isn't in the join tree, bail out
2998 if ( strcmp( core_class, cname ) && ! is_joined( join_hash, cname ) ) {
3001 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3006 osrfAppSessionStatus(
3008 OSRF_STATUS_INTERNALSERVERERROR,
3009 "osrfMethodException",
3011 "Selected class not in FROM clause in JSON query"
3013 jsonIteratorFree( selclass_itr );
3014 buffer_free( sql_buf );
3015 buffer_free( select_buf );
3016 buffer_free( group_buf );
3017 if( defaultselhash ) jsonObjectFree( defaultselhash );
3022 // Look up some attributes of the current class, so that we
3023 // don't have to look them up again for each field
3024 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
3025 osrfHash* class_field_set = osrfHashGet( idlClass, "fields" );
3026 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3027 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3029 // stitch together the column list ...
3030 jsonIterator* select_itr = jsonNewIterator( selclass );
3031 while ( (selfield = jsonIteratorNext( select_itr )) ) { // for each SELECT column
3033 // If we need a separator comma, add one
3037 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3040 // ... if it's a string, just toss it on the pile
3041 if (selfield->type == JSON_STRING) {
3043 // Look up the field in the IDL
3044 const char* col_name = jsonObjectGetString( selfield );
3045 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3047 // No such field in current class
3050 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3056 osrfAppSessionStatus(
3058 OSRF_STATUS_INTERNALSERVERERROR,
3059 "osrfMethodException",
3061 "Selected column not defined in JSON query"
3063 jsonIteratorFree( select_itr );
3064 jsonIteratorFree( selclass_itr );
3065 buffer_free( sql_buf );
3066 buffer_free( select_buf );
3067 buffer_free( group_buf );
3068 if( defaultselhash ) jsonObjectFree( defaultselhash );
3071 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3072 // Virtual field not allowed
3075 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3081 osrfAppSessionStatus(
3083 OSRF_STATUS_INTERNALSERVERERROR,
3084 "osrfMethodException",
3086 "Selected column may not be virtual in JSON query"
3088 jsonIteratorFree( select_itr );
3089 jsonIteratorFree( selclass_itr );
3090 buffer_free( sql_buf );
3091 buffer_free( select_buf );
3092 buffer_free( group_buf );
3093 if( defaultselhash ) jsonObjectFree( defaultselhash );
3100 if (flags & DISABLE_I18N)
3103 i18n = osrfHashGet(field_def, "i18n");
3105 if( str_is_true( i18n ) ) {
3106 buffer_fadd( select_buf,
3107 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3108 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3110 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3113 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3116 // ... but it could be an object, in which case we check for a Field Transform
3117 } else if (selfield->type == JSON_HASH) {
3119 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3121 // Get the field definition from the IDL
3122 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3124 // No such field in current class
3127 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3133 osrfAppSessionStatus(
3135 OSRF_STATUS_INTERNALSERVERERROR,
3136 "osrfMethodException",
3138 "Selected column is not defined in JSON query"
3140 jsonIteratorFree( select_itr );
3141 jsonIteratorFree( selclass_itr );
3142 buffer_free( sql_buf );
3143 buffer_free( select_buf );
3144 buffer_free( group_buf );
3145 if( defaultselhash ) jsonObjectFree( defaultselhash );
3148 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3149 // No such field in current class
3152 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3158 osrfAppSessionStatus(
3160 OSRF_STATUS_INTERNALSERVERERROR,
3161 "osrfMethodException",
3163 "Selected column is virtual in JSON query"
3165 jsonIteratorFree( select_itr );
3166 jsonIteratorFree( selclass_itr );
3167 buffer_free( sql_buf );
3168 buffer_free( select_buf );
3169 buffer_free( group_buf );
3170 if( defaultselhash ) jsonObjectFree( defaultselhash );
3175 // Decide what to use as a column alias
3177 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3178 _alias = jsonObjectGetString( tmp_const );
3179 } else { // Use field name as the alias
3183 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3184 char* transform_str = searchFieldTransform(cname, field_def, selfield);
3185 if( transform_str ) {
3186 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3187 free(transform_str);
3190 osrfAppSessionStatus(
3192 OSRF_STATUS_INTERNALSERVERERROR,
3193 "osrfMethodException",
3195 "Unable to generate transform function in JSON query"
3197 jsonIteratorFree( select_itr );
3198 jsonIteratorFree( selclass_itr );
3199 buffer_free( sql_buf );
3200 buffer_free( select_buf );
3201 buffer_free( group_buf );
3202 if( defaultselhash ) jsonObjectFree( defaultselhash );
3210 if (flags & DISABLE_I18N)
3213 i18n = osrfHashGet(field_def, "i18n");
3215 if( str_is_true( i18n ) ) {
3216 buffer_fadd( select_buf,
3217 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3218 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3220 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3223 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3230 "%s: Selected item is unexpected JSON type: %s",
3232 json_type( selfield->type )
3235 osrfAppSessionStatus(
3237 OSRF_STATUS_INTERNALSERVERERROR,
3238 "osrfMethodException",
3240 "Ill-formed SELECT item in JSON query"
3242 jsonIteratorFree( select_itr );
3243 jsonIteratorFree( selclass_itr );
3244 buffer_free( sql_buf );
3245 buffer_free( select_buf );
3246 buffer_free( group_buf );
3247 if( defaultselhash ) jsonObjectFree( defaultselhash );
3252 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3253 if( obj_is_true( agg_obj ) )
3254 aggregate_found = 1;
3256 // Append a comma (except for the first one)
3257 // and add the column to a GROUP BY clause
3261 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3263 buffer_fadd(group_buf, " %d", sel_pos);
3267 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3269 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3270 if ( ! obj_is_true( aggregate_obj ) ) {
3274 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3277 buffer_fadd(group_buf, " %d", sel_pos);
3280 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3284 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3287 _column = searchFieldTransform(cname, field, selfield);
3288 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3289 OSRF_BUFFER_ADD(group_buf, _column);
3290 _column = searchFieldTransform(cname, field, selfield);
3297 } // end while -- iterating across SELECT columns
3299 jsonIteratorFree(select_itr);
3300 } // end while -- iterating across classes
3302 jsonIteratorFree(selclass_itr);
3306 char* col_list = buffer_release(select_buf);
3308 if (from_function) table = searchValueTransform(join_hash);
3309 else table = getSourceDefinition(core_meta);
3313 osrfAppSessionStatus(
3315 OSRF_STATUS_INTERNALSERVERERROR,
3316 "osrfMethodException",
3318 "Unable to identify table for core class"
3321 buffer_free( sql_buf );
3322 buffer_free( group_buf );
3323 if( defaultselhash ) jsonObjectFree( defaultselhash );
3328 // Put it all together
3329 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3333 char* order_by_list = NULL;
3334 char* having_buf = NULL;
3336 if (!from_function) {
3338 // Now, walk the join tree and add that clause
3340 char* join_clause = searchJOIN( join_hash, core_meta );
3342 buffer_add(sql_buf, join_clause);
3346 osrfAppSessionStatus(
3348 OSRF_STATUS_INTERNALSERVERERROR,
3349 "osrfMethodException",
3351 "Unable to construct JOIN clause(s)"
3353 buffer_free( sql_buf );
3354 buffer_free( group_buf );
3355 if( defaultselhash ) jsonObjectFree( defaultselhash );
3361 // Build a WHERE clause, if there is one
3362 if ( search_hash ) {
3363 buffer_add(sql_buf, " WHERE ");
3365 // and it's on the WHERE clause
3366 char* pred = searchWHERE( search_hash, core_meta, AND_OP_JOIN, ctx );
3369 buffer_add(sql_buf, pred);
3373 osrfAppSessionStatus(
3375 OSRF_STATUS_INTERNALSERVERERROR,
3376 "osrfMethodException",
3378 "Severe query error in WHERE predicate -- see error log for more details"
3382 buffer_free(group_buf);
3383 buffer_free(sql_buf);
3384 if (defaultselhash) jsonObjectFree(defaultselhash);
3389 // Build a HAVING clause, if there is one
3390 if ( having_hash ) {
3392 // and it's on the the WHERE clause
3393 having_buf = searchWHERE( having_hash, core_meta, AND_OP_JOIN, ctx );
3395 if( ! having_buf ) {
3397 osrfAppSessionStatus(
3399 OSRF_STATUS_INTERNALSERVERERROR,
3400 "osrfMethodException",
3402 "Severe query error in HAVING predicate -- see error log for more details"
3406 buffer_free(group_buf);
3407 buffer_free(sql_buf);
3408 if (defaultselhash) jsonObjectFree(defaultselhash);
3413 growing_buffer* order_buf = NULL; // to collect ORDER BY list
3415 // Build an ORDER BY clause, if there is one
3416 if( NULL == order_hash )
3417 ; // No ORDER BY? do nothing
3418 else if( JSON_ARRAY == order_hash->type ) {
3419 // Array of field specifications, each specification being a
3420 // hash to define the class, field, and other details
3422 jsonObject* order_spec;
3423 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
3425 if( JSON_HASH != order_spec->type ) {
3426 osrfLogError(OSRF_LOG_MARK,
3427 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
3428 MODULENAME, json_type( order_spec->type ) );
3430 osrfAppSessionStatus(
3432 OSRF_STATUS_INTERNALSERVERERROR,
3433 "osrfMethodException",
3435 "Malformed ORDER BY clause -- see error log for more details"
3437 buffer_free( order_buf );
3440 buffer_free(group_buf);
3441 buffer_free(sql_buf);
3442 if (defaultselhash) jsonObjectFree(defaultselhash);
3447 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
3449 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
3452 OSRF_BUFFER_ADD(order_buf, ", ");
3454 order_buf = buffer_init(128);
3456 if( !field || !class ) {
3457 osrfLogError(OSRF_LOG_MARK,
3458 "%s: Missing class or field name in field specification of ORDER BY clause",
3461 osrfAppSessionStatus(
3463 OSRF_STATUS_INTERNALSERVERERROR,
3464 "osrfMethodException",
3466 "Malformed ORDER BY clause -- see error log for more details"
3468 buffer_free( order_buf );
3471 buffer_free(group_buf);
3472 buffer_free(sql_buf);
3473 if (defaultselhash) jsonObjectFree(defaultselhash);
3477 if ( ! jsonObjectGetKeyConst( selhash, class )
3478 && strcmp( core_class, class )
3479 && ! is_joined( join_hash, class ) ) {
3480 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
3481 "not in FROM clause", MODULENAME, class );
3483 osrfAppSessionStatus(
3485 OSRF_STATUS_INTERNALSERVERERROR,
3486 "osrfMethodException",
3488 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3492 buffer_free(group_buf);
3493 buffer_free(sql_buf);
3494 if (defaultselhash) jsonObjectFree(defaultselhash);
3498 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s", class, field );
3500 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
3501 MODULENAME, class, field );
3503 osrfAppSessionStatus(
3505 OSRF_STATUS_INTERNALSERVERERROR,
3506 "osrfMethodException",
3508 "Invalid field referenced in ORDER BY clause -- see error log for more details"
3512 buffer_free(group_buf);
3513 buffer_free(sql_buf);
3514 if (defaultselhash) jsonObjectFree(defaultselhash);
3516 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3517 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3518 MODULENAME, field );
3520 osrfAppSessionStatus(
3522 OSRF_STATUS_INTERNALSERVERERROR,
3523 "osrfMethodException",
3525 "Virtual field in ORDER BY clause -- see error log for more details"
3527 buffer_free( order_buf );
3530 buffer_free(group_buf);
3531 buffer_free(sql_buf);
3532 if (defaultselhash) jsonObjectFree(defaultselhash);
3536 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
3537 char* transform_str = searchFieldTransform( class, field_def, order_spec );
3538 if( ! transform_str ) {
3540 osrfAppSessionStatus(
3542 OSRF_STATUS_INTERNALSERVERERROR,
3543 "osrfMethodException",
3545 "Severe query error in ORDER BY clause -- see error log for more details"
3547 buffer_free( order_buf );
3550 buffer_free(group_buf);
3551 buffer_free(sql_buf);
3552 if (defaultselhash) jsonObjectFree(defaultselhash);
3556 OSRF_BUFFER_ADD( order_buf, transform_str );
3557 free( transform_str );
3560 buffer_fadd( order_buf, "\"%s\".%s", class, field );
3562 const char* direction =
3563 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
3565 if( direction[ 0 ] || 'D' == direction[ 0 ] )
3566 OSRF_BUFFER_ADD( order_buf, " DESC" );
3568 OSRF_BUFFER_ADD( order_buf, " ASC" );
3571 } else if( JSON_HASH == order_hash->type ) {
3572 // This hash is keyed on class name. Each class has either
3573 // an array of field names or a hash keyed on field name.
3574 jsonIterator* class_itr = jsonNewIterator( order_hash );
3575 while ( (snode = jsonIteratorNext( class_itr )) ) {
3577 if ( ! jsonObjectGetKeyConst( selhash,class_itr->key )
3578 && strcmp( core_class, class_itr->key )
3579 && ! is_joined( join_hash, class_itr->key ) ) {
3580 osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
3581 MODULENAME, class_itr->key );
3583 osrfAppSessionStatus(
3585 OSRF_STATUS_INTERNALSERVERERROR,
3586 "osrfMethodException",
3588 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3590 jsonIteratorFree( class_itr );
3591 buffer_free( order_buf );
3594 buffer_free(group_buf);
3595 buffer_free(sql_buf);
3596 if (defaultselhash) jsonObjectFree(defaultselhash);
3600 osrfHash* field_list_def = oilsIDLFindPath( "/%s/fields", class_itr->key );
3602 if ( snode->type == JSON_HASH ) {
3604 // Hash is keyed on field names from the current class. For each field
3605 // there is another layer of hash to define the sorting details, if any,
3606 // or a string to indicate direction of sorting.
3607 jsonIterator* order_itr = jsonNewIterator( snode );
3608 while ( (onode = jsonIteratorNext( order_itr )) ) {
3610 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
3612 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3613 MODULENAME, order_itr->key );
3615 osrfAppSessionStatus(
3617 OSRF_STATUS_INTERNALSERVERERROR,
3618 "osrfMethodException",
3620 "Invalid field in ORDER BY clause -- see error log for more details"
3622 jsonIteratorFree( order_itr );
3623 jsonIteratorFree( class_itr );
3624 buffer_free( order_buf );
3627 buffer_free(group_buf);
3628 buffer_free(sql_buf);
3629 if (defaultselhash) jsonObjectFree(defaultselhash);
3631 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3632 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3633 MODULENAME, order_itr->key );
3635 osrfAppSessionStatus(
3637 OSRF_STATUS_INTERNALSERVERERROR,
3638 "osrfMethodException",
3640 "Virtual field in ORDER BY clause -- see error log for more details"
3642 jsonIteratorFree( order_itr );
3643 jsonIteratorFree( class_itr );
3644 buffer_free( order_buf );
3647 buffer_free(group_buf);
3648 buffer_free(sql_buf);
3649 if (defaultselhash) jsonObjectFree(defaultselhash);
3653 const char* direction = NULL;
3654 if ( onode->type == JSON_HASH ) {
3655 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3656 string = searchFieldTransform(
3658 osrfHashGet( field_list_def, order_itr->key ),
3662 if( ctx ) osrfAppSessionStatus(
3664 OSRF_STATUS_INTERNALSERVERERROR,
3665 "osrfMethodException",
3667 "Severe query error in ORDER BY clause -- see error log for more details"
3669 jsonIteratorFree( order_itr );
3670 jsonIteratorFree( class_itr );
3673 buffer_free(group_buf);
3674 buffer_free(order_buf);
3675 buffer_free(sql_buf);
3676 if (defaultselhash) jsonObjectFree(defaultselhash);
3680 growing_buffer* field_buf = buffer_init(16);
3681 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3682 string = buffer_release(field_buf);
3685 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3686 const char* dir = jsonObjectGetString(tmp_const);
3687 if (!strncasecmp(dir, "d", 1)) {
3688 direction = " DESC";
3694 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
3695 osrfLogError( OSRF_LOG_MARK,
3696 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
3697 MODULENAME, json_type( onode->type ) );
3699 osrfAppSessionStatus(
3701 OSRF_STATUS_INTERNALSERVERERROR,
3702 "osrfMethodException",
3704 "Malformed ORDER BY clause -- see error log for more details"
3706 jsonIteratorFree( order_itr );
3707 jsonIteratorFree( class_itr );
3710 buffer_free(group_buf);
3711 buffer_free(order_buf);
3712 buffer_free(sql_buf);
3713 if (defaultselhash) jsonObjectFree(defaultselhash);
3717 string = strdup(order_itr->key);
3718 const char* dir = jsonObjectGetString(onode);
3719 if (!strncasecmp(dir, "d", 1)) {
3720 direction = " DESC";
3727 OSRF_BUFFER_ADD(order_buf, ", ");
3729 order_buf = buffer_init(128);
3731 OSRF_BUFFER_ADD(order_buf, string);
3735 OSRF_BUFFER_ADD(order_buf, direction);
3739 jsonIteratorFree(order_itr);
3741 } else if ( snode->type == JSON_ARRAY ) {
3743 // Array is a list of fields from the current class
3744 unsigned long order_idx = 0;
3745 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
3747 const char* _f = jsonObjectGetString( onode );
3749 osrfHash* field_def = osrfHashGet( field_list_def, _f );
3751 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3754 osrfAppSessionStatus(
3756 OSRF_STATUS_INTERNALSERVERERROR,
3757 "osrfMethodException",
3759 "Invalid field in ORDER BY clause -- see error log for more details"
3761 jsonIteratorFree( class_itr );
3762 buffer_free( order_buf );
3765 buffer_free(group_buf);
3766 buffer_free(sql_buf);
3767 if (defaultselhash) jsonObjectFree(defaultselhash);
3769 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3770 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3773 osrfAppSessionStatus(
3775 OSRF_STATUS_INTERNALSERVERERROR,
3776 "osrfMethodException",
3778 "Virtual field in ORDER BY clause -- see error log for more details"
3780 jsonIteratorFree( class_itr );
3781 buffer_free( order_buf );
3784 buffer_free(group_buf);
3785 buffer_free(sql_buf);
3786 if (defaultselhash) jsonObjectFree(defaultselhash);
3791 OSRF_BUFFER_ADD(order_buf, ", ");
3793 order_buf = buffer_init(128);
3795 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
3799 // IT'S THE OOOOOOOOOOOLD STYLE!
3801 osrfLogError(OSRF_LOG_MARK,
3802 "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3804 osrfAppSessionStatus(
3806 OSRF_STATUS_INTERNALSERVERERROR,
3807 "osrfMethodException",
3809 "Severe query error -- see error log for more details"
3815 buffer_free(group_buf);
3816 buffer_free(order_buf);
3817 buffer_free(sql_buf);
3818 if (defaultselhash) jsonObjectFree(defaultselhash);
3819 jsonIteratorFree(class_itr);
3823 jsonIteratorFree( class_itr );
3825 osrfLogError(OSRF_LOG_MARK,
3826 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
3827 MODULENAME, json_type( order_hash->type ) );
3829 osrfAppSessionStatus(
3831 OSRF_STATUS_INTERNALSERVERERROR,
3832 "osrfMethodException",
3834 "Malformed ORDER BY clause -- see error log for more details"
3836 buffer_free( order_buf );
3839 buffer_free(group_buf);
3840 buffer_free(sql_buf);
3841 if (defaultselhash) jsonObjectFree(defaultselhash);
3846 order_by_list = buffer_release( order_buf );
3850 string = buffer_release(group_buf);
3852 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
3853 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3854 OSRF_BUFFER_ADD( sql_buf, string );
3859 if( having_buf && *having_buf ) {
3860 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3861 OSRF_BUFFER_ADD( sql_buf, having_buf );
3865 if( order_by_list ) {
3867 if ( *order_by_list ) {
3868 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3869 OSRF_BUFFER_ADD( sql_buf, order_by_list );
3872 free( order_by_list );
3876 const char* str = jsonObjectGetString(limit);
3877 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
3881 const char* str = jsonObjectGetString(offset);
3882 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
3885 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3888 if (defaultselhash) jsonObjectFree(defaultselhash);
3890 return buffer_release(sql_buf);
3894 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
3896 const char* locale = osrf_message_get_last_locale();
3898 osrfHash* fields = osrfHashGet(meta, "fields");
3899 char* core_class = osrfHashGet(meta, "classname");
3901 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
3903 jsonObject* node = NULL;
3904 jsonObject* snode = NULL;
3905 jsonObject* onode = NULL;
3906 const jsonObject* _tmp = NULL;
3907 jsonObject* selhash = NULL;
3908 jsonObject* defaultselhash = NULL;
3910 growing_buffer* sql_buf = buffer_init(128);
3911 growing_buffer* select_buf = buffer_init(128);
3913 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
3914 defaultselhash = jsonNewObjectType(JSON_HASH);
3915 selhash = defaultselhash;
3918 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
3919 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
3920 jsonObject* flist = jsonObjectGetKey( selhash, core_class );
3925 osrfStringArray* keys = osrfHashKeys( fields );
3926 while ( (field = osrfStringArrayGetString(keys, i++)) ) {
3927 if( ! str_is_true( osrfHashGet( osrfHashGet( fields, field ), "virtual" ) ) )
3928 jsonObjectPush( flist, jsonNewObject( field ) );
3930 osrfStringArrayFree(keys);
3934 jsonIterator* class_itr = jsonNewIterator( selhash );
3935 while ( (snode = jsonIteratorNext( class_itr )) ) {
3937 char* cname = class_itr->key;
3938 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
3939 if (!idlClass) continue;
3941 if (strcmp(core_class,class_itr->key)) {
3942 if (!join_hash) continue;
3944 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
3946 jsonObjectFree(found);
3950 jsonObjectFree(found);
3953 jsonIterator* select_itr = jsonNewIterator( snode );
3954 while ( (node = jsonIteratorNext( select_itr )) ) {
3955 const char* item_str = jsonObjectGetString( node );
3956 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
3957 char* fname = osrfHashGet(field, "name");
3959 if (!field) continue;
3964 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
3969 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
3970 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
3973 i18n = osrfHashGet(field, "i18n");
3975 if( str_is_true( i18n ) ) {
3976 char* pkey = osrfHashGet(idlClass, "primarykey");
3977 char* tname = osrfHashGet(idlClass, "tablename");
3979 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);
3981 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3984 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3988 jsonIteratorFree(select_itr);
3991 jsonIteratorFree(class_itr);
3993 char* col_list = buffer_release(select_buf);
3994 char* table = getSourceDefinition(meta);
3996 table = strdup( "(null)" );
3998 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4003 char* join_clause = searchJOIN( join_hash, meta );
4004 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
4005 OSRF_BUFFER_ADD(sql_buf, join_clause);
4009 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4010 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
4012 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
4014 char* pred = searchWHERE( search_hash, meta, AND_OP_JOIN, ctx );
4016 osrfAppSessionStatus(
4018 OSRF_STATUS_INTERNALSERVERERROR,
4019 "osrfMethodException",
4021 "Severe query error -- see error log for more details"
4023 buffer_free(sql_buf);
4024 if(defaultselhash) jsonObjectFree(defaultselhash);
4027 buffer_add(sql_buf, pred);
4032 char* string = NULL;
4033 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4035 growing_buffer* order_buf = buffer_init(128);
4038 jsonIterator* class_itr = jsonNewIterator( _tmp );
4039 while ( (snode = jsonIteratorNext( class_itr )) ) {
4041 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4044 if ( snode->type == JSON_HASH ) {
4046 jsonIterator* order_itr = jsonNewIterator( snode );
4047 while ( (onode = jsonIteratorNext( order_itr )) ) {
4049 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4050 class_itr->key, order_itr->key );
4054 char* direction = NULL;
4055 if ( onode->type == JSON_HASH ) {
4056 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4057 string = searchFieldTransform( class_itr->key, field_def, onode );
4059 osrfAppSessionStatus(
4061 OSRF_STATUS_INTERNALSERVERERROR,
4062 "osrfMethodException",
4064 "Severe query error in ORDER BY clause -- see error log for more details"
4066 jsonIteratorFree( order_itr );
4067 jsonIteratorFree( class_itr );
4068 buffer_free( order_buf );
4069 buffer_free( sql_buf );
4070 if( defaultselhash ) jsonObjectFree( defaultselhash );
4074 growing_buffer* field_buf = buffer_init(16);
4075 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4076 string = buffer_release(field_buf);
4079 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4080 const char* dir = jsonObjectGetString(_tmp);
4081 if (!strncasecmp(dir, "d", 1)) {
4082 direction = " DESC";
4089 string = strdup(order_itr->key);
4090 const char* dir = jsonObjectGetString(onode);
4091 if (!strncasecmp(dir, "d", 1)) {
4092 direction = " DESC";
4101 buffer_add(order_buf, ", ");
4104 buffer_add(order_buf, string);
4108 buffer_add(order_buf, direction);
4113 jsonIteratorFree(order_itr);
4116 const char* str = jsonObjectGetString(snode);
4117 buffer_add(order_buf, str);
4123 jsonIteratorFree(class_itr);
4125 string = buffer_release(order_buf);
4128 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4129 OSRF_BUFFER_ADD( sql_buf, string );
4135 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4136 const char* str = jsonObjectGetString(_tmp);
4144 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4146 const char* str = jsonObjectGetString(_tmp);
4155 if (defaultselhash) jsonObjectFree(defaultselhash);
4157 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4158 return buffer_release(sql_buf);
4161 int doJSONSearch ( osrfMethodContext* ctx ) {
4162 if(osrfMethodVerifyContext( ctx )) {
4163 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4167 osrfLogDebug(OSRF_LOG_MARK, "Recieved query request");
4172 dbhandle = writehandle;
4174 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4178 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4179 flags |= SELECT_DISTINCT;
4181 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4182 flags |= DISABLE_I18N;
4184 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4187 jsonObjectGetKey( hash, "select" ),
4188 jsonObjectGetKey( hash, "from" ),
4189 jsonObjectGetKey( hash, "where" ),
4190 jsonObjectGetKey( hash, "having" ),
4191 jsonObjectGetKey( hash, "order_by" ),
4192 jsonObjectGetKey( hash, "limit" ),
4193 jsonObjectGetKey( hash, "offset" ),
4202 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4203 dbi_result result = dbi_conn_query(dbhandle, sql);
4206 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4208 if (dbi_result_first_row(result)) {
4209 /* JSONify the result */
4210 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4213 jsonObject* return_val = oilsMakeJSONFromResult( result );
4214 osrfAppRespond( ctx, return_val );
4215 jsonObjectFree( return_val );
4216 } while (dbi_result_next_row(result));
4219 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4222 osrfAppRespondComplete( ctx, NULL );
4224 /* clean up the query */
4225 dbi_result_free(result);
4229 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4230 osrfAppSessionStatus(
4232 OSRF_STATUS_INTERNALSERVERERROR,
4233 "osrfMethodException",
4235 "Severe query error -- see error log for more details"
4243 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4244 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4247 dbhandle = writehandle;
4249 osrfHash* links = osrfHashGet(meta, "links");
4250 osrfHash* fields = osrfHashGet(meta, "fields");
4251 char* core_class = osrfHashGet(meta, "classname");
4252 char* pkey = osrfHashGet(meta, "primarykey");
4254 const jsonObject* _tmp;
4257 char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4259 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4264 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4266 dbi_result result = dbi_conn_query(dbhandle, sql);
4267 if( NULL == result ) {
4268 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4269 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4270 osrfAppSessionStatus(
4272 OSRF_STATUS_INTERNALSERVERERROR,
4273 "osrfMethodException",
4275 "Severe query error -- see error log for more details"
4282 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4285 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
4286 osrfHash* dedup = osrfNewHash();
4288 if (dbi_result_first_row(result)) {
4289 /* JSONify the result */
4290 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4292 obj = oilsMakeFieldmapperFromResult( result, meta );
4293 char* pkey_val = oilsFMGetString( obj, pkey );
4294 if ( osrfHashGet( dedup, pkey_val ) ) {
4295 jsonObjectFree(obj);
4298 osrfHashSet( dedup, pkey_val, pkey_val );
4299 jsonObjectPush(res_list, obj);
4301 } while (dbi_result_next_row(result));
4303 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
4307 osrfHashFree(dedup);
4308 /* clean up the query */
4309 dbi_result_free(result);
4312 if (res_list->size && query_hash) {
4313 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
4315 int x = (int)jsonObjectGetNumber(_tmp);
4316 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
4318 const jsonObject* temp_blob;
4319 if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
4321 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
4322 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
4324 osrfStringArray* link_fields = NULL;
4327 if (flesh_fields->size == 1) {
4328 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
4329 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
4334 link_fields = osrfNewStringArray(1);
4335 jsonIterator* _i = jsonNewIterator( flesh_fields );
4336 while ((_f = jsonIteratorNext( _i ))) {
4337 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4339 jsonIteratorFree(_i);
4344 unsigned long res_idx = 0;
4345 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
4350 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4352 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4354 osrfHash* kid_link = osrfHashGet(links, link_field);
4355 if (!kid_link) continue;
4357 osrfHash* field = osrfHashGet(fields, link_field);
4358 if (!field) continue;
4360 osrfHash* value_field = field;
4362 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4363 if (!kid_idl) continue;
4365 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4366 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4369 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4370 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4373 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4375 if (link_map->size > 0) {
4376 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4379 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4384 osrfHashGet(kid_link, "class"),
4391 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4392 osrfHashGet(kid_link, "field"),
4393 osrfHashGet(kid_link, "class"),
4394 osrfHashGet(kid_link, "key"),
4395 osrfHashGet(kid_link, "reltype")
4398 const char* search_key = jsonObjectGetString(
4401 atoi( osrfHashGet(value_field, "array_position") )
4406 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4410 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
4412 // construct WHERE clause
4413 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
4416 osrfHashGet(kid_link, "key"),
4417 jsonNewObject( search_key )
4420 // construct the rest of the query
4421 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
4422 jsonObjectSetKey( rest_of_query, "flesh",
4423 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4427 jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
4429 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
4430 jsonObjectSetKey( rest_of_query, "order_by",
4431 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
4435 if (jsonObjectGetKeyConst(query_hash, "select")) {
4436 jsonObjectSetKey( rest_of_query, "select",
4437 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
4441 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
4442 where_clause, rest_of_query, err);
4444 jsonObjectFree( where_clause );
4445 jsonObjectFree( rest_of_query );
4448 osrfStringArrayFree(link_fields);
4449 jsonObjectFree(res_list);
4450 jsonObjectFree(flesh_blob);
4454 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4456 jsonObject* X = NULL;
4457 if ( link_map->size > 0 && kids->size > 0 ) {
4459 kids = jsonNewObjectType(JSON_ARRAY);
4461 jsonObject* _k_node;
4462 unsigned long res_idx = 0;
4463 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
4469 (unsigned long)atoi(
4475 osrfHashGet(kid_link, "class")
4479 osrfStringArrayGetString( link_map, 0 )
4487 } // end while loop traversing X
4490 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4491 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4494 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4495 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4499 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4500 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4503 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4504 jsonObjectClone( kids )
4509 jsonObjectFree(kids);
4513 jsonObjectFree( kids );
4515 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4516 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4519 } // end while loop traversing res_list
4520 jsonObjectFree( flesh_blob );
4521 osrfStringArrayFree(link_fields);
4530 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
4532 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4534 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
4536 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
4539 if (!verifyObjectClass(ctx, target)) {
4544 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4545 osrfAppSessionStatus(
4547 OSRF_STATUS_BADREQUEST,
4548 "osrfMethodException",
4550 "No active transaction -- required for UPDATE"
4556 // The following test is harmless but redundant. If a class is
4557 // readonly, we don't register an update method for it.
4558 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4559 osrfAppSessionStatus(
4561 OSRF_STATUS_BADREQUEST,
4562 "osrfMethodException",
4564 "Cannot UPDATE readonly class"
4570 dbhandle = writehandle;
4572 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4574 // Set the last_xact_id
4575 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4577 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
4578 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4581 char* pkey = osrfHashGet(meta, "primarykey");
4582 osrfHash* fields = osrfHashGet(meta, "fields");
4584 char* id = oilsFMGetString( target, pkey );
4588 "%s updating %s object with %s = %s",
4590 osrfHashGet(meta, "fieldmapper"),
4595 growing_buffer* sql = buffer_init(128);
4596 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4601 osrfStringArray* field_list = osrfHashKeys( fields );
4602 while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
4604 osrfHash* field = osrfHashGet( fields, field_name );
4606 if(!( strcmp( field_name, pkey ) )) continue;
4607 if( str_is_true( osrfHashGet(osrfHashGet(fields,field_name), "virtual") ) )
4610 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4612 int value_is_numeric = 0; // boolean
4614 if (field_object && field_object->classname) {
4615 value = oilsFMGetString(
4617 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4620 value = jsonObjectToSimpleString( field_object );
4621 if( field_object && JSON_NUMBER == field_object->type )
4622 value_is_numeric = 1;
4625 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s", osrfHashGet(meta, "fieldmapper"), field_name, value);
4627 if (!field_object || field_object->type == JSON_NULL) {
4628 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) ) && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
4629 if (first) first = 0;
4630 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4631 buffer_fadd( sql, " %s = NULL", field_name );
4634 } else if ( value_is_numeric || !strcmp( get_primitive( field ), "number") ) {
4635 if (first) first = 0;
4636 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4638 const char* numtype = get_datatype( field );
4639 if ( !strncmp( numtype, "INT", 3 ) ) {
4640 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
4641 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
4642 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
4644 // Must really be intended as a string, so quote it
4645 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4646 buffer_fadd( sql, " %s = %s", field_name, value );
4648 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4649 osrfAppSessionStatus(
4651 OSRF_STATUS_INTERNALSERVERERROR,
4652 "osrfMethodException",
4654 "Error quoting string -- please see the error log for more details"
4664 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
4667 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4668 if (first) first = 0;
4669 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4670 buffer_fadd( sql, " %s = %s", field_name, value );
4673 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4674 osrfAppSessionStatus(
4676 OSRF_STATUS_INTERNALSERVERERROR,
4677 "osrfMethodException",
4679 "Error quoting string -- please see the error log for more details"
4693 jsonObject* obj = jsonNewObject(id);
4695 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4696 dbi_conn_quote_string(dbhandle, &id);
4698 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
4700 char* query = buffer_release(sql);
4701 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
4703 dbi_result result = dbi_conn_query(dbhandle, query);
4707 jsonObjectFree(obj);
4708 obj = jsonNewObject(NULL);
4711 "%s ERROR updating %s object with %s = %s",
4713 osrfHashGet(meta, "fieldmapper"),
4724 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4726 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4728 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4729 osrfAppSessionStatus(
4731 OSRF_STATUS_BADREQUEST,
4732 "osrfMethodException",
4734 "No active transaction -- required for DELETE"
4740 // The following test is harmless but redundant. If a class is
4741 // readonly, we don't register a delete method for it.
4742 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4743 osrfAppSessionStatus(
4745 OSRF_STATUS_BADREQUEST,
4746 "osrfMethodException",
4748 "Cannot DELETE readonly class"
4754 dbhandle = writehandle;
4758 char* pkey = osrfHashGet(meta, "primarykey");
4766 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4767 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4772 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4775 if (!verifyObjectPCRUD( ctx, NULL )) {
4780 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4785 "%s deleting %s object with %s = %s",
4787 osrfHashGet(meta, "fieldmapper"),
4792 obj = jsonNewObject(id);
4794 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4795 dbi_conn_quote_string(writehandle, &id);
4797 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4800 jsonObjectFree(obj);
4801 obj = jsonNewObject(NULL);
4804 "%s ERROR deleting %s object with %s = %s",
4806 osrfHashGet(meta, "fieldmapper"),
4819 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4820 if(!(result && meta)) return jsonNULL;
4822 jsonObject* object = jsonNewObject(NULL);
4823 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4825 osrfHash* fields = osrfHashGet(meta, "fields");
4827 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4831 char dt_string[256];
4835 int columnIndex = 1;
4837 unsigned short type;
4838 const char* columnName;
4840 /* cycle through the column list */
4841 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
4843 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4845 fmIndex = -1; // reset the position
4847 /* determine the field type and storage attributes */
4848 type = dbi_result_get_field_type_idx(result, columnIndex);
4849 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
4851 /* fetch the fieldmapper index */
4852 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4854 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4857 const char* pos = (char*)osrfHashGet(_f, "array_position");
4858 if ( !pos ) continue;
4860 fmIndex = atoi( pos );
4861 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
4866 if (dbi_result_field_is_null_idx(result, columnIndex)) {
4867 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
4872 case DBI_TYPE_INTEGER :
4874 if( attr & DBI_INTEGER_SIZE8 )
4875 jsonObjectSetIndex( object, fmIndex,
4876 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
4878 jsonObjectSetIndex( object, fmIndex,
4879 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
4883 case DBI_TYPE_DECIMAL :
4884 jsonObjectSetIndex( object, fmIndex,
4885 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
4888 case DBI_TYPE_STRING :
4894 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
4899 case DBI_TYPE_DATETIME :
4901 memset(dt_string, '\0', sizeof(dt_string));
4902 memset(&gmdt, '\0', sizeof(gmdt));
4904 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
4907 if (!(attr & DBI_DATETIME_DATE)) {
4908 gmtime_r( &_tmp_dt, &gmdt );
4909 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4910 } else if (!(attr & DBI_DATETIME_TIME)) {
4911 localtime_r( &_tmp_dt, &gmdt );
4912 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4914 localtime_r( &_tmp_dt, &gmdt );
4915 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4918 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
4922 case DBI_TYPE_BINARY :
4923 osrfLogError( OSRF_LOG_MARK,
4924 "Can't do binary at column %s : index %d", columnName, columnIndex);
4933 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
4934 if(!result) return jsonNULL;
4936 jsonObject* object = jsonNewObject(NULL);
4939 char dt_string[256];
4943 int columnIndex = 1;
4945 unsigned short type;
4946 const char* columnName;
4948 /* cycle through the column list */
4949 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
4951 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4953 fmIndex = -1; // reset the position
4955 /* determine the field type and storage attributes */
4956 type = dbi_result_get_field_type_idx(result, columnIndex);
4957 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
4959 if (dbi_result_field_is_null_idx(result, columnIndex)) {
4960 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
4965 case DBI_TYPE_INTEGER :
4967 if( attr & DBI_INTEGER_SIZE8 )
4968 jsonObjectSetKey( object, columnName,
4969 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)) );
4971 jsonObjectSetKey( object, columnName,
4972 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
4975 case DBI_TYPE_DECIMAL :
4976 jsonObjectSetKey( object, columnName,
4977 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
4980 case DBI_TYPE_STRING :
4981 jsonObjectSetKey( object, columnName,
4982 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
4985 case DBI_TYPE_DATETIME :
4987 memset(dt_string, '\0', sizeof(dt_string));
4988 memset(&gmdt, '\0', sizeof(gmdt));
4990 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
4993 if (!(attr & DBI_DATETIME_DATE)) {
4994 gmtime_r( &_tmp_dt, &gmdt );
4995 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4996 } else if (!(attr & DBI_DATETIME_TIME)) {
4997 localtime_r( &_tmp_dt, &gmdt );
4998 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5000 localtime_r( &_tmp_dt, &gmdt );
5001 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5004 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
5007 case DBI_TYPE_BINARY :
5008 osrfLogError( OSRF_LOG_MARK,
5009 "Can't do binary at column %s : index %d", columnName, columnIndex );
5013 } // end while loop traversing result
5018 // Interpret a string as true or false
5019 static int str_is_true( const char* str ) {
5020 if( NULL == str || strcasecmp( str, "true" ) )
5026 // Interpret a jsonObject as true or false
5027 static int obj_is_true( const jsonObject* obj ) {
5030 else switch( obj->type )
5038 if( strcasecmp( obj->value.s, "true" ) )
5042 case JSON_NUMBER : // Support 1/0 for perl's sake
5043 if( jsonObjectGetNumber( obj ) == 1.0 )
5052 // Translate a numeric code into a text string identifying a type of
5053 // jsonObject. To be used for building error messages.
5054 static const char* json_type( int code ) {
5060 return "JSON_ARRAY";
5062 return "JSON_STRING";
5064 return "JSON_NUMBER";
5070 return "(unrecognized)";
5074 // Extract the "primitive" attribute from an IDL field definition.
5075 // If we haven't initialized the app, then we must be running in
5076 // some kind of testbed. In that case, default to "string".
5077 static const char* get_primitive( osrfHash* field ) {
5078 const char* s = osrfHashGet( field, "primitive" );
5080 if( child_initialized )
5083 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5085 osrfHashGet( field, "name" )
5093 // Extract the "datatype" attribute from an IDL field definition.
5094 // If we haven't initialized the app, then we must be running in
5095 // some kind of testbed. In that case, default to to NUMERIC,
5096 // since we look at the datatype only for numbers.
5097 static const char* get_datatype( osrfHash* field ) {
5098 const char* s = osrfHashGet( field, "datatype" );
5100 if( child_initialized )
5103 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5105 osrfHashGet( field, "name" )
5114 If the input string is potentially a valid SQL identifier, return 1.
5117 Purpose: to prevent certain kinds of SQL injection. To that end we
5118 don't necessarily need to follow all the rules exactly, such as requiring
5119 that the first character not be a digit.
5121 We allow leading and trailing white space. In between, we do not allow
5122 punctuation (except for underscores and dollar signs), control
5123 characters, or embedded white space.
5125 More pedantically we should allow quoted identifiers containing arbitrary
5126 characters, but for the foreseeable future such quoted identifiers are not
5127 likely to be an issue.
5129 static int is_identifier( const char* s) {
5133 // Skip leading white space
5134 while( isspace( (unsigned char) *s ) )
5138 return 0; // Nothing but white space? Not okay.
5140 // Check each character until we reach white space or
5141 // end-of-string. Letters, digits, underscores, and
5142 // dollar signs are okay. With the exception of periods
5143 // (as in schema.identifier), control characters and other
5144 // punctuation characters are not okay. Anything else
5145 // is okay -- it could for example be part of a multibyte
5146 // UTF8 character such as a letter with diacritical marks,
5147 // and those are allowed.
5149 if( isalnum( (unsigned char) *s )
5153 ; // Fine; keep going
5154 else if( ispunct( (unsigned char) *s )
5155 || iscntrl( (unsigned char) *s ) )
5158 } while( *s && ! isspace( (unsigned char) *s ) );
5160 // If we found any white space in the above loop,
5161 // the rest had better be all white space.
5163 while( isspace( (unsigned char) *s ) )
5167 return 0; // White space was embedded within non-white space
5173 Determine whether to accept a character string as a comparison operator.
5174 Return 1 if it's good, or 0 if it's bad.
5176 We don't validate it for real. We just make sure that it doesn't contain
5177 any semicolons or white space (with a special exception for the
5178 "SIMILAR TO" operator). The idea is to block certain kinds of SQL
5179 injection. If it has no semicolons or white space but it's still not a
5180 valid operator, then the database will complain.
5182 Another approach would be to compare the string against a short list of
5183 approved operators. We don't do that because we want to allow custom
5184 operators like ">100*", which would be difficult or impossible to
5185 express otherwise in a JSON query.
5187 static int is_good_operator( const char* op ) {
5188 if( !op ) return 0; // Sanity check
5192 if( isspace( (unsigned char) *s ) ) {
5193 // Special exception for SIMILAR TO. Someday we might make
5194 // exceptions for IS DISTINCT FROM and IS NOT DISTINCT FROM.
5195 if( !strcasecmp( op, "similar to" ) )
5200 else if( ';' == *s )