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 jsonObject* single_hash( const char* key, const char* value );
90 static int child_initialized = 0; /* boolean */
92 static dbi_conn writehandle; /* our MASTER db connection */
93 static dbi_conn dbhandle; /* our CURRENT db connection */
94 //static osrfHash * readHandles;
95 static jsonObject* jsonNULL = NULL; //
96 static int max_flesh_depth = 100;
98 /* called when this process is about to exit */
99 void osrfAppChildExit() {
100 osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
103 if (writehandle == dbhandle) same = 1;
105 dbi_conn_query(writehandle, "ROLLBACK;");
106 dbi_conn_close(writehandle);
109 if (dbhandle && !same)
110 dbi_conn_close(dbhandle);
112 // XXX add cleanup of readHandles whenever that gets used
117 int osrfAppInitialize() {
119 osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
120 osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
122 if (!oilsIDLInit( osrf_settings_host_value("/IDL") )) return 1; /* return non-zero to indicate error */
124 growing_buffer* method_name = buffer_init(64);
126 // Generic search thingy
127 buffer_add(method_name, MODULENAME);
128 buffer_add(method_name, ".json_query");
129 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
130 "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
133 // first we register all the transaction and savepoint methods
134 buffer_reset(method_name);
135 OSRF_BUFFER_ADD(method_name, MODULENAME);
136 OSRF_BUFFER_ADD(method_name, ".transaction.begin");
137 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
138 "beginTransaction", "", 0, 0 );
140 buffer_reset(method_name);
141 OSRF_BUFFER_ADD(method_name, MODULENAME);
142 OSRF_BUFFER_ADD(method_name, ".transaction.commit");
143 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
144 "commitTransaction", "", 0, 0 );
146 buffer_reset(method_name);
147 OSRF_BUFFER_ADD(method_name, MODULENAME);
148 OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
149 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
150 "rollbackTransaction", "", 0, 0 );
152 buffer_reset(method_name);
153 OSRF_BUFFER_ADD(method_name, MODULENAME);
154 OSRF_BUFFER_ADD(method_name, ".savepoint.set");
155 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
156 "setSavepoint", "", 1, 0 );
158 buffer_reset(method_name);
159 OSRF_BUFFER_ADD(method_name, MODULENAME);
160 OSRF_BUFFER_ADD(method_name, ".savepoint.release");
161 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
162 "releaseSavepoint", "", 1, 0 );
164 buffer_reset(method_name);
165 OSRF_BUFFER_ADD(method_name, MODULENAME);
166 OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
167 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
168 "rollbackSavepoint", "", 1, 0 );
170 static const char* global_method[] = {
178 const int global_method_count
179 = sizeof( global_method ) / sizeof ( global_method[0] );
183 osrfStringArray* classes = osrfHashKeys( oilsIDL() );
184 osrfLogDebug(OSRF_LOG_MARK, "%d classes loaded", classes->size );
185 osrfLogDebug(OSRF_LOG_MARK,
186 "At most %d methods will be generated", classes->size * global_method_count);
188 // For each class in IDL...
189 while ( (classname = osrfStringArrayGetString(classes, c_index++)) ) {
190 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
192 osrfHash* idlClass = osrfHashGet(oilsIDL(), classname);
194 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), MODULENAME )) {
195 osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on", MODULENAME, classname);
199 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
200 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
204 // Look up some other attributes of the current class
205 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
206 if( !idlClass_fieldmapper ) {
207 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL", classname );
212 osrfHash* idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
213 if (!idlClass_permacrud) {
214 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no permacrud in IDL", classname );
218 const char* readonly = osrfHashGet(idlClass, "readonly");
221 for( i = 0; i < global_method_count; ++i ) { // for each global method
222 const char* method_type = global_method[ i ];
223 osrfLogDebug(OSRF_LOG_MARK,
224 "Using files to build %s class methods for %s", method_type, classname);
227 const char* tmp_method = method_type;
228 if ( *tmp_method == 'i' || *tmp_method == 's') {
229 tmp_method = "retrieve";
231 if (!osrfHashGet( idlClass_permacrud, tmp_method )) continue;
234 if ( str_is_true( readonly ) &&
235 ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd')
238 buffer_reset( method_name );
240 buffer_fadd(method_name, "%s.%s.%s", MODULENAME, method_type, classname);
244 char* _fm = strdup( idlClass_fieldmapper );
245 part = strtok_r(_fm, ":", &st_tmp);
247 buffer_fadd(method_name, "%s.direct.%s", MODULENAME, part);
249 while ((part = strtok_r(NULL, ":", &st_tmp))) {
250 OSRF_BUFFER_ADD_CHAR(method_name, '.');
251 OSRF_BUFFER_ADD(method_name, part);
253 OSRF_BUFFER_ADD_CHAR(method_name, '.');
254 OSRF_BUFFER_ADD(method_name, method_type);
258 char* method = buffer_data(method_name);
261 if (*method_type == 'i' || *method_type == 's') {
262 flags = flags | OSRF_METHOD_STREAMING;
265 osrfHash* method_meta = osrfNewHash();
266 osrfHashSet( method_meta, idlClass, "class");
267 osrfHashSet( method_meta, method, "methodname" );
268 osrfHashSet( method_meta, strdup(method_type), "methodtype" );
270 osrfAppRegisterExtendedMethod(
273 "dispatchCRUDMethod",
281 } // end for each global method
282 } // end for each class in IDL
284 buffer_free( method_name );
285 osrfStringArrayFree( classes );
290 static char* getSourceDefinition( osrfHash* class ) {
292 char* tabledef = osrfHashGet(class, "tablename");
295 tabledef = strdup(tabledef);
297 tabledef = osrfHashGet(class, "source_definition");
299 growing_buffer* tablebuf = buffer_init(128);
300 buffer_fadd( tablebuf, "(%s)", tabledef );
301 tabledef = buffer_release(tablebuf);
303 const char* classname = osrfHashGet( class, "classname" );
308 "%s ERROR No tablename or source_definition for class \"%s\"",
319 * Connects to the database
321 int osrfAppChildInit() {
323 osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
324 dbi_initialize(NULL);
325 osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
327 char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
328 char* user = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
329 char* host = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
330 char* port = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
331 char* db = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
332 char* pw = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
333 char* md = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion", MODULENAME);
335 osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
336 writehandle = dbi_conn_new(driver);
339 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
342 osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
344 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
345 "port=%s, user=%s, pw=%s, db=%s", MODULENAME, host, port, user, pw, db );
347 if(host) dbi_conn_set_option(writehandle, "host", host );
348 if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
349 if(user) dbi_conn_set_option(writehandle, "username", user);
350 if(pw) dbi_conn_set_option(writehandle, "password", pw );
351 if(db) dbi_conn_set_option(writehandle, "dbname", db );
353 if(md) max_flesh_depth = atoi(md);
354 if(max_flesh_depth < 0) max_flesh_depth = 1;
355 if(max_flesh_depth > 1000) max_flesh_depth = 1000;
364 if (dbi_conn_connect(writehandle) < 0) {
366 if (dbi_conn_connect(writehandle) < 0) {
367 dbi_conn_error(writehandle, &err);
368 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
373 osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
377 osrfStringArray* classes = osrfHashKeys( oilsIDL() );
379 while ( (classname = osrfStringArrayGetString(classes, i++)) ) {
380 osrfHash* class = osrfHashGet( oilsIDL(), classname );
381 osrfHash* fields = osrfHashGet( class, "fields" );
383 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
384 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
388 char* tabledef = getSourceDefinition(class);
390 tabledef = strdup( "(null)" );
392 growing_buffer* sql_buf = buffer_init(32);
393 buffer_fadd( sql_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
397 char* sql = buffer_release(sql_buf);
398 osrfLogDebug(OSRF_LOG_MARK, "%s Investigatory SQL = %s", MODULENAME, sql);
400 dbi_result result = dbi_conn_query(writehandle, sql);
406 const char* columnName;
408 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
410 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
412 /* fetch the fieldmapper index */
413 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
415 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", (char*)columnName);
417 /* determine the field type and storage attributes */
419 switch( dbi_result_get_field_type_idx(result, columnIndex) ) {
421 case DBI_TYPE_INTEGER : {
423 if ( !osrfHashGet(_f, "primitive") )
424 osrfHashSet(_f,"number", "primitive");
426 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
427 if( attr & DBI_INTEGER_SIZE8 )
428 osrfHashSet(_f,"INT8", "datatype");
430 osrfHashSet(_f,"INT", "datatype");
433 case DBI_TYPE_DECIMAL :
434 if ( !osrfHashGet(_f, "primitive") )
435 osrfHashSet(_f,"number", "primitive");
437 osrfHashSet(_f,"NUMERIC", "datatype");
440 case DBI_TYPE_STRING :
441 if ( !osrfHashGet(_f, "primitive") )
442 osrfHashSet(_f,"string", "primitive");
443 osrfHashSet(_f,"TEXT", "datatype");
446 case DBI_TYPE_DATETIME :
447 if ( !osrfHashGet(_f, "primitive") )
448 osrfHashSet(_f,"string", "primitive");
450 osrfHashSet(_f,"TIMESTAMP", "datatype");
453 case DBI_TYPE_BINARY :
454 if ( !osrfHashGet(_f, "primitive") )
455 osrfHashSet(_f,"string", "primitive");
457 osrfHashSet(_f,"BYTEA", "datatype");
462 "Setting [%s] to primitive [%s] and datatype [%s]...",
464 osrfHashGet(_f, "primitive"),
465 osrfHashGet(_f, "datatype")
469 } // end while loop for traversing result
470 dbi_result_free(result);
472 osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", (char*)classname);
476 osrfStringArrayFree(classes);
478 child_initialized = 1;
483 This function is a sleazy hack intended *only* for testing and
484 debugging. Any real server process should initialize the
485 database connection by calling osrfAppChildInit().
487 void set_cstore_dbi_conn( dbi_conn conn ) {
488 dbhandle = writehandle = conn;
491 void userDataFree( void* blob ) {
492 osrfHashFree( (osrfHash*)blob );
496 static void sessionDataFree( char* key, void* item ) {
497 if (!(strcmp(key,"xact_id"))) {
499 dbi_conn_query(writehandle, "ROLLBACK;");
506 int beginTransaction ( osrfMethodContext* ctx ) {
507 if(osrfMethodVerifyContext( ctx )) {
508 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
513 jsonObject* user = verifyUserPCRUD( ctx );
514 if (!user) return -1;
515 jsonObjectFree(user);
518 dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
520 osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
521 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error starting transaction" );
524 jsonObject* ret = jsonNewObject(ctx->session->session_id);
525 osrfAppRespondComplete( ctx, ret );
528 if (!ctx->session->userData) {
529 ctx->session->userData = osrfNewHash();
530 osrfHashSetCallback((osrfHash*)ctx->session->userData, &sessionDataFree);
533 osrfHashSet( (osrfHash*)ctx->session->userData, strdup( ctx->session->session_id ), "xact_id" );
534 ctx->session->userDataFree = &userDataFree;
540 int setSavepoint ( osrfMethodContext* ctx ) {
541 if(osrfMethodVerifyContext( ctx )) {
542 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
549 jsonObject* user = verifyUserPCRUD( ctx );
550 if (!user) return -1;
551 jsonObjectFree(user);
554 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
555 osrfAppSessionStatus(
557 OSRF_STATUS_INTERNALSERVERERROR,
558 "osrfMethodException",
560 "No active transaction -- required for savepoints"
565 const char* spName = jsonObjectGetString(jsonObjectGetIndex(ctx->params, spNamePos));
567 dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
571 "%s: Error creating savepoint %s in transaction %s",
574 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
576 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
577 "osrfMethodException", ctx->request, "Error creating savepoint" );
580 jsonObject* ret = jsonNewObject(spName);
581 osrfAppRespondComplete( ctx, ret );
587 int releaseSavepoint ( osrfMethodContext* ctx ) {
588 if(osrfMethodVerifyContext( ctx )) {
589 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
596 jsonObject* user = verifyUserPCRUD( ctx );
597 if (!user) return -1;
598 jsonObjectFree(user);
601 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
602 osrfAppSessionStatus(
604 OSRF_STATUS_INTERNALSERVERERROR,
605 "osrfMethodException",
607 "No active transaction -- required for savepoints"
612 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
614 dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
618 "%s: Error releasing savepoint %s in transaction %s",
621 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
623 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
624 "osrfMethodException", ctx->request, "Error releasing savepoint" );
627 jsonObject* ret = jsonNewObject(spName);
628 osrfAppRespondComplete( ctx, ret );
634 int rollbackSavepoint ( osrfMethodContext* ctx ) {
635 if(osrfMethodVerifyContext( ctx )) {
636 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
643 jsonObject* user = verifyUserPCRUD( ctx );
644 if (!user) return -1;
645 jsonObjectFree(user);
648 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
649 osrfAppSessionStatus(
651 OSRF_STATUS_INTERNALSERVERERROR,
652 "osrfMethodException",
654 "No active transaction -- required for savepoints"
659 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
661 dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
665 "%s: Error rolling back savepoint %s in transaction %s",
668 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
670 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
671 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
674 jsonObject* ret = jsonNewObject(spName);
675 osrfAppRespondComplete( ctx, ret );
681 int commitTransaction ( osrfMethodContext* ctx ) {
682 if(osrfMethodVerifyContext( ctx )) {
683 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
688 jsonObject* user = verifyUserPCRUD( ctx );
689 if (!user) return -1;
690 jsonObjectFree(user);
693 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
694 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to commit" );
698 dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
700 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
701 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error committing transaction" );
704 osrfHashRemove(ctx->session->userData, "xact_id");
705 jsonObject* ret = jsonNewObject(ctx->session->session_id);
706 osrfAppRespondComplete( ctx, ret );
712 int rollbackTransaction ( osrfMethodContext* ctx ) {
713 if(osrfMethodVerifyContext( ctx )) {
714 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
719 jsonObject* user = verifyUserPCRUD( ctx );
720 if (!user) return -1;
721 jsonObjectFree(user);
724 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
725 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to roll back" );
729 dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
731 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
732 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error rolling back transaction" );
735 osrfHashRemove(ctx->session->userData, "xact_id");
736 jsonObject* ret = jsonNewObject(ctx->session->session_id);
737 osrfAppRespondComplete( ctx, ret );
743 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
744 if(osrfMethodVerifyContext( ctx )) {
745 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
749 osrfHash* meta = (osrfHash*) ctx->method->userData;
750 osrfHash* class_obj = osrfHashGet( meta, "class" );
754 const char* methodtype = osrfHashGet(meta, "methodtype");
755 jsonObject * obj = NULL;
757 if (!strcmp(methodtype, "create")) {
758 obj = doCreate(ctx, &err);
759 osrfAppRespondComplete( ctx, obj );
761 else if (!strcmp(methodtype, "retrieve")) {
762 obj = doRetrieve(ctx, &err);
763 osrfAppRespondComplete( ctx, obj );
765 else if (!strcmp(methodtype, "update")) {
766 obj = doUpdate(ctx, &err);
767 osrfAppRespondComplete( ctx, obj );
769 else if (!strcmp(methodtype, "delete")) {
770 obj = doDelete(ctx, &err);
771 osrfAppRespondComplete( ctx, obj );
773 else if (!strcmp(methodtype, "search")) {
775 jsonObject* where_clause;
776 jsonObject* rest_of_query;
779 where_clause = jsonObjectGetIndex( ctx->params, 1 );
780 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
782 where_clause = jsonObjectGetIndex( ctx->params, 0 );
783 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
786 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
791 jsonIterator* itr = jsonNewIterator( obj );
792 while ((cur = jsonIteratorNext( itr ))) {
794 if(!verifyObjectPCRUD(ctx, cur)) continue;
796 osrfAppRespond( ctx, cur );
798 jsonIteratorFree(itr);
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 ) );
832 "{ \"%s\":[\"%s\"] }",
833 osrfHashGet( class_obj, "classname" ),
834 osrfHashGet( class_obj, "primarykey" )
838 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
840 jsonObjectFree( rest_of_query );
844 jsonIterator* itr = jsonNewIterator( obj );
845 while ((cur = jsonIteratorNext( itr ))) {
847 if(!verifyObjectPCRUD(ctx, cur)) continue;
851 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
854 jsonIteratorFree(itr);
855 osrfAppRespondComplete( ctx, NULL );
858 osrfAppRespondComplete( ctx, obj );
866 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
869 osrfHash* meta = (osrfHash*) ctx->method->userData;
870 osrfHash* class = osrfHashGet( meta, "class" );
872 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
874 growing_buffer* msg = buffer_init(128);
877 "%s: %s method for type %s was passed a %s",
879 osrfHashGet(meta, "methodtype"),
880 osrfHashGet(class, "classname"),
884 char* m = buffer_release(msg);
885 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException", ctx->request, m );
893 ret = verifyObjectPCRUD( ctx, param );
901 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
902 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
903 jsonObject* auth_object = jsonNewObject(auth);
904 jsonObject* user = oilsUtilsQuickReq("open-ils.auth","open-ils.auth.session.retrieve", auth_object);
905 jsonObjectFree(auth_object);
907 if (!user->classname || strcmp(user->classname, "au")) {
909 growing_buffer* msg = buffer_init(128);
912 "%s: permacrud received a bad auth token: %s",
917 char* m = buffer_release(msg);
918 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException", ctx->request, m );
921 jsonObjectFree(user);
929 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
931 dbhandle = writehandle;
933 osrfHash* meta = (osrfHash*) ctx->method->userData;
934 osrfHash* class = osrfHashGet( meta, "class" );
935 char* method_type = strdup( osrfHashGet(meta, "methodtype") );
938 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
940 method_type = strdup("retrieve"); // search and id_list are equivelant to retrieve for this
941 } else if ( *method_type == 'u' || *method_type == 'd' ) {
942 fetch = 1; // MUST go to the db for the object for update and delete
945 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
949 // No permacrud for this method type on this class
951 growing_buffer* msg = buffer_init(128);
954 "%s: %s on class %s has no permacrud IDL entry",
956 osrfHashGet(meta, "methodtype"),
957 osrfHashGet(class, "classname")
960 char* m = buffer_release(msg);
961 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN, "osrfMethodException", ctx->request, m );
968 jsonObject* user = verifyUserPCRUD( ctx );
971 int userid = atoi( oilsFMGetString( user, "id" ) );
972 jsonObjectFree(user);
974 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
975 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
976 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
978 osrfStringArray* context_org_array = osrfNewStringArray(1);
981 char* pkey_value = NULL;
982 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
983 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions required, fetching top of the org tree" );
985 // check for perm at top of org tree
986 jsonObject* _tmp_params = jsonParseString("{\"parent_ou\":null}");
987 jsonObject* _list = doFieldmapperSearch(ctx, osrfHashGet( oilsIDL(), "aou" ),
988 _tmp_params, NULL, &err);
990 jsonObject* _tree_top = jsonObjectGetIndex(_list, 0);
993 jsonObjectFree(_tmp_params);
994 jsonObjectFree(_list);
996 growing_buffer* msg = buffer_init(128);
997 OSRF_BUFFER_ADD( msg, MODULENAME );
998 OSRF_BUFFER_ADD( msg,
999 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1001 char* m = buffer_release(msg);
1002 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1008 osrfStringArrayAdd( context_org_array, oilsFMGetString( _tree_top, "id" ) );
1009 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", osrfStringArrayGetString(context_org_array, 0) );
1011 jsonObjectFree(_tmp_params);
1012 jsonObjectFree(_list);
1015 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, fetching context org ids" );
1016 char* pkey = osrfHashGet(class, "primarykey");
1017 jsonObject *param = NULL;
1019 if (obj->classname) {
1020 pkey_value = oilsFMGetString( obj, pkey );
1021 if (!fetch) param = jsonObjectClone(obj);
1022 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s", pkey_value );
1024 pkey_value = jsonObjectToSimpleString( obj );
1026 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value of %s and retrieving from the database", pkey_value );
1030 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1031 jsonObject* _list = doFieldmapperSearch(
1032 ctx, class, _tmp_params, NULL, &err );
1034 param = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1036 jsonObjectFree(_tmp_params);
1037 jsonObjectFree(_list);
1041 osrfLogDebug( OSRF_LOG_MARK, "Object not found in the database with primary key %s of %s", pkey, pkey_value );
1043 growing_buffer* msg = buffer_init(128);
1046 "%s: no object found with primary key %s of %s",
1052 char* m = buffer_release(msg);
1053 osrfAppSessionStatus(
1055 OSRF_STATUS_INTERNALSERVERERROR,
1056 "osrfMethodException",
1062 if (pkey_value) free(pkey_value);
1067 if (local_context->size > 0) {
1068 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified", local_context->size);
1070 char* lcontext = NULL;
1071 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1072 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1075 "adding class-local field %s (value: %s) to the context org list",
1077 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1082 osrfStringArray* class_list;
1084 if (foreign_context) {
1085 class_list = osrfHashKeys( foreign_context );
1086 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_list->size);
1088 if (class_list->size > 0) {
1091 char* class_name = NULL;
1092 while ( (class_name = osrfStringArrayGetString(class_list, i++)) ) {
1093 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1097 "%d foreign context fields(s) specified for class %s",
1098 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1102 char* foreign_pkey = osrfHashGet(fcontext, "field");
1103 char* foreign_pkey_value = oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1105 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1107 jsonObject* _list = doFieldmapperSearch(
1108 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1110 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1111 jsonObjectFree(_tmp_params);
1112 jsonObjectFree(_list);
1114 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1116 if (_fparam && jump_list) {
1119 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1120 free(foreign_pkey_value);
1122 osrfHash* foreign_link_hash = oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1124 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1125 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1127 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1129 _list = doFieldmapperSearch(
1131 osrfHashGet( oilsIDL(), osrfHashGet( foreign_link_hash, "class" ) ),
1137 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1138 jsonObjectFree(_tmp_params);
1139 jsonObjectFree(_list);
1146 growing_buffer* msg = buffer_init(128);
1149 "%s: no object found with primary key %s of %s",
1155 char* m = buffer_release(msg);
1156 osrfAppSessionStatus(
1158 OSRF_STATUS_INTERNALSERVERERROR,
1159 "osrfMethodException",
1165 osrfStringArrayFree(class_list);
1166 free(foreign_pkey_value);
1167 jsonObjectFree(param);
1172 free(foreign_pkey_value);
1175 char* foreign_field = NULL;
1176 while ( (foreign_field = osrfStringArrayGetString(osrfHashGet(fcontext,"context"), j++)) ) {
1177 osrfStringArrayAdd( context_org_array, oilsFMGetString( _fparam, foreign_field ) );
1180 "adding foreign class %s field %s (value: %s) to the context org list",
1183 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1187 jsonObjectFree(_fparam);
1190 osrfStringArrayFree(class_list);
1194 jsonObjectFree(param);
1197 char* context_org = NULL;
1201 if (permission->size == 0) {
1202 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1207 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1209 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1215 "Checking object permission [%s] for user %d on object %s (class %s) at org %d",
1219 osrfHashGet(class, "classname"),
1223 result = dbi_conn_queryf(
1225 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1228 osrfHashGet(class, "classname"),
1236 "Recieved a result for object permission [%s] for user %d on object %s (class %s) at org %d",
1240 osrfHashGet(class, "classname"),
1244 if (dbi_result_first_row(result)) {
1245 jsonObject* return_val = oilsMakeJSONFromResult( result );
1246 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1250 "Status of object permission [%s] for user %d on object %s (class %s) at org %d is %s",
1254 osrfHashGet(class, "classname"),
1259 if ( *has_perm == 't' ) OK = 1;
1260 jsonObjectFree(return_val);
1263 dbi_result_free(result);
1268 osrfLogDebug( OSRF_LOG_MARK, "Checking non-object permission [%s] for user %d at org %d", perm, userid, atoi(context_org) );
1269 result = dbi_conn_queryf(
1271 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1278 osrfLogDebug( OSRF_LOG_MARK, "Received a result for permission [%s] for user %d at org %d",
1279 perm, userid, atoi(context_org) );
1280 if ( dbi_result_first_row(result) ) {
1281 jsonObject* return_val = oilsMakeJSONFromResult( result );
1282 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1283 osrfLogDebug( OSRF_LOG_MARK, "Status of permission [%s] for user %d at org %d is [%s]",
1284 perm, userid, atoi(context_org), has_perm );
1285 if ( *has_perm == 't' ) OK = 1;
1286 jsonObjectFree(return_val);
1289 dbi_result_free(result);
1297 if (pkey_value) free(pkey_value);
1298 osrfStringArrayFree(context_org_array);
1304 Utility function: create a JSON_HASH with a single key/value pair.
1305 This function is equivalent to:
1307 jsonParseStringFmt( "{\"%s\":\"%s\"}", key, value )
1309 ...but faster because it doesn't create and parse a JSON string.
1311 static jsonObject* single_hash( const char* key, const char* value ) {
1313 if( ! key ) key = "";
1314 if( ! value ) value = "";
1316 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1317 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1323 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1325 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1327 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1328 jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1330 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1331 jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1334 if (!verifyObjectClass(ctx, target)) {
1339 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1341 char* trans_id = NULL;
1342 if( ctx->session && ctx->session->userData )
1343 trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
1346 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1348 osrfAppSessionStatus(
1350 OSRF_STATUS_BADREQUEST,
1351 "osrfMethodException",
1353 "No active transaction -- required for CREATE"
1359 // The following test is harmless but redundant. If a class is
1360 // readonly, we don't register a create method for it.
1361 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1362 osrfAppSessionStatus(
1364 OSRF_STATUS_BADREQUEST,
1365 "osrfMethodException",
1367 "Cannot INSERT readonly class"
1373 // Set the last_xact_id
1374 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1376 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
1377 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1380 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1382 dbhandle = writehandle;
1384 osrfHash* fields = osrfHashGet(meta, "fields");
1385 char* pkey = osrfHashGet(meta, "primarykey");
1386 char* seq = osrfHashGet(meta, "sequence");
1388 growing_buffer* table_buf = buffer_init(128);
1389 growing_buffer* col_buf = buffer_init(128);
1390 growing_buffer* val_buf = buffer_init(128);
1392 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1393 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1394 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1395 buffer_add(val_buf,"VALUES (");
1401 osrfStringArray* field_list = osrfHashKeys( fields );
1402 while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
1404 osrfHash* field = osrfHashGet( fields, field_name );
1406 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1409 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1412 if (field_object && field_object->classname) {
1413 value = oilsFMGetString(
1415 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1418 value = jsonObjectToSimpleString( field_object );
1425 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1426 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1429 buffer_add(col_buf, field_name);
1431 if (!field_object || field_object->type == JSON_NULL) {
1432 buffer_add( val_buf, "DEFAULT" );
1434 } else if ( !strcmp(get_primitive( field ), "number") ) {
1435 const char* numtype = get_datatype( field );
1436 if ( !strcmp( numtype, "INT8") ) {
1437 buffer_fadd( val_buf, "%lld", atoll(value) );
1439 } else if ( !strcmp( numtype, "INT") ) {
1440 buffer_fadd( val_buf, "%d", atoi(value) );
1442 } else if ( !strcmp( numtype, "NUMERIC") ) {
1443 buffer_fadd( val_buf, "%f", atof(value) );
1446 if ( dbi_conn_quote_string(writehandle, &value) ) {
1447 OSRF_BUFFER_ADD( val_buf, value );
1450 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1451 osrfAppSessionStatus(
1453 OSRF_STATUS_INTERNALSERVERERROR,
1454 "osrfMethodException",
1456 "Error quoting string -- please see the error log for more details"
1459 buffer_free(table_buf);
1460 buffer_free(col_buf);
1461 buffer_free(val_buf);
1472 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1473 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1475 char* table_str = buffer_release(table_buf);
1476 char* col_str = buffer_release(col_buf);
1477 char* val_str = buffer_release(val_buf);
1478 growing_buffer* sql = buffer_init(128);
1479 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1484 char* query = buffer_release(sql);
1486 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1489 dbi_result result = dbi_conn_query(writehandle, query);
1491 jsonObject* obj = NULL;
1494 obj = jsonNewObject(NULL);
1497 "%s ERROR inserting %s object using query [%s]",
1499 osrfHashGet(meta, "fieldmapper"),
1502 osrfAppSessionStatus(
1504 OSRF_STATUS_INTERNALSERVERERROR,
1505 "osrfMethodException",
1507 "INSERT error -- please see the error log for more details"
1512 char* id = oilsFMGetString(target, pkey);
1514 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1515 growing_buffer* _id = buffer_init(10);
1516 buffer_fadd(_id, "%lld", new_id);
1517 id = buffer_release(_id);
1520 // Find quietness specification, if present
1521 const char* quiet_str = NULL;
1523 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1525 quiet_str = jsonObjectGetString( quiet_obj );
1528 if( str_is_true( quiet_str ) ) { // if quietness is specified
1529 obj = jsonNewObject(id);
1533 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1534 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1536 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1538 jsonObjectFree( where_clause );
1543 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1546 jsonObjectFree( list );
1559 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1569 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1571 const char* id = jsonObjectGetString(jsonObjectGetIndex(ctx->params, id_pos));
1572 jsonObject* order_hash = jsonObjectGetIndex(ctx->params, order_pos);
1576 "%s retrieving %s object with primary key value of %s",
1578 osrfHashGet(meta, "fieldmapper"),
1582 jsonObject* key_param = jsonNewObjectType( JSON_HASH );
1585 osrfHashGet( meta, "primarykey" ),
1586 jsonObjectClone( jsonObjectGetIndex(ctx->params, id_pos) )
1589 jsonObject* list = doFieldmapperSearch( ctx, meta, key_param, order_hash, err );
1592 jsonObjectFree( key_param );
1596 jsonObject* obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1598 jsonObjectFree( list );
1599 jsonObjectFree( key_param );
1602 if(!verifyObjectPCRUD(ctx, obj)) {
1603 jsonObjectFree(obj);
1606 growing_buffer* msg = buffer_init(128);
1607 OSRF_BUFFER_ADD( msg, MODULENAME );
1608 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1610 char* m = buffer_release(msg);
1611 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1622 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1623 growing_buffer* val_buf = buffer_init(32);
1624 const char* numtype = get_datatype( field );
1626 if ( !strncmp( numtype, "INT", 3 ) ) {
1627 if (value->type == JSON_NUMBER)
1628 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1629 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1631 //const char* val_str = jsonObjectGetString( value );
1632 //buffer_fadd( val_buf, "%ld", atol(val_str) );
1633 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1636 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
1637 if (value->type == JSON_NUMBER)
1638 //buffer_fadd( val_buf, "%f", jsonObjectGetNumber(value) );
1639 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1641 //const char* val_str = jsonObjectGetString( value );
1642 //buffer_fadd( val_buf, "%f", atof(val_str) );
1643 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1647 // Presumably this was really intended ot be a string, so quote it
1648 char* str = jsonObjectToSimpleString( value );
1649 if ( dbi_conn_quote_string(dbhandle, &str) ) {
1650 OSRF_BUFFER_ADD( val_buf, str );
1653 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
1655 buffer_free(val_buf);
1660 return buffer_release(val_buf);
1663 static char* searchINPredicate (const char* class, osrfHash* field,
1664 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1665 growing_buffer* sql_buf = buffer_init(32);
1671 osrfHashGet(field, "name")
1675 buffer_add(sql_buf, "IN (");
1676 } else if (!(strcasecmp(op,"not in"))) {
1677 buffer_add(sql_buf, "NOT IN (");
1679 buffer_add(sql_buf, "IN (");
1682 if (node->type == JSON_HASH) {
1683 // subquery predicate
1684 char* subpred = SELECT(
1686 jsonObjectGetKey( node, "select" ),
1687 jsonObjectGetKey( node, "from" ),
1688 jsonObjectGetKey( node, "where" ),
1689 jsonObjectGetKey( node, "having" ),
1690 jsonObjectGetKey( node, "order_by" ),
1691 jsonObjectGetKey( node, "limit" ),
1692 jsonObjectGetKey( node, "offset" ),
1697 buffer_add(sql_buf, subpred);
1700 buffer_free( sql_buf );
1704 } else if (node->type == JSON_ARRAY) {
1705 // literal value list
1706 int in_item_index = 0;
1707 int in_item_first = 1;
1708 const jsonObject* in_item;
1709 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1714 buffer_add(sql_buf, ", ");
1717 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1718 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1719 MODULENAME, json_type( in_item->type ) );
1720 buffer_free(sql_buf);
1724 // Append the literal value -- quoted if not a number
1725 if ( JSON_NUMBER == in_item->type ) {
1726 char* val = jsonNumberToDBString( field, in_item );
1727 OSRF_BUFFER_ADD( sql_buf, val );
1730 } else if ( !strcmp( get_primitive( field ), "number") ) {
1731 char* val = jsonNumberToDBString( field, in_item );
1732 OSRF_BUFFER_ADD( sql_buf, val );
1736 char* key_string = jsonObjectToSimpleString(in_item);
1737 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1738 OSRF_BUFFER_ADD( sql_buf, key_string );
1741 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1743 buffer_free(sql_buf);
1749 if( in_item_first ) {
1750 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1751 buffer_free( sql_buf );
1755 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1756 MODULENAME, json_type( node->type ) );
1757 buffer_free(sql_buf);
1761 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1763 return buffer_release(sql_buf);
1766 // Receive a JSON_ARRAY representing a function call. The first
1767 // entry in the array is the function name. The rest are parameters.
1768 static char* searchValueTransform( const jsonObject* array ) {
1770 if( array->size < 1 ) {
1771 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
1775 // Get the function name
1776 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
1777 if( func_item->type != JSON_STRING ) {
1778 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
1779 MODULENAME, json_type( func_item->type ) );
1783 growing_buffer* sql_buf = buffer_init(32);
1785 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1786 OSRF_BUFFER_ADD( sql_buf, "( " );
1788 // Get the parameters
1789 int func_item_index = 1; // We already grabbed the zeroth entry
1790 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1792 // Add a separator comma, if we need one
1793 if( func_item_index > 2 )
1794 buffer_add( sql_buf, ", " );
1796 // Add the current parameter
1797 if (func_item->type == JSON_NULL) {
1798 buffer_add( sql_buf, "NULL" );
1800 char* val = jsonObjectToSimpleString(func_item);
1801 if ( dbi_conn_quote_string(dbhandle, &val) ) {
1802 OSRF_BUFFER_ADD( sql_buf, val );
1805 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1806 buffer_free(sql_buf);
1813 buffer_add( sql_buf, " )" );
1815 return buffer_release(sql_buf);
1818 static char* searchFunctionPredicate (const char* class, osrfHash* field,
1819 const jsonObject* node, const char* op) {
1821 if( ! is_good_operator( op ) ) {
1822 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
1826 char* val = searchValueTransform(node);
1830 growing_buffer* sql_buf = buffer_init(32);
1835 osrfHashGet(field, "name"),
1842 return buffer_release(sql_buf);
1845 // class is a class name
1846 // field is a field definition as stored in the IDL
1847 // node comes from the method parameter, and may represent an entry in the SELECT list
1848 static char* searchFieldTransform (const char* class, osrfHash* field, const jsonObject* node) {
1849 growing_buffer* sql_buf = buffer_init(32);
1851 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
1852 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
1854 if(transform_subcolumn) {
1855 if( ! is_identifier( transform_subcolumn ) ) {
1856 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
1857 MODULENAME, transform_subcolumn );
1858 buffer_free( sql_buf );
1861 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
1864 if (field_transform) {
1866 if( ! is_identifier( field_transform ) ) {
1867 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
1868 MODULENAME, field_transform );
1869 buffer_free( sql_buf );
1873 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class, osrfHashGet(field, "name"));
1874 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
1877 if( array->type != JSON_ARRAY ) {
1878 osrfLogError( OSRF_LOG_MARK,
1879 "%s: Expected JSON_ARRAY for function params; found %s",
1880 MODULENAME, json_type( array->type ) );
1881 buffer_free( sql_buf );
1884 int func_item_index = 0;
1885 jsonObject* func_item;
1886 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1888 char* val = jsonObjectToSimpleString(func_item);
1891 buffer_add( sql_buf, ",NULL" );
1892 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
1893 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
1894 OSRF_BUFFER_ADD( sql_buf, val );
1896 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1898 buffer_free(sql_buf);
1905 buffer_add( sql_buf, " )" );
1908 buffer_fadd( sql_buf, "\"%s\".%s", class, osrfHashGet(field, "name"));
1911 if (transform_subcolumn)
1912 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
1914 return buffer_release(sql_buf);
1917 static char* searchFieldTransformPredicate (const char* class, osrfHash* field,
1918 const jsonObject* node, const char* op ) {
1920 if( ! is_good_operator( op ) ) {
1921 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
1925 char* field_transform = searchFieldTransform( class, field, node );
1926 if( ! field_transform )
1929 int extra_parens = 0; // boolean
1931 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
1932 if ( ! value_obj ) {
1933 value = searchWHERE( node, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1935 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
1936 free(field_transform);
1940 } else if ( value_obj->type == JSON_ARRAY ) {
1941 value = searchValueTransform( value_obj );
1943 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
1944 free( field_transform );
1947 } else if ( value_obj->type == JSON_HASH ) {
1948 value = searchWHERE( value_obj, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1950 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
1951 free(field_transform);
1955 } else if ( value_obj->type == JSON_NUMBER ) {
1956 value = jsonNumberToDBString( field, value_obj );
1957 } else if ( value_obj->type == JSON_NULL ) {
1958 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
1959 free(field_transform);
1961 } else if ( value_obj->type == JSON_BOOL ) {
1962 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
1963 free(field_transform);
1966 if ( !strcmp( get_primitive( field ), "number") ) {
1967 value = jsonNumberToDBString( field, value_obj );
1969 value = jsonObjectToSimpleString( value_obj );
1970 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
1971 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
1973 free(field_transform);
1979 const char* left_parens = "";
1980 const char* right_parens = "";
1982 if( extra_parens ) {
1987 growing_buffer* sql_buf = buffer_init(32);
1991 "%s%s %s %s %s %s%s",
2002 free(field_transform);
2004 return buffer_release(sql_buf);
2007 static char* searchSimplePredicate (const char* op, const char* class,
2008 osrfHash* field, const jsonObject* node) {
2010 if( ! is_good_operator( op ) ) {
2011 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2017 // Get the value to which we are comparing the specified column
2018 if (node->type != JSON_NULL) {
2019 if ( node->type == JSON_NUMBER ) {
2020 val = jsonNumberToDBString( field, node );
2021 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2022 val = jsonNumberToDBString( field, node );
2024 val = jsonObjectToSimpleString(node);
2029 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2030 // Value is not numeric; enclose it in quotes
2031 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2032 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2038 // Compare to a null value
2039 val = strdup( "NULL" );
2040 if (strcmp( op, "=" ))
2046 growing_buffer* sql_buf = buffer_init(32);
2047 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class, osrfHashGet(field, "name"), op, val );
2048 char* pred = buffer_release( sql_buf );
2055 static char* searchBETWEENPredicate (const char* class, osrfHash* field, const jsonObject* node) {
2057 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2058 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2060 if( NULL == y_node ) {
2061 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2064 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2065 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2072 if ( !strcmp( get_primitive( field ), "number") ) {
2073 x_string = jsonNumberToDBString(field, x_node);
2074 y_string = jsonNumberToDBString(field, y_node);
2077 x_string = jsonObjectToSimpleString(x_node);
2078 y_string = jsonObjectToSimpleString(y_node);
2079 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2080 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2081 MODULENAME, x_string, y_string);
2088 growing_buffer* sql_buf = buffer_init(32);
2089 buffer_fadd( sql_buf, "%s BETWEEN %s AND %s", osrfHashGet(field, "name"), x_string, y_string );
2093 return buffer_release(sql_buf);
2096 static char* searchPredicate ( const char* class, osrfHash* field,
2097 jsonObject* node, osrfMethodContext* ctx ) {
2100 if (node->type == JSON_ARRAY) { // equality IN search
2101 pred = searchINPredicate( class, field, node, NULL, ctx );
2102 } else if (node->type == JSON_HASH) { // other search
2103 jsonIterator* pred_itr = jsonNewIterator( node );
2104 if( !jsonIteratorHasNext( pred_itr ) ) {
2105 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2106 MODULENAME, osrfHashGet(field, "name") );
2108 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2110 // Verify that there are no additional predicates
2111 if( jsonIteratorHasNext( pred_itr ) ) {
2112 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2113 MODULENAME, osrfHashGet(field, "name") );
2114 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2115 pred = searchBETWEENPredicate( class, field, pred_node );
2116 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2117 pred = searchINPredicate( class, field, pred_node, pred_itr->key, ctx );
2118 else if ( pred_node->type == JSON_ARRAY )
2119 pred = searchFunctionPredicate( class, field, pred_node, pred_itr->key );
2120 else if ( pred_node->type == JSON_HASH )
2121 pred = searchFieldTransformPredicate( class, field, pred_node, pred_itr->key );
2123 pred = searchSimplePredicate( pred_itr->key, class, field, pred_node );
2125 jsonIteratorFree(pred_itr);
2127 } else if (node->type == JSON_NULL) { // IS NULL search
2128 growing_buffer* _p = buffer_init(64);
2131 "\"%s\".%s IS NULL",
2133 osrfHashGet(field, "name")
2135 pred = buffer_release(_p);
2136 } else { // equality search
2137 pred = searchSimplePredicate( "=", class, field, node );
2156 field : call_number,
2172 static char* searchJOIN ( const jsonObject* join_hash, osrfHash* leftmeta ) {
2174 const jsonObject* working_hash;
2175 jsonObject* freeable_hash = NULL;
2177 if (join_hash->type == JSON_STRING) {
2178 // create a wrapper around a copy of the original
2179 const char* _tmp = jsonObjectGetString( join_hash );
2180 freeable_hash = jsonNewObjectType(JSON_HASH);
2181 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2182 working_hash = freeable_hash;
2185 if( join_hash->type != JSON_HASH ) {
2188 "%s: JOIN failed; expected JSON object type not found",
2193 working_hash = join_hash;
2196 growing_buffer* join_buf = buffer_init(128);
2197 const char* leftclass = osrfHashGet(leftmeta, "classname");
2199 jsonObject* snode = NULL;
2200 jsonIterator* search_itr = jsonNewIterator( working_hash );
2202 while ( (snode = jsonIteratorNext( search_itr )) ) {
2203 const char* class = search_itr->key;
2204 osrfHash* idlClass = osrfHashGet( oilsIDL(), class );
2208 "%s: JOIN failed. No class \"%s\" defined in IDL",
2212 jsonIteratorFree( search_itr );
2213 buffer_free( join_buf );
2215 jsonObjectFree( freeable_hash );
2219 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2220 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2222 if (field && !fkey) {
2223 // Look up the corresponding join column in the IDL.
2224 // The link must be defined in the child table,
2225 // and point to the right parent table.
2226 osrfHash* idl_link = (osrfHash*) oilsIDLFindPath( "/%s/links/%s", class, field );
2227 const char* reltype = NULL;
2228 const char* other_class = NULL;
2229 reltype = osrfHashGet( idl_link, "reltype" );
2230 if( reltype && strcmp( reltype, "has_many" ) )
2231 other_class = osrfHashGet( idl_link, "class" );
2232 if( other_class && !strcmp( other_class, leftclass ) )
2233 fkey = osrfHashGet( idl_link, "key" );
2237 "%s: JOIN failed. No link defined from %s.%s to %s",
2243 buffer_free(join_buf);
2245 jsonObjectFree(freeable_hash);
2246 jsonIteratorFree(search_itr);
2250 } else if (!field && fkey) {
2251 // Look up the corresponding join column in the IDL.
2252 // The link must be defined in the child table,
2253 // and point to the right parent table.
2254 osrfHash* idl_link = (osrfHash*) oilsIDLFindPath( "/%s/links/%s", leftclass, fkey );
2255 const char* reltype = NULL;
2256 const char* other_class = NULL;
2257 reltype = osrfHashGet( idl_link, "reltype" );
2258 if( reltype && strcmp( reltype, "has_many" ) )
2259 other_class = osrfHashGet( idl_link, "class" );
2260 if( other_class && !strcmp( other_class, class ) )
2261 field = osrfHashGet( idl_link, "key" );
2265 "%s: JOIN failed. No link defined from %s.%s to %s",
2271 buffer_free(join_buf);
2273 jsonObjectFree(freeable_hash);
2274 jsonIteratorFree(search_itr);
2278 } else if (!field && !fkey) {
2279 osrfHash* _links = oilsIDL_links( leftclass );
2281 // For each link defined for the left class:
2282 // see if the link references the joined class
2283 osrfHashIterator* itr = osrfNewHashIterator( _links );
2284 osrfHash* curr_link = NULL;
2285 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2286 const char* other_class = osrfHashGet( curr_link, "class" );
2287 if( other_class && !strcmp( other_class, class ) ) {
2289 // In the IDL, the parent class doesn't know then names of the child
2290 // columns that are pointing to it, so don't use that end of the link
2291 const char* reltype = osrfHashGet( curr_link, "reltype" );
2292 if( reltype && strcmp( reltype, "has_many" ) ) {
2293 // Found a link between the classes
2294 fkey = osrfHashIteratorKey( itr );
2295 field = osrfHashGet( curr_link, "key" );
2300 osrfHashIteratorFree( itr );
2302 if (!field || !fkey) {
2303 // Do another such search, with the classes reversed
2304 _links = oilsIDL_links( class );
2306 // For each link defined for the joined class:
2307 // see if the link references the left class
2308 osrfHashIterator* itr = osrfNewHashIterator( _links );
2309 osrfHash* curr_link = NULL;
2310 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2311 const char* other_class = osrfHashGet( curr_link, "class" );
2312 if( other_class && !strcmp( other_class, leftclass ) ) {
2314 // In the IDL, the parent class doesn't know then names of the child
2315 // columns that are pointing to it, so don't use that end of the link
2316 const char* reltype = osrfHashGet( curr_link, "reltype" );
2317 if( reltype && strcmp( reltype, "has_many" ) ) {
2318 // Found a link between the classes
2319 field = osrfHashIteratorKey( itr );
2320 fkey = osrfHashGet( curr_link, "key" );
2325 osrfHashIteratorFree( itr );
2328 if (!field || !fkey) {
2331 "%s: JOIN failed. No link defined between %s and %s",
2336 buffer_free(join_buf);
2338 jsonObjectFree(freeable_hash);
2339 jsonIteratorFree(search_itr);
2345 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2347 if ( !strcasecmp(type,"left") ) {
2348 buffer_add(join_buf, " LEFT JOIN");
2349 } else if ( !strcasecmp(type,"right") ) {
2350 buffer_add(join_buf, " RIGHT JOIN");
2351 } else if ( !strcasecmp(type,"full") ) {
2352 buffer_add(join_buf, " FULL JOIN");
2354 buffer_add(join_buf, " INNER JOIN");
2357 buffer_add(join_buf, " INNER JOIN");
2360 char* table = getSourceDefinition(idlClass);
2362 jsonIteratorFree( search_itr );
2363 buffer_free( join_buf );
2365 jsonObjectFree( freeable_hash );
2369 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2370 table, class, class, field, leftclass, fkey);
2373 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2375 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2376 if ( filter_op && !strcasecmp("or",filter_op) ) {
2377 buffer_add( join_buf, " OR " );
2379 buffer_add( join_buf, " AND " );
2382 char* jpred = searchWHERE( filter, idlClass, AND_OP_JOIN, NULL );
2384 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2385 OSRF_BUFFER_ADD( join_buf, jpred );
2390 "%s: JOIN failed. Invalid conditional expression.",
2393 jsonIteratorFree( search_itr );
2394 buffer_free( join_buf );
2396 jsonObjectFree( freeable_hash );
2401 buffer_add(join_buf, " ) ");
2403 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2405 char* jpred = searchJOIN( join_filter, idlClass );
2407 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2408 OSRF_BUFFER_ADD( join_buf, jpred );
2411 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2412 jsonIteratorFree( search_itr );
2413 buffer_free( join_buf );
2415 jsonObjectFree( freeable_hash );
2422 jsonObjectFree(freeable_hash);
2423 jsonIteratorFree(search_itr);
2425 return buffer_release(join_buf);
2430 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2431 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2432 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2434 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2436 search_hash is the JSON expression of the conditions.
2437 meta is the class definition from the IDL, for the relevant table.
2438 opjoin_type indicates whether multiple conditions, if present, should be
2439 connected by AND or OR.
2440 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2441 to pass it to other functions -- and all they do with it is to use the session
2442 and request members to send error messages back to the client.
2446 static char* searchWHERE ( const jsonObject* search_hash, osrfHash* meta, int opjoin_type, osrfMethodContext* ctx ) {
2450 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2458 growing_buffer* sql_buf = buffer_init(128);
2460 jsonObject* node = NULL;
2463 if ( search_hash->type == JSON_ARRAY ) {
2464 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2465 jsonIterator* search_itr = jsonNewIterator( search_hash );
2466 if( !jsonIteratorHasNext( search_itr ) ) {
2469 "%s: Invalid predicate structure: empty JSON array",
2472 jsonIteratorFree( search_itr );
2473 buffer_free( sql_buf );
2477 while ( (node = jsonIteratorNext( search_itr )) ) {
2481 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2482 else buffer_add(sql_buf, " AND ");
2485 char* subpred = searchWHERE( node, meta, opjoin_type, ctx );
2487 buffer_fadd(sql_buf, "( %s )", subpred);
2490 jsonIteratorFree( search_itr );
2491 buffer_free( sql_buf );
2495 jsonIteratorFree(search_itr);
2497 } else if ( search_hash->type == JSON_HASH ) {
2498 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2499 jsonIterator* search_itr = jsonNewIterator( search_hash );
2500 if( !jsonIteratorHasNext( search_itr ) ) {
2503 "%s: Invalid predicate structure: empty JSON object",
2506 jsonIteratorFree( search_itr );
2507 buffer_free( sql_buf );
2511 while ( (node = jsonIteratorNext( search_itr )) ) {
2516 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2517 else buffer_add(sql_buf, " AND ");
2520 if ( '+' == search_itr->key[ 0 ] ) {
2521 if ( node->type == JSON_STRING ) {
2522 // Intended purpose; to allow reference to a Boolean column
2524 // Verify that the class alias is not empty
2525 if( '\0' == search_itr->key[ 1 ] ) {
2528 "%s: Table alias is empty",
2531 jsonIteratorFree( search_itr );
2532 buffer_free( sql_buf );
2536 // Verify that the string looks like an identifier.
2537 const char* subpred = jsonObjectGetString( node );
2538 if( ! is_identifier( subpred ) ) {
2541 "%s: Invalid boolean identifier in WHERE clause: \"%s\"",
2545 jsonIteratorFree( search_itr );
2546 buffer_free( sql_buf );
2550 buffer_fadd(sql_buf, " \"%s\".%s ", search_itr->key + 1, subpred);
2552 char* subpred = searchWHERE( node, osrfHashGet( oilsIDL(), search_itr->key + 1 ), AND_OP_JOIN, ctx );
2554 buffer_fadd(sql_buf, "( %s )", subpred);
2557 jsonIteratorFree( search_itr );
2558 buffer_free( sql_buf );
2562 } else if ( !strcasecmp("-or",search_itr->key) ) {
2563 char* subpred = searchWHERE( node, meta, OR_OP_JOIN, ctx );
2565 buffer_fadd(sql_buf, "( %s )", subpred);
2568 buffer_free( sql_buf );
2571 } else if ( !strcasecmp("-and",search_itr->key) ) {
2572 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2574 buffer_fadd(sql_buf, "( %s )", subpred);
2577 buffer_free( sql_buf );
2580 } else if ( !strcasecmp("-not",search_itr->key) ) {
2581 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2583 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2586 buffer_free( sql_buf );
2589 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2590 char* subpred = SELECT(
2592 jsonObjectGetKey( node, "select" ),
2593 jsonObjectGetKey( node, "from" ),
2594 jsonObjectGetKey( node, "where" ),
2595 jsonObjectGetKey( node, "having" ),
2596 jsonObjectGetKey( node, "order_by" ),
2597 jsonObjectGetKey( node, "limit" ),
2598 jsonObjectGetKey( node, "offset" ),
2603 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2606 buffer_free( sql_buf );
2609 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2610 char* subpred = SELECT(
2612 jsonObjectGetKey( node, "select" ),
2613 jsonObjectGetKey( node, "from" ),
2614 jsonObjectGetKey( node, "where" ),
2615 jsonObjectGetKey( node, "having" ),
2616 jsonObjectGetKey( node, "order_by" ),
2617 jsonObjectGetKey( node, "limit" ),
2618 jsonObjectGetKey( node, "offset" ),
2623 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2626 buffer_free( sql_buf );
2632 char* class = osrfHashGet(meta, "classname");
2633 osrfHash* fields = osrfHashGet(meta, "fields");
2634 osrfHash* field = osrfHashGet( fields, search_itr->key );
2638 char* table = getSourceDefinition(meta);
2640 table = strdup( "(?)" );
2643 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2649 buffer_free(sql_buf);
2651 jsonIteratorFree(search_itr);
2655 char* subpred = searchPredicate( class, field, node, ctx );
2657 buffer_add( sql_buf, subpred );
2660 buffer_free(sql_buf);
2661 jsonIteratorFree(search_itr);
2666 jsonIteratorFree(search_itr);
2669 // ERROR ... only hash and array allowed at this level
2670 char* predicate_string = jsonObjectToJSON( search_hash );
2673 "%s: Invalid predicate structure: %s",
2677 buffer_free(sql_buf);
2678 free(predicate_string);
2682 return buffer_release(sql_buf);
2685 // Return 1 if the class is in the FROM clause, or 0 if not
2686 static int is_joined( jsonObject* from_clause, const char* class ) {
2690 else if( from_clause->type == JSON_STRING ) {
2691 if( strcmp( class, jsonObjectGetString( from_clause ) ) )
2695 } else if( from_clause->type != JSON_HASH ) {
2697 } else { // Look at any subjoins
2698 jsonIterator* class_itr = jsonNewIterator( from_clause );
2699 jsonObject* curr_class;
2700 int rc = 0; // return code
2701 while ( ( curr_class = jsonIteratorNext( class_itr ) ) ) {
2702 if( ! strcmp( class_itr->key, class ) ) {
2706 jsonObject* subjoin = jsonObjectGetKey( curr_class, "join" );
2707 if( subjoin && is_joined( subjoin, class ) ) {
2713 jsonIteratorFree( class_itr );
2720 /* method context */ osrfMethodContext* ctx,
2722 /* SELECT */ jsonObject* selhash,
2723 /* FROM */ jsonObject* join_hash,
2724 /* WHERE */ jsonObject* search_hash,
2725 /* HAVING */ jsonObject* having_hash,
2726 /* ORDER BY */ jsonObject* order_hash,
2727 /* LIMIT */ jsonObject* limit,
2728 /* OFFSET */ jsonObject* offset,
2729 /* flags */ int flags
2731 const char* locale = osrf_message_get_last_locale();
2733 // in case we don't get a select list
2734 jsonObject* defaultselhash = NULL;
2736 // general tmp objects
2737 const jsonObject* tmp_const;
2738 jsonObject* selclass = NULL;
2739 jsonObject* selfield = NULL;
2740 jsonObject* snode = NULL;
2741 jsonObject* onode = NULL;
2743 char* string = NULL;
2744 int from_function = 0;
2749 // the core search class
2750 char* core_class = NULL;
2752 // metadata about the core search class
2753 osrfHash* core_meta = NULL;
2755 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale);
2757 // punt if there's no core class
2758 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2761 "%s: FROM clause is missing or empty",
2765 osrfAppSessionStatus(
2767 OSRF_STATUS_INTERNALSERVERERROR,
2768 "osrfMethodException",
2770 "FROM clause is missing or empty in JSON query"
2775 // get the core class -- the only key of the top level FROM clause, or a string
2776 if (join_hash->type == JSON_HASH) {
2777 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2778 snode = jsonIteratorNext( tmp_itr );
2780 core_class = strdup( tmp_itr->key );
2783 jsonObject* extra = jsonIteratorNext( tmp_itr );
2785 jsonIteratorFree( tmp_itr );
2788 // There shouldn't be more than one entry in join_hash
2792 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2796 osrfAppSessionStatus(
2798 OSRF_STATUS_INTERNALSERVERERROR,
2799 "osrfMethodException",
2801 "Malformed FROM clause in JSON query"
2804 return NULL; // Malformed join_hash; extra entry
2806 } else if (join_hash->type == JSON_ARRAY) {
2808 core_class = jsonObjectToSimpleString( jsonObjectGetIndex(join_hash, 0) );
2811 } else if (join_hash->type == JSON_STRING) {
2812 core_class = jsonObjectToSimpleString( join_hash );
2818 "%s: FROM clause is unexpected JSON type: %s",
2820 json_type( join_hash->type )
2823 osrfAppSessionStatus(
2825 OSRF_STATUS_INTERNALSERVERERROR,
2826 "osrfMethodException",
2828 "Ill-formed FROM clause in JSON query"
2834 if (!from_function) {
2835 // Get the IDL class definition for the core class
2836 core_meta = osrfHashGet( oilsIDL(), core_class );
2837 if( !core_meta ) { // Didn't find it?
2840 "%s: SELECT clause references undefined class: \"%s\"",
2845 osrfAppSessionStatus(
2847 OSRF_STATUS_INTERNALSERVERERROR,
2848 "osrfMethodException",
2850 "SELECT clause references undefined class in JSON query"
2856 // Make sure the class isn't virtual
2857 if( str_is_true( osrfHashGet( core_meta, "virtual" ) ) ) {
2860 "%s: Core class is virtual: \"%s\"",
2865 osrfAppSessionStatus(
2867 OSRF_STATUS_INTERNALSERVERERROR,
2868 "osrfMethodException",
2870 "FROM clause references virtual class in JSON query"
2877 // if the select list is empty, or the core class field list is '*',
2878 // build the default select list ...
2880 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
2881 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2882 } else if( selhash->type != JSON_HASH ) {
2885 "%s: Expected JSON_HASH for SELECT clause; found %s",
2887 json_type( selhash->type )
2891 osrfAppSessionStatus(
2893 OSRF_STATUS_INTERNALSERVERERROR,
2894 "osrfMethodException",
2896 "Malformed SELECT clause in JSON query"
2900 } else if ( (tmp_const = jsonObjectGetKeyConst( selhash, core_class )) && tmp_const->type == JSON_STRING ) {
2901 const char* _x = jsonObjectGetString( tmp_const );
2902 if (!strncmp( "*", _x, 1 )) {
2903 jsonObjectRemoveKey( selhash, core_class );
2904 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2909 growing_buffer* sql_buf = buffer_init(128);
2911 // temp buffers for the SELECT list and GROUP BY clause
2912 growing_buffer* select_buf = buffer_init(128);
2913 growing_buffer* group_buf = buffer_init(128);
2915 int aggregate_found = 0; // boolean
2917 // Build a select list
2918 if(from_function) // From a function we select everything
2919 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
2922 // If we need to build a default list, prepare to do so
2923 jsonObject* _tmp = jsonObjectGetKey( selhash, core_class );
2924 if ( _tmp && !_tmp->size ) {
2926 osrfHash* core_fields = osrfHashGet( core_meta, "fields" );
2928 osrfHashIterator* field_itr = osrfNewHashIterator( core_fields );
2929 osrfHash* field_def;
2930 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2931 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2932 // This field is not virtual, so add it to the list
2933 jsonObjectPush( _tmp, jsonNewObject( osrfHashIteratorKey( field_itr ) ) );
2936 osrfHashIteratorFree( field_itr );
2939 // Now build the actual select list
2943 jsonIterator* selclass_itr = jsonNewIterator( selhash );
2944 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
2946 const char* cname = selclass_itr->key;
2948 // Make sure the target relation is in the FROM clause.
2950 // At this point join_hash is a step down from the join_hash we
2951 // received as a parameter. If the original was a JSON_STRING,
2952 // then json_hash is now NULL. If the original was a JSON_HASH,
2953 // then json_hash is now the first (and only) entry in it,
2954 // denoting the core class. We've already excluded the
2955 // possibility that the original was a JSON_ARRAY, because in
2956 // that case from_function would be non-NULL, and we wouldn't
2959 // If the current class isn't the core class
2960 // and it isn't in the join tree, bail out
2961 if ( strcmp( core_class, cname ) && ! is_joined( join_hash, cname ) ) {
2964 "%s: SELECT clause references class not in FROM clause: \"%s\"",
2969 osrfAppSessionStatus(
2971 OSRF_STATUS_INTERNALSERVERERROR,
2972 "osrfMethodException",
2974 "Selected class not in FROM clause in JSON query"
2976 jsonIteratorFree( selclass_itr );
2977 buffer_free( sql_buf );
2978 buffer_free( select_buf );
2979 buffer_free( group_buf );
2980 if( defaultselhash ) jsonObjectFree( defaultselhash );
2985 // Look up some attributes of the current class, so that we
2986 // don't have to look them up again for each field
2987 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
2988 osrfHash* class_field_set = osrfHashGet( idlClass, "fields" );
2989 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
2990 const char* class_tname = osrfHashGet( idlClass, "tablename" );
2992 // stitch together the column list ...
2993 jsonIterator* select_itr = jsonNewIterator( selclass );
2994 while ( (selfield = jsonIteratorNext( select_itr )) ) { // for each SELECT column
2996 // If we need a separator comma, add one
3000 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3003 // ... if it's a string, just toss it on the pile
3004 if (selfield->type == JSON_STRING) {
3006 // Look up the field in the IDL
3007 const char* col_name = jsonObjectGetString( selfield );
3008 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3010 // No such field in current class
3013 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3019 osrfAppSessionStatus(
3021 OSRF_STATUS_INTERNALSERVERERROR,
3022 "osrfMethodException",
3024 "Selected column not defined in JSON query"
3026 jsonIteratorFree( select_itr );
3027 jsonIteratorFree( selclass_itr );
3028 buffer_free( sql_buf );
3029 buffer_free( select_buf );
3030 buffer_free( group_buf );
3031 if( defaultselhash ) jsonObjectFree( defaultselhash );
3034 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3035 // Virtual field not allowed
3038 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3044 osrfAppSessionStatus(
3046 OSRF_STATUS_INTERNALSERVERERROR,
3047 "osrfMethodException",
3049 "Selected column may not be virtual in JSON query"
3051 jsonIteratorFree( select_itr );
3052 jsonIteratorFree( selclass_itr );
3053 buffer_free( sql_buf );
3054 buffer_free( select_buf );
3055 buffer_free( group_buf );
3056 if( defaultselhash ) jsonObjectFree( defaultselhash );
3063 if (flags & DISABLE_I18N)
3066 i18n = osrfHashGet(field_def, "i18n");
3068 if( str_is_true( i18n ) ) {
3069 buffer_fadd( select_buf,
3070 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3071 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3073 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3076 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3079 // ... but it could be an object, in which case we check for a Field Transform
3080 } else if (selfield->type == JSON_HASH) {
3082 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3084 // Get the field definition from the IDL
3085 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3087 // No such field in current class
3090 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3096 osrfAppSessionStatus(
3098 OSRF_STATUS_INTERNALSERVERERROR,
3099 "osrfMethodException",
3101 "Selected column is not defined in JSON query"
3103 jsonIteratorFree( select_itr );
3104 jsonIteratorFree( selclass_itr );
3105 buffer_free( sql_buf );
3106 buffer_free( select_buf );
3107 buffer_free( group_buf );
3108 if( defaultselhash ) jsonObjectFree( defaultselhash );
3111 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3112 // No such field in current class
3115 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3121 osrfAppSessionStatus(
3123 OSRF_STATUS_INTERNALSERVERERROR,
3124 "osrfMethodException",
3126 "Selected column is virtual in JSON query"
3128 jsonIteratorFree( select_itr );
3129 jsonIteratorFree( selclass_itr );
3130 buffer_free( sql_buf );
3131 buffer_free( select_buf );
3132 buffer_free( group_buf );
3133 if( defaultselhash ) jsonObjectFree( defaultselhash );
3138 // Decide what to use as a column alias
3140 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3141 _alias = jsonObjectGetString( tmp_const );
3142 } else { // Use field name as the alias
3146 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3147 char* transform_str = searchFieldTransform(cname, field_def, selfield);
3148 if( transform_str ) {
3149 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3150 free(transform_str);
3153 osrfAppSessionStatus(
3155 OSRF_STATUS_INTERNALSERVERERROR,
3156 "osrfMethodException",
3158 "Unable to generate transform function in JSON query"
3160 jsonIteratorFree( select_itr );
3161 jsonIteratorFree( selclass_itr );
3162 buffer_free( sql_buf );
3163 buffer_free( select_buf );
3164 buffer_free( group_buf );
3165 if( defaultselhash ) jsonObjectFree( defaultselhash );
3173 if (flags & DISABLE_I18N)
3176 i18n = osrfHashGet(field_def, "i18n");
3178 if( str_is_true( i18n ) ) {
3179 buffer_fadd( select_buf,
3180 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3181 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3183 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3186 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3193 "%s: Selected item is unexpected JSON type: %s",
3195 json_type( selfield->type )
3198 osrfAppSessionStatus(
3200 OSRF_STATUS_INTERNALSERVERERROR,
3201 "osrfMethodException",
3203 "Ill-formed SELECT item in JSON query"
3205 jsonIteratorFree( select_itr );
3206 jsonIteratorFree( selclass_itr );
3207 buffer_free( sql_buf );
3208 buffer_free( select_buf );
3209 buffer_free( group_buf );
3210 if( defaultselhash ) jsonObjectFree( defaultselhash );
3215 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3216 if( obj_is_true( agg_obj ) )
3217 aggregate_found = 1;
3219 // Append a comma (except for the first one)
3220 // and add the column to a GROUP BY clause
3224 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3226 buffer_fadd(group_buf, " %d", sel_pos);
3230 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3232 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3233 if ( ! obj_is_true( aggregate_obj ) ) {
3237 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3240 buffer_fadd(group_buf, " %d", sel_pos);
3243 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3247 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3250 _column = searchFieldTransform(cname, field, selfield);
3251 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3252 OSRF_BUFFER_ADD(group_buf, _column);
3253 _column = searchFieldTransform(cname, field, selfield);
3260 } // end while -- iterating across SELECT columns
3262 jsonIteratorFree(select_itr);
3263 } // end while -- iterating across classes
3265 jsonIteratorFree(selclass_itr);
3269 char* col_list = buffer_release(select_buf);
3271 if (from_function) table = searchValueTransform(join_hash);
3272 else table = getSourceDefinition(core_meta);
3276 osrfAppSessionStatus(
3278 OSRF_STATUS_INTERNALSERVERERROR,
3279 "osrfMethodException",
3281 "Unable to identify table for core class"
3284 buffer_free( sql_buf );
3285 buffer_free( group_buf );
3286 if( defaultselhash ) jsonObjectFree( defaultselhash );
3291 // Put it all together
3292 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3296 char* order_by_list = NULL;
3297 char* having_buf = NULL;
3299 if (!from_function) {
3301 // Now, walk the join tree and add that clause
3303 char* join_clause = searchJOIN( join_hash, core_meta );
3305 buffer_add(sql_buf, join_clause);
3309 osrfAppSessionStatus(
3311 OSRF_STATUS_INTERNALSERVERERROR,
3312 "osrfMethodException",
3314 "Unable to construct JOIN clause(s)"
3316 buffer_free( sql_buf );
3317 buffer_free( group_buf );
3318 if( defaultselhash ) jsonObjectFree( defaultselhash );
3324 // Build a WHERE clause, if there is one
3325 if ( search_hash ) {
3326 buffer_add(sql_buf, " WHERE ");
3328 // and it's on the WHERE clause
3329 char* pred = searchWHERE( search_hash, core_meta, AND_OP_JOIN, ctx );
3332 buffer_add(sql_buf, pred);
3336 osrfAppSessionStatus(
3338 OSRF_STATUS_INTERNALSERVERERROR,
3339 "osrfMethodException",
3341 "Severe query error in WHERE predicate -- see error log for more details"
3345 buffer_free(group_buf);
3346 buffer_free(sql_buf);
3347 if (defaultselhash) jsonObjectFree(defaultselhash);
3352 // Build a HAVING clause, if there is one
3353 if ( having_hash ) {
3355 // and it's on the the WHERE clause
3356 having_buf = searchWHERE( having_hash, core_meta, AND_OP_JOIN, ctx );
3358 if( ! having_buf ) {
3360 osrfAppSessionStatus(
3362 OSRF_STATUS_INTERNALSERVERERROR,
3363 "osrfMethodException",
3365 "Severe query error in HAVING predicate -- see error log for more details"
3369 buffer_free(group_buf);
3370 buffer_free(sql_buf);
3371 if (defaultselhash) jsonObjectFree(defaultselhash);
3376 growing_buffer* order_buf = NULL; // to collect ORDER BY list
3378 // Build an ORDER BY clause, if there is one
3379 if( NULL == order_hash )
3380 ; // No ORDER BY? do nothing
3381 else if( JSON_ARRAY == order_hash->type ) {
3382 // Array of field specifications, each specification being a
3383 // hash to define the class, field, and other details
3385 jsonObject* order_spec;
3386 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
3388 if( JSON_HASH != order_spec->type ) {
3389 osrfLogError(OSRF_LOG_MARK,
3390 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
3391 MODULENAME, json_type( order_spec->type ) );
3393 osrfAppSessionStatus(
3395 OSRF_STATUS_INTERNALSERVERERROR,
3396 "osrfMethodException",
3398 "Malformed ORDER BY clause -- see error log for more details"
3400 buffer_free( order_buf );
3403 buffer_free(group_buf);
3404 buffer_free(sql_buf);
3405 if (defaultselhash) jsonObjectFree(defaultselhash);
3410 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
3412 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
3415 OSRF_BUFFER_ADD(order_buf, ", ");
3417 order_buf = buffer_init(128);
3419 if( !field || !class ) {
3420 osrfLogError(OSRF_LOG_MARK,
3421 "%s: Missing class or field name in field specification of ORDER BY clause",
3424 osrfAppSessionStatus(
3426 OSRF_STATUS_INTERNALSERVERERROR,
3427 "osrfMethodException",
3429 "Malformed ORDER BY clause -- see error log for more details"
3431 buffer_free( order_buf );
3434 buffer_free(group_buf);
3435 buffer_free(sql_buf);
3436 if (defaultselhash) jsonObjectFree(defaultselhash);
3440 if ( ! jsonObjectGetKeyConst( selhash, class )
3441 && strcmp( core_class, class )
3442 && ! is_joined( join_hash, class ) ) {
3443 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
3444 "not in FROM clause", MODULENAME, class );
3446 osrfAppSessionStatus(
3448 OSRF_STATUS_INTERNALSERVERERROR,
3449 "osrfMethodException",
3451 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3455 buffer_free(group_buf);
3456 buffer_free(sql_buf);
3457 if (defaultselhash) jsonObjectFree(defaultselhash);
3461 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s", class, field );
3463 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
3464 MODULENAME, class, field );
3466 osrfAppSessionStatus(
3468 OSRF_STATUS_INTERNALSERVERERROR,
3469 "osrfMethodException",
3471 "Invalid field referenced in ORDER BY clause -- see error log for more details"
3475 buffer_free(group_buf);
3476 buffer_free(sql_buf);
3477 if (defaultselhash) jsonObjectFree(defaultselhash);
3479 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3480 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3481 MODULENAME, field );
3483 osrfAppSessionStatus(
3485 OSRF_STATUS_INTERNALSERVERERROR,
3486 "osrfMethodException",
3488 "Virtual field in ORDER BY clause -- see error log for more details"
3490 buffer_free( order_buf );
3493 buffer_free(group_buf);
3494 buffer_free(sql_buf);
3495 if (defaultselhash) jsonObjectFree(defaultselhash);
3499 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
3500 char* transform_str = searchFieldTransform( class, field_def, order_spec );
3501 if( ! transform_str ) {
3503 osrfAppSessionStatus(
3505 OSRF_STATUS_INTERNALSERVERERROR,
3506 "osrfMethodException",
3508 "Severe query error in ORDER BY clause -- see error log for more details"
3510 buffer_free( order_buf );
3513 buffer_free(group_buf);
3514 buffer_free(sql_buf);
3515 if (defaultselhash) jsonObjectFree(defaultselhash);
3519 OSRF_BUFFER_ADD( order_buf, transform_str );
3520 free( transform_str );
3523 buffer_fadd( order_buf, "\"%s\".%s", class, field );
3525 const char* direction =
3526 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
3528 if( direction[ 0 ] || 'D' == direction[ 0 ] )
3529 OSRF_BUFFER_ADD( order_buf, " DESC" );
3531 OSRF_BUFFER_ADD( order_buf, " ASC" );
3534 } else if( JSON_HASH == order_hash->type ) {
3535 // This hash is keyed on class name. Each class has either
3536 // an array of field names or a hash keyed on field name.
3537 jsonIterator* class_itr = jsonNewIterator( order_hash );
3538 while ( (snode = jsonIteratorNext( class_itr )) ) {
3540 if ( ! jsonObjectGetKeyConst( selhash,class_itr->key )
3541 && strcmp( core_class, class_itr->key )
3542 && ! is_joined( join_hash, class_itr->key ) ) {
3543 osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
3544 MODULENAME, class_itr->key );
3546 osrfAppSessionStatus(
3548 OSRF_STATUS_INTERNALSERVERERROR,
3549 "osrfMethodException",
3551 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3553 jsonIteratorFree( class_itr );
3554 buffer_free( order_buf );
3557 buffer_free(group_buf);
3558 buffer_free(sql_buf);
3559 if (defaultselhash) jsonObjectFree(defaultselhash);
3563 osrfHash* field_list_def = oilsIDLFindPath( "/%s/fields", class_itr->key );
3565 if ( snode->type == JSON_HASH ) {
3567 // Hash is keyed on field names from the current class. For each field
3568 // there is another layer of hash to define the sorting details, if any,
3569 // or a string to indicate direction of sorting.
3570 jsonIterator* order_itr = jsonNewIterator( snode );
3571 while ( (onode = jsonIteratorNext( order_itr )) ) {
3573 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
3575 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3576 MODULENAME, order_itr->key );
3578 osrfAppSessionStatus(
3580 OSRF_STATUS_INTERNALSERVERERROR,
3581 "osrfMethodException",
3583 "Invalid field in ORDER BY clause -- see error log for more details"
3585 jsonIteratorFree( order_itr );
3586 jsonIteratorFree( class_itr );
3587 buffer_free( order_buf );
3590 buffer_free(group_buf);
3591 buffer_free(sql_buf);
3592 if (defaultselhash) jsonObjectFree(defaultselhash);
3594 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3595 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3596 MODULENAME, order_itr->key );
3598 osrfAppSessionStatus(
3600 OSRF_STATUS_INTERNALSERVERERROR,
3601 "osrfMethodException",
3603 "Virtual field in ORDER BY clause -- see error log for more details"
3605 jsonIteratorFree( order_itr );
3606 jsonIteratorFree( class_itr );
3607 buffer_free( order_buf );
3610 buffer_free(group_buf);
3611 buffer_free(sql_buf);
3612 if (defaultselhash) jsonObjectFree(defaultselhash);
3616 const char* direction = NULL;
3617 if ( onode->type == JSON_HASH ) {
3618 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3619 string = searchFieldTransform(
3621 osrfHashGet( field_list_def, order_itr->key ),
3625 if( ctx ) osrfAppSessionStatus(
3627 OSRF_STATUS_INTERNALSERVERERROR,
3628 "osrfMethodException",
3630 "Severe query error in ORDER BY clause -- see error log for more details"
3632 jsonIteratorFree( order_itr );
3633 jsonIteratorFree( class_itr );
3636 buffer_free(group_buf);
3637 buffer_free(order_buf);
3638 buffer_free(sql_buf);
3639 if (defaultselhash) jsonObjectFree(defaultselhash);
3643 growing_buffer* field_buf = buffer_init(16);
3644 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3645 string = buffer_release(field_buf);
3648 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3649 const char* dir = jsonObjectGetString(tmp_const);
3650 if (!strncasecmp(dir, "d", 1)) {
3651 direction = " DESC";
3657 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
3658 osrfLogError( OSRF_LOG_MARK,
3659 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
3660 MODULENAME, json_type( onode->type ) );
3662 osrfAppSessionStatus(
3664 OSRF_STATUS_INTERNALSERVERERROR,
3665 "osrfMethodException",
3667 "Malformed 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 string = strdup(order_itr->key);
3681 const char* dir = jsonObjectGetString(onode);
3682 if (!strncasecmp(dir, "d", 1)) {
3683 direction = " DESC";
3690 OSRF_BUFFER_ADD(order_buf, ", ");
3692 order_buf = buffer_init(128);
3694 OSRF_BUFFER_ADD(order_buf, string);
3698 OSRF_BUFFER_ADD(order_buf, direction);
3702 jsonIteratorFree(order_itr);
3704 } else if ( snode->type == JSON_ARRAY ) {
3706 // Array is a list of fields from the current class
3707 unsigned long order_idx = 0;
3708 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
3710 const char* _f = jsonObjectGetString( onode );
3712 osrfHash* field_def = osrfHashGet( field_list_def, _f );
3714 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3717 osrfAppSessionStatus(
3719 OSRF_STATUS_INTERNALSERVERERROR,
3720 "osrfMethodException",
3722 "Invalid field in ORDER BY clause -- see error log for more details"
3724 jsonIteratorFree( class_itr );
3725 buffer_free( order_buf );
3728 buffer_free(group_buf);
3729 buffer_free(sql_buf);
3730 if (defaultselhash) jsonObjectFree(defaultselhash);
3732 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3733 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3736 osrfAppSessionStatus(
3738 OSRF_STATUS_INTERNALSERVERERROR,
3739 "osrfMethodException",
3741 "Virtual field in ORDER BY clause -- see error log for more details"
3743 jsonIteratorFree( class_itr );
3744 buffer_free( order_buf );
3747 buffer_free(group_buf);
3748 buffer_free(sql_buf);
3749 if (defaultselhash) jsonObjectFree(defaultselhash);
3754 OSRF_BUFFER_ADD(order_buf, ", ");
3756 order_buf = buffer_init(128);
3758 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
3762 // IT'S THE OOOOOOOOOOOLD STYLE!
3764 osrfLogError(OSRF_LOG_MARK,
3765 "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3767 osrfAppSessionStatus(
3769 OSRF_STATUS_INTERNALSERVERERROR,
3770 "osrfMethodException",
3772 "Severe query error -- see error log for more details"
3778 buffer_free(group_buf);
3779 buffer_free(order_buf);
3780 buffer_free(sql_buf);
3781 if (defaultselhash) jsonObjectFree(defaultselhash);
3782 jsonIteratorFree(class_itr);
3786 jsonIteratorFree( class_itr );
3788 osrfLogError(OSRF_LOG_MARK,
3789 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
3790 MODULENAME, json_type( order_hash->type ) );
3792 osrfAppSessionStatus(
3794 OSRF_STATUS_INTERNALSERVERERROR,
3795 "osrfMethodException",
3797 "Malformed ORDER BY clause -- see error log for more details"
3799 buffer_free( order_buf );
3802 buffer_free(group_buf);
3803 buffer_free(sql_buf);
3804 if (defaultselhash) jsonObjectFree(defaultselhash);
3809 order_by_list = buffer_release( order_buf );
3813 string = buffer_release(group_buf);
3815 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
3816 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3817 OSRF_BUFFER_ADD( sql_buf, string );
3822 if( having_buf && *having_buf ) {
3823 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3824 OSRF_BUFFER_ADD( sql_buf, having_buf );
3828 if( order_by_list ) {
3830 if ( *order_by_list ) {
3831 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3832 OSRF_BUFFER_ADD( sql_buf, order_by_list );
3835 free( order_by_list );
3839 const char* str = jsonObjectGetString(limit);
3840 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
3844 const char* str = jsonObjectGetString(offset);
3845 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
3848 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3851 if (defaultselhash) jsonObjectFree(defaultselhash);
3853 return buffer_release(sql_buf);
3857 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
3859 const char* locale = osrf_message_get_last_locale();
3861 osrfHash* fields = osrfHashGet(meta, "fields");
3862 char* core_class = osrfHashGet(meta, "classname");
3864 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
3866 jsonObject* node = NULL;
3867 jsonObject* snode = NULL;
3868 jsonObject* onode = NULL;
3869 const jsonObject* _tmp = NULL;
3870 jsonObject* selhash = NULL;
3871 jsonObject* defaultselhash = NULL;
3873 growing_buffer* sql_buf = buffer_init(128);
3874 growing_buffer* select_buf = buffer_init(128);
3876 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
3877 defaultselhash = jsonNewObjectType(JSON_HASH);
3878 selhash = defaultselhash;
3881 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
3882 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
3883 jsonObject* flist = jsonObjectGetKey( selhash, core_class );
3888 osrfStringArray* keys = osrfHashKeys( fields );
3889 while ( (field = osrfStringArrayGetString(keys, i++)) ) {
3890 if( ! str_is_true( osrfHashGet( osrfHashGet( fields, field ), "virtual" ) ) )
3891 jsonObjectPush( flist, jsonNewObject( field ) );
3893 osrfStringArrayFree(keys);
3897 jsonIterator* class_itr = jsonNewIterator( selhash );
3898 while ( (snode = jsonIteratorNext( class_itr )) ) {
3900 char* cname = class_itr->key;
3901 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
3902 if (!idlClass) continue;
3904 if (strcmp(core_class,class_itr->key)) {
3905 if (!join_hash) continue;
3907 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
3909 jsonObjectFree(found);
3913 jsonObjectFree(found);
3916 jsonIterator* select_itr = jsonNewIterator( snode );
3917 while ( (node = jsonIteratorNext( select_itr )) ) {
3918 const char* item_str = jsonObjectGetString( node );
3919 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
3920 char* fname = osrfHashGet(field, "name");
3922 if (!field) continue;
3927 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
3932 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
3933 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
3936 i18n = osrfHashGet(field, "i18n");
3938 if( str_is_true( i18n ) ) {
3939 char* pkey = osrfHashGet(idlClass, "primarykey");
3940 char* tname = osrfHashGet(idlClass, "tablename");
3942 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);
3944 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3947 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3951 jsonIteratorFree(select_itr);
3954 jsonIteratorFree(class_itr);
3956 char* col_list = buffer_release(select_buf);
3957 char* table = getSourceDefinition(meta);
3959 table = strdup( "(null)" );
3961 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
3966 char* join_clause = searchJOIN( join_hash, meta );
3967 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
3968 OSRF_BUFFER_ADD(sql_buf, join_clause);
3972 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
3973 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
3975 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
3977 char* pred = searchWHERE( search_hash, meta, AND_OP_JOIN, ctx );
3979 osrfAppSessionStatus(
3981 OSRF_STATUS_INTERNALSERVERERROR,
3982 "osrfMethodException",
3984 "Severe query error -- see error log for more details"
3986 buffer_free(sql_buf);
3987 if(defaultselhash) jsonObjectFree(defaultselhash);
3990 buffer_add(sql_buf, pred);
3995 char* string = NULL;
3996 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
3998 growing_buffer* order_buf = buffer_init(128);
4001 jsonIterator* class_itr = jsonNewIterator( _tmp );
4002 while ( (snode = jsonIteratorNext( class_itr )) ) {
4004 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4007 if ( snode->type == JSON_HASH ) {
4009 jsonIterator* order_itr = jsonNewIterator( snode );
4010 while ( (onode = jsonIteratorNext( order_itr )) ) {
4012 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4013 class_itr->key, order_itr->key );
4017 char* direction = NULL;
4018 if ( onode->type == JSON_HASH ) {
4019 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4020 string = searchFieldTransform( class_itr->key, field_def, onode );
4022 osrfAppSessionStatus(
4024 OSRF_STATUS_INTERNALSERVERERROR,
4025 "osrfMethodException",
4027 "Severe query error in ORDER BY clause -- see error log for more details"
4029 jsonIteratorFree( order_itr );
4030 jsonIteratorFree( class_itr );
4031 buffer_free( order_buf );
4032 buffer_free( sql_buf );
4033 if( defaultselhash ) jsonObjectFree( defaultselhash );
4037 growing_buffer* field_buf = buffer_init(16);
4038 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4039 string = buffer_release(field_buf);
4042 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4043 const char* dir = jsonObjectGetString(_tmp);
4044 if (!strncasecmp(dir, "d", 1)) {
4045 direction = " DESC";
4052 string = strdup(order_itr->key);
4053 const char* dir = jsonObjectGetString(onode);
4054 if (!strncasecmp(dir, "d", 1)) {
4055 direction = " DESC";
4064 buffer_add(order_buf, ", ");
4067 buffer_add(order_buf, string);
4071 buffer_add(order_buf, direction);
4076 jsonIteratorFree(order_itr);
4079 const char* str = jsonObjectGetString(snode);
4080 buffer_add(order_buf, str);
4086 jsonIteratorFree(class_itr);
4088 string = buffer_release(order_buf);
4091 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4092 OSRF_BUFFER_ADD( sql_buf, string );
4098 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4099 const char* str = jsonObjectGetString(_tmp);
4107 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4109 const char* str = jsonObjectGetString(_tmp);
4118 if (defaultselhash) jsonObjectFree(defaultselhash);
4120 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4121 return buffer_release(sql_buf);
4124 int doJSONSearch ( osrfMethodContext* ctx ) {
4125 if(osrfMethodVerifyContext( ctx )) {
4126 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4130 osrfLogDebug(OSRF_LOG_MARK, "Recieved query request");
4135 dbhandle = writehandle;
4137 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4141 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4142 flags |= SELECT_DISTINCT;
4144 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4145 flags |= DISABLE_I18N;
4147 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4150 jsonObjectGetKey( hash, "select" ),
4151 jsonObjectGetKey( hash, "from" ),
4152 jsonObjectGetKey( hash, "where" ),
4153 jsonObjectGetKey( hash, "having" ),
4154 jsonObjectGetKey( hash, "order_by" ),
4155 jsonObjectGetKey( hash, "limit" ),
4156 jsonObjectGetKey( hash, "offset" ),
4165 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4166 dbi_result result = dbi_conn_query(dbhandle, sql);
4169 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4171 if (dbi_result_first_row(result)) {
4172 /* JSONify the result */
4173 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4176 jsonObject* return_val = oilsMakeJSONFromResult( result );
4177 osrfAppRespond( ctx, return_val );
4178 jsonObjectFree( return_val );
4179 } while (dbi_result_next_row(result));
4182 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4185 osrfAppRespondComplete( ctx, NULL );
4187 /* clean up the query */
4188 dbi_result_free(result);
4192 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4193 osrfAppSessionStatus(
4195 OSRF_STATUS_INTERNALSERVERERROR,
4196 "osrfMethodException",
4198 "Severe query error -- see error log for more details"
4206 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4207 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4210 dbhandle = writehandle;
4212 osrfHash* links = osrfHashGet(meta, "links");
4213 osrfHash* fields = osrfHashGet(meta, "fields");
4214 char* core_class = osrfHashGet(meta, "classname");
4215 char* pkey = osrfHashGet(meta, "primarykey");
4217 const jsonObject* _tmp;
4220 char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4222 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4227 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4229 dbi_result result = dbi_conn_query(dbhandle, sql);
4230 if( NULL == result ) {
4231 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4232 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4233 osrfAppSessionStatus(
4235 OSRF_STATUS_INTERNALSERVERERROR,
4236 "osrfMethodException",
4238 "Severe query error -- see error log for more details"
4245 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4248 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
4249 osrfHash* dedup = osrfNewHash();
4251 if (dbi_result_first_row(result)) {
4252 /* JSONify the result */
4253 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4255 obj = oilsMakeFieldmapperFromResult( result, meta );
4256 char* pkey_val = oilsFMGetString( obj, pkey );
4257 if ( osrfHashGet( dedup, pkey_val ) ) {
4258 jsonObjectFree(obj);
4261 osrfHashSet( dedup, pkey_val, pkey_val );
4262 jsonObjectPush(res_list, obj);
4264 } while (dbi_result_next_row(result));
4266 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
4270 osrfHashFree(dedup);
4271 /* clean up the query */
4272 dbi_result_free(result);
4275 if (res_list->size && query_hash) {
4276 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
4278 int x = (int)jsonObjectGetNumber(_tmp);
4279 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
4281 const jsonObject* temp_blob;
4282 if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
4284 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
4285 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
4287 osrfStringArray* link_fields = NULL;
4290 if (flesh_fields->size == 1) {
4291 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
4292 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
4297 link_fields = osrfNewStringArray(1);
4298 jsonIterator* _i = jsonNewIterator( flesh_fields );
4299 while ((_f = jsonIteratorNext( _i ))) {
4300 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4302 jsonIteratorFree(_i);
4307 jsonIterator* itr = jsonNewIterator( res_list );
4308 while ((cur = jsonIteratorNext( itr ))) {
4313 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4315 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4317 osrfHash* kid_link = osrfHashGet(links, link_field);
4318 if (!kid_link) continue;
4320 osrfHash* field = osrfHashGet(fields, link_field);
4321 if (!field) continue;
4323 osrfHash* value_field = field;
4325 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4326 if (!kid_idl) continue;
4328 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4329 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4332 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4333 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4336 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4338 if (link_map->size > 0) {
4339 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4342 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4347 osrfHashGet(kid_link, "class"),
4354 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4355 osrfHashGet(kid_link, "field"),
4356 osrfHashGet(kid_link, "class"),
4357 osrfHashGet(kid_link, "key"),
4358 osrfHashGet(kid_link, "reltype")
4361 const char* search_key = jsonObjectGetString(
4364 atoi( osrfHashGet(value_field, "array_position") )
4369 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4373 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
4375 // construct WHERE clause
4376 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
4379 osrfHashGet(kid_link, "key"),
4380 jsonNewObject( search_key )
4383 // construct the rest of the query
4384 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
4385 jsonObjectSetKey( rest_of_query, "flesh",
4386 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4390 jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
4392 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
4393 jsonObjectSetKey( rest_of_query, "order_by",
4394 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
4398 if (jsonObjectGetKeyConst(query_hash, "select")) {
4399 jsonObjectSetKey( rest_of_query, "select",
4400 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
4404 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
4405 where_clause, rest_of_query, err);
4407 jsonObjectFree( where_clause );
4408 jsonObjectFree( rest_of_query );
4411 osrfStringArrayFree(link_fields);
4412 jsonIteratorFree(itr);
4413 jsonObjectFree(res_list);
4414 jsonObjectFree(flesh_blob);
4418 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4420 jsonObject* X = NULL;
4421 if ( link_map->size > 0 && kids->size > 0 ) {
4423 kids = jsonNewObjectType(JSON_ARRAY);
4425 jsonObject* _k_node;
4426 jsonIterator* _k = jsonNewIterator( X );
4427 while ((_k_node = jsonIteratorNext( _k ))) {
4433 (unsigned long)atoi(
4439 osrfHashGet(kid_link, "class")
4443 osrfStringArrayGetString( link_map, 0 )
4452 jsonIteratorFree(_k);
4455 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4456 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4459 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4460 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4464 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4465 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4468 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4469 jsonObjectClone( kids )
4474 jsonObjectFree(kids);
4478 jsonObjectFree( kids );
4480 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4481 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4485 jsonObjectFree( flesh_blob );
4486 osrfStringArrayFree(link_fields);
4487 jsonIteratorFree(itr);
4496 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
4498 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4500 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
4502 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
4505 if (!verifyObjectClass(ctx, target)) {
4510 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4511 osrfAppSessionStatus(
4513 OSRF_STATUS_BADREQUEST,
4514 "osrfMethodException",
4516 "No active transaction -- required for UPDATE"
4522 // The following test is harmless but redundant. If a class is
4523 // readonly, we don't register an update method for it.
4524 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4525 osrfAppSessionStatus(
4527 OSRF_STATUS_BADREQUEST,
4528 "osrfMethodException",
4530 "Cannot UPDATE readonly class"
4536 dbhandle = writehandle;
4538 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4540 // Set the last_xact_id
4541 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4543 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
4544 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4547 char* pkey = osrfHashGet(meta, "primarykey");
4548 osrfHash* fields = osrfHashGet(meta, "fields");
4550 char* id = oilsFMGetString( target, pkey );
4554 "%s updating %s object with %s = %s",
4556 osrfHashGet(meta, "fieldmapper"),
4561 growing_buffer* sql = buffer_init(128);
4562 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4567 osrfStringArray* field_list = osrfHashKeys( fields );
4568 while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
4570 osrfHash* field = osrfHashGet( fields, field_name );
4572 if(!( strcmp( field_name, pkey ) )) continue;
4573 if( str_is_true( osrfHashGet(osrfHashGet(fields,field_name), "virtual") ) )
4576 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4578 int value_is_numeric = 0; // boolean
4580 if (field_object && field_object->classname) {
4581 value = oilsFMGetString(
4583 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4586 value = jsonObjectToSimpleString( field_object );
4587 if( field_object && JSON_NUMBER == field_object->type )
4588 value_is_numeric = 1;
4591 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s", osrfHashGet(meta, "fieldmapper"), field_name, value);
4593 if (!field_object || field_object->type == JSON_NULL) {
4594 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) ) && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
4595 if (first) first = 0;
4596 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4597 buffer_fadd( sql, " %s = NULL", field_name );
4600 } else if ( value_is_numeric || !strcmp( get_primitive( field ), "number") ) {
4601 if (first) first = 0;
4602 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4604 const char* numtype = get_datatype( field );
4605 if ( !strncmp( numtype, "INT", 3 ) ) {
4606 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
4607 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
4608 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
4610 // Must really be intended as a string, so quote it
4611 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4612 buffer_fadd( sql, " %s = %s", field_name, value );
4614 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4615 osrfAppSessionStatus(
4617 OSRF_STATUS_INTERNALSERVERERROR,
4618 "osrfMethodException",
4620 "Error quoting string -- please see the error log for more details"
4630 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
4633 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4634 if (first) first = 0;
4635 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4636 buffer_fadd( sql, " %s = %s", field_name, value );
4639 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4640 osrfAppSessionStatus(
4642 OSRF_STATUS_INTERNALSERVERERROR,
4643 "osrfMethodException",
4645 "Error quoting string -- please see the error log for more details"
4659 jsonObject* obj = jsonNewObject(id);
4661 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4662 dbi_conn_quote_string(dbhandle, &id);
4664 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
4666 char* query = buffer_release(sql);
4667 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
4669 dbi_result result = dbi_conn_query(dbhandle, query);
4673 jsonObjectFree(obj);
4674 obj = jsonNewObject(NULL);
4677 "%s ERROR updating %s object with %s = %s",
4679 osrfHashGet(meta, "fieldmapper"),
4690 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4692 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4694 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4695 osrfAppSessionStatus(
4697 OSRF_STATUS_BADREQUEST,
4698 "osrfMethodException",
4700 "No active transaction -- required for DELETE"
4706 // The following test is harmless but redundant. If a class is
4707 // readonly, we don't register a delete method for it.
4708 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4709 osrfAppSessionStatus(
4711 OSRF_STATUS_BADREQUEST,
4712 "osrfMethodException",
4714 "Cannot DELETE readonly class"
4720 dbhandle = writehandle;
4724 char* pkey = osrfHashGet(meta, "primarykey");
4732 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4733 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4738 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4741 if (!verifyObjectPCRUD( ctx, NULL )) {
4746 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4751 "%s deleting %s object with %s = %s",
4753 osrfHashGet(meta, "fieldmapper"),
4758 obj = jsonNewObject(id);
4760 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4761 dbi_conn_quote_string(writehandle, &id);
4763 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4766 jsonObjectFree(obj);
4767 obj = jsonNewObject(NULL);
4770 "%s ERROR deleting %s object with %s = %s",
4772 osrfHashGet(meta, "fieldmapper"),
4785 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4786 if(!(result && meta)) return jsonNULL;
4788 jsonObject* object = jsonNewObject(NULL);
4789 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4791 osrfHash* fields = osrfHashGet(meta, "fields");
4793 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4797 char dt_string[256];
4801 int columnIndex = 1;
4803 unsigned short type;
4804 const char* columnName;
4806 /* cycle through the column list */
4807 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
4809 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4811 fmIndex = -1; // reset the position
4813 /* determine the field type and storage attributes */
4814 type = dbi_result_get_field_type_idx(result, columnIndex);
4815 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
4817 /* fetch the fieldmapper index */
4818 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4820 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4823 const char* pos = (char*)osrfHashGet(_f, "array_position");
4824 if ( !pos ) continue;
4826 fmIndex = atoi( pos );
4827 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
4832 if (dbi_result_field_is_null_idx(result, columnIndex)) {
4833 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
4838 case DBI_TYPE_INTEGER :
4840 if( attr & DBI_INTEGER_SIZE8 )
4841 jsonObjectSetIndex( object, fmIndex,
4842 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
4844 jsonObjectSetIndex( object, fmIndex,
4845 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
4849 case DBI_TYPE_DECIMAL :
4850 jsonObjectSetIndex( object, fmIndex,
4851 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
4854 case DBI_TYPE_STRING :
4860 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
4865 case DBI_TYPE_DATETIME :
4867 memset(dt_string, '\0', sizeof(dt_string));
4868 memset(&gmdt, '\0', sizeof(gmdt));
4870 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
4873 if (!(attr & DBI_DATETIME_DATE)) {
4874 gmtime_r( &_tmp_dt, &gmdt );
4875 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4876 } else if (!(attr & DBI_DATETIME_TIME)) {
4877 localtime_r( &_tmp_dt, &gmdt );
4878 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4880 localtime_r( &_tmp_dt, &gmdt );
4881 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4884 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
4888 case DBI_TYPE_BINARY :
4889 osrfLogError( OSRF_LOG_MARK,
4890 "Can't do binary at column %s : index %d", columnName, columnIndex);
4899 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
4900 if(!result) return jsonNULL;
4902 jsonObject* object = jsonNewObject(NULL);
4905 char dt_string[256];
4909 int columnIndex = 1;
4911 unsigned short type;
4912 const char* columnName;
4914 /* cycle through the column list */
4915 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
4917 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4919 fmIndex = -1; // reset the position
4921 /* determine the field type and storage attributes */
4922 type = dbi_result_get_field_type_idx(result, columnIndex);
4923 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
4925 if (dbi_result_field_is_null_idx(result, columnIndex)) {
4926 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
4931 case DBI_TYPE_INTEGER :
4933 if( attr & DBI_INTEGER_SIZE8 )
4934 jsonObjectSetKey( object, columnName,
4935 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)) );
4937 jsonObjectSetKey( object, columnName,
4938 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
4941 case DBI_TYPE_DECIMAL :
4942 jsonObjectSetKey( object, columnName,
4943 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
4946 case DBI_TYPE_STRING :
4947 jsonObjectSetKey( object, columnName,
4948 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
4951 case DBI_TYPE_DATETIME :
4953 memset(dt_string, '\0', sizeof(dt_string));
4954 memset(&gmdt, '\0', sizeof(gmdt));
4956 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
4959 if (!(attr & DBI_DATETIME_DATE)) {
4960 gmtime_r( &_tmp_dt, &gmdt );
4961 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4962 } else if (!(attr & DBI_DATETIME_TIME)) {
4963 localtime_r( &_tmp_dt, &gmdt );
4964 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4966 localtime_r( &_tmp_dt, &gmdt );
4967 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4970 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
4973 case DBI_TYPE_BINARY :
4974 osrfLogError( OSRF_LOG_MARK,
4975 "Can't do binary at column %s : index %d", columnName, columnIndex );
4979 } // end while loop traversing result
4984 // Interpret a string as true or false
4985 static int str_is_true( const char* str ) {
4986 if( NULL == str || strcasecmp( str, "true" ) )
4992 // Interpret a jsonObject as true or false
4993 static int obj_is_true( const jsonObject* obj ) {
4996 else switch( obj->type )
5004 if( strcasecmp( obj->value.s, "true" ) )
5008 case JSON_NUMBER : // Support 1/0 for perl's sake
5009 if( jsonObjectGetNumber( obj ) == 1.0 )
5018 // Translate a numeric code into a text string identifying a type of
5019 // jsonObject. To be used for building error messages.
5020 static const char* json_type( int code ) {
5026 return "JSON_ARRAY";
5028 return "JSON_STRING";
5030 return "JSON_NUMBER";
5036 return "(unrecognized)";
5040 // Extract the "primitive" attribute from an IDL field definition.
5041 // If we haven't initialized the app, then we must be running in
5042 // some kind of testbed. In that case, default to "string".
5043 static const char* get_primitive( osrfHash* field ) {
5044 const char* s = osrfHashGet( field, "primitive" );
5046 if( child_initialized )
5049 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5051 osrfHashGet( field, "name" )
5059 // Extract the "datatype" attribute from an IDL field definition.
5060 // If we haven't initialized the app, then we must be running in
5061 // some kind of testbed. In that case, default to to NUMERIC,
5062 // since we look at the datatype only for numbers.
5063 static const char* get_datatype( osrfHash* field ) {
5064 const char* s = osrfHashGet( field, "datatype" );
5066 if( child_initialized )
5069 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5071 osrfHashGet( field, "name" )
5080 If the input string is potentially a valid SQL identifier, return 1.
5083 Purpose: to prevent certain kinds of SQL injection. To that end we
5084 don't necessarily need to follow all the rules exactly, such as requiring
5085 that the first character not be a digit.
5087 We allow leading and trailing white space. In between, we do not allow
5088 punctuation (except for underscores and dollar signs), control
5089 characters, or embedded white space.
5091 More pedantically we should allow quoted identifiers containing arbitrary
5092 characters, but for the foreseeable future such quoted identifiers are not
5093 likely to be an issue.
5095 static int is_identifier( const char* s) {
5099 // Skip leading white space
5100 while( isspace( (unsigned char) *s ) )
5104 return 0; // Nothing but white space? Not okay.
5106 // Check each character until we reach white space or
5107 // end-of-string. Letters, digits, underscores, and
5108 // dollar signs are okay. With the exception of periods
5109 // (as in schema.identifier), control characters and other
5110 // punctuation characters are not okay. Anything else
5111 // is okay -- it could for example be part of a multibyte
5112 // UTF8 character such as a letter with diacritical marks,
5113 // and those are allowed.
5115 if( isalnum( (unsigned char) *s )
5119 ; // Fine; keep going
5120 else if( ispunct( (unsigned char) *s )
5121 || iscntrl( (unsigned char) *s ) )
5124 } while( *s && ! isspace( (unsigned char) *s ) );
5126 // If we found any white space in the above loop,
5127 // the rest had better be all white space.
5129 while( isspace( (unsigned char) *s ) )
5133 return 0; // White space was embedded within non-white space
5139 Determine whether to accept a character string as a comparison operator.
5140 Return 1 if it's good, or 0 if it's bad.
5142 We don't validate it for real. We just make sure that it doesn't contain
5143 any semicolons or white space (with a special exception for the
5144 "SIMILAR TO" operator). The idea is to block certain kinds of SQL
5145 injection. If it has no semicolons or white space but it's still not a
5146 valid operator, then the database will complain.
5148 Another approach would be to compare the string against a short list of
5149 approved operators. We don't do that because we want to allow custom
5150 operators like ">100*", which would be difficult or impossible to
5151 express otherwise in a JSON query.
5153 static int is_good_operator( const char* op ) {
5154 if( !op ) return 0; // Sanity check
5158 if( isspace( (unsigned char) *s ) ) {
5159 // Special exception for SIMILAR TO. Someday we might make
5160 // exceptions for IS DISTINCT FROM and IS NOT DISTINCT FROM.
5161 if( !strcasecmp( op, "similar to" ) )
5166 else if( ';' == *s )