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 unsigned long res_idx = 0;
792 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
794 if(!verifyObjectPCRUD(ctx, cur)) continue;
796 osrfAppRespond( ctx, cur );
798 osrfAppRespondComplete( ctx, NULL );
800 } else if (!strcmp(methodtype, "id_list")) {
802 jsonObject* where_clause;
803 jsonObject* rest_of_query;
805 // We use the where clause without change. But we need
806 // to massage the rest of the query, so we work with a copy
807 // of it instead of modifying the original.
809 where_clause = jsonObjectGetIndex( ctx->params, 1 );
810 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
812 where_clause = jsonObjectGetIndex( ctx->params, 0 );
813 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
816 if ( rest_of_query ) {
817 jsonObjectRemoveKey( rest_of_query, "select" );
818 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
819 jsonObjectRemoveKey( rest_of_query, "flesh" );
820 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
822 rest_of_query = jsonNewObjectType( JSON_HASH );
825 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
831 "{ \"%s\":[\"%s\"] }",
832 osrfHashGet( class_obj, "classname" ),
833 osrfHashGet( class_obj, "primarykey" )
837 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
839 jsonObjectFree( rest_of_query );
843 unsigned long res_idx = 0;
844 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
846 if(!verifyObjectPCRUD(ctx, cur)) continue;
850 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
853 osrfAppRespondComplete( ctx, NULL );
856 osrfAppRespondComplete( ctx, obj );
864 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
867 osrfHash* meta = (osrfHash*) ctx->method->userData;
868 osrfHash* class = osrfHashGet( meta, "class" );
870 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
872 growing_buffer* msg = buffer_init(128);
875 "%s: %s method for type %s was passed a %s",
877 osrfHashGet(meta, "methodtype"),
878 osrfHashGet(class, "classname"),
882 char* m = buffer_release(msg);
883 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException", ctx->request, m );
891 ret = verifyObjectPCRUD( ctx, param );
899 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
900 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
901 jsonObject* auth_object = jsonNewObject(auth);
902 jsonObject* user = oilsUtilsQuickReq("open-ils.auth","open-ils.auth.session.retrieve", auth_object);
903 jsonObjectFree(auth_object);
905 if (!user->classname || strcmp(user->classname, "au")) {
907 growing_buffer* msg = buffer_init(128);
910 "%s: permacrud received a bad auth token: %s",
915 char* m = buffer_release(msg);
916 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException", ctx->request, m );
919 jsonObjectFree(user);
927 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
929 dbhandle = writehandle;
931 osrfHash* meta = (osrfHash*) ctx->method->userData;
932 osrfHash* class = osrfHashGet( meta, "class" );
933 char* method_type = strdup( osrfHashGet(meta, "methodtype") );
936 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
938 method_type = strdup("retrieve"); // search and id_list are equivelant to retrieve for this
939 } else if ( *method_type == 'u' || *method_type == 'd' ) {
940 fetch = 1; // MUST go to the db for the object for update and delete
943 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
947 // No permacrud for this method type on this class
949 growing_buffer* msg = buffer_init(128);
952 "%s: %s on class %s has no permacrud IDL entry",
954 osrfHashGet(meta, "methodtype"),
955 osrfHashGet(class, "classname")
958 char* m = buffer_release(msg);
959 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN, "osrfMethodException", ctx->request, m );
966 jsonObject* user = verifyUserPCRUD( ctx );
969 int userid = atoi( oilsFMGetString( user, "id" ) );
970 jsonObjectFree(user);
972 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
973 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
974 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
976 osrfStringArray* context_org_array = osrfNewStringArray(1);
979 char* pkey_value = NULL;
980 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
981 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions required, fetching top of the org tree" );
983 // check for perm at top of org tree
984 jsonObject* _tmp_params = jsonParseString("{\"parent_ou\":null}");
985 jsonObject* _list = doFieldmapperSearch(ctx, osrfHashGet( oilsIDL(), "aou" ),
986 _tmp_params, NULL, &err);
988 jsonObject* _tree_top = jsonObjectGetIndex(_list, 0);
991 jsonObjectFree(_tmp_params);
992 jsonObjectFree(_list);
994 growing_buffer* msg = buffer_init(128);
995 OSRF_BUFFER_ADD( msg, MODULENAME );
996 OSRF_BUFFER_ADD( msg,
997 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
999 char* m = buffer_release(msg);
1000 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1006 osrfStringArrayAdd( context_org_array, oilsFMGetString( _tree_top, "id" ) );
1007 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", osrfStringArrayGetString(context_org_array, 0) );
1009 jsonObjectFree(_tmp_params);
1010 jsonObjectFree(_list);
1013 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, fetching context org ids" );
1014 char* pkey = osrfHashGet(class, "primarykey");
1015 jsonObject *param = NULL;
1017 if (obj->classname) {
1018 pkey_value = oilsFMGetString( obj, pkey );
1019 if (!fetch) param = jsonObjectClone(obj);
1020 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s", pkey_value );
1022 pkey_value = jsonObjectToSimpleString( obj );
1024 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value of %s and retrieving from the database", pkey_value );
1028 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1029 jsonObject* _list = doFieldmapperSearch(
1030 ctx, class, _tmp_params, NULL, &err );
1032 param = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1034 jsonObjectFree(_tmp_params);
1035 jsonObjectFree(_list);
1039 osrfLogDebug( OSRF_LOG_MARK, "Object not found in the database with primary key %s of %s", pkey, pkey_value );
1041 growing_buffer* msg = buffer_init(128);
1044 "%s: no object found with primary key %s of %s",
1050 char* m = buffer_release(msg);
1051 osrfAppSessionStatus(
1053 OSRF_STATUS_INTERNALSERVERERROR,
1054 "osrfMethodException",
1060 if (pkey_value) free(pkey_value);
1065 if (local_context->size > 0) {
1066 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified", local_context->size);
1068 char* lcontext = NULL;
1069 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1070 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1073 "adding class-local field %s (value: %s) to the context org list",
1075 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1080 osrfStringArray* class_list;
1082 if (foreign_context) {
1083 class_list = osrfHashKeys( foreign_context );
1084 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_list->size);
1086 if (class_list->size > 0) {
1089 char* class_name = NULL;
1090 while ( (class_name = osrfStringArrayGetString(class_list, i++)) ) {
1091 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1095 "%d foreign context fields(s) specified for class %s",
1096 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1100 char* foreign_pkey = osrfHashGet(fcontext, "field");
1101 char* foreign_pkey_value = oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1103 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1105 jsonObject* _list = doFieldmapperSearch(
1106 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1108 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1109 jsonObjectFree(_tmp_params);
1110 jsonObjectFree(_list);
1112 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1114 if (_fparam && jump_list) {
1117 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1118 free(foreign_pkey_value);
1120 osrfHash* foreign_link_hash = oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1122 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1123 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1125 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1127 _list = doFieldmapperSearch(
1129 osrfHashGet( oilsIDL(), osrfHashGet( foreign_link_hash, "class" ) ),
1135 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1136 jsonObjectFree(_tmp_params);
1137 jsonObjectFree(_list);
1144 growing_buffer* msg = buffer_init(128);
1147 "%s: no object found with primary key %s of %s",
1153 char* m = buffer_release(msg);
1154 osrfAppSessionStatus(
1156 OSRF_STATUS_INTERNALSERVERERROR,
1157 "osrfMethodException",
1163 osrfStringArrayFree(class_list);
1164 free(foreign_pkey_value);
1165 jsonObjectFree(param);
1170 free(foreign_pkey_value);
1173 char* foreign_field = NULL;
1174 while ( (foreign_field = osrfStringArrayGetString(osrfHashGet(fcontext,"context"), j++)) ) {
1175 osrfStringArrayAdd( context_org_array, oilsFMGetString( _fparam, foreign_field ) );
1178 "adding foreign class %s field %s (value: %s) to the context org list",
1181 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1185 jsonObjectFree(_fparam);
1188 osrfStringArrayFree(class_list);
1192 jsonObjectFree(param);
1195 char* context_org = NULL;
1199 if (permission->size == 0) {
1200 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1205 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1207 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1213 "Checking object permission [%s] for user %d on object %s (class %s) at org %d",
1217 osrfHashGet(class, "classname"),
1221 result = dbi_conn_queryf(
1223 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1226 osrfHashGet(class, "classname"),
1234 "Recieved a result for object permission [%s] for user %d on object %s (class %s) at org %d",
1238 osrfHashGet(class, "classname"),
1242 if (dbi_result_first_row(result)) {
1243 jsonObject* return_val = oilsMakeJSONFromResult( result );
1244 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1248 "Status of object permission [%s] for user %d on object %s (class %s) at org %d is %s",
1252 osrfHashGet(class, "classname"),
1257 if ( *has_perm == 't' ) OK = 1;
1258 jsonObjectFree(return_val);
1261 dbi_result_free(result);
1266 osrfLogDebug( OSRF_LOG_MARK, "Checking non-object permission [%s] for user %d at org %d", perm, userid, atoi(context_org) );
1267 result = dbi_conn_queryf(
1269 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1276 osrfLogDebug( OSRF_LOG_MARK, "Received a result for permission [%s] for user %d at org %d",
1277 perm, userid, atoi(context_org) );
1278 if ( dbi_result_first_row(result) ) {
1279 jsonObject* return_val = oilsMakeJSONFromResult( result );
1280 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1281 osrfLogDebug( OSRF_LOG_MARK, "Status of permission [%s] for user %d at org %d is [%s]",
1282 perm, userid, atoi(context_org), has_perm );
1283 if ( *has_perm == 't' ) OK = 1;
1284 jsonObjectFree(return_val);
1287 dbi_result_free(result);
1295 if (pkey_value) free(pkey_value);
1296 osrfStringArrayFree(context_org_array);
1302 Utility function: create a JSON_HASH with a single key/value pair.
1303 This function is equivalent to:
1305 jsonParseStringFmt( "{\"%s\":\"%s\"}", key, value )
1307 ...but faster because it doesn't create and parse a JSON string.
1309 static jsonObject* single_hash( const char* key, const char* value ) {
1311 if( ! key ) key = "";
1312 if( ! value ) value = "";
1314 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1315 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1321 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1323 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1325 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1326 jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1328 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1329 jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1332 if (!verifyObjectClass(ctx, target)) {
1337 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1339 char* trans_id = NULL;
1340 if( ctx->session && ctx->session->userData )
1341 trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
1344 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1346 osrfAppSessionStatus(
1348 OSRF_STATUS_BADREQUEST,
1349 "osrfMethodException",
1351 "No active transaction -- required for CREATE"
1357 // The following test is harmless but redundant. If a class is
1358 // readonly, we don't register a create method for it.
1359 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1360 osrfAppSessionStatus(
1362 OSRF_STATUS_BADREQUEST,
1363 "osrfMethodException",
1365 "Cannot INSERT readonly class"
1371 // Set the last_xact_id
1372 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1374 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
1375 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1378 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1380 dbhandle = writehandle;
1382 osrfHash* fields = osrfHashGet(meta, "fields");
1383 char* pkey = osrfHashGet(meta, "primarykey");
1384 char* seq = osrfHashGet(meta, "sequence");
1386 growing_buffer* table_buf = buffer_init(128);
1387 growing_buffer* col_buf = buffer_init(128);
1388 growing_buffer* val_buf = buffer_init(128);
1390 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1391 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1392 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1393 buffer_add(val_buf,"VALUES (");
1399 osrfStringArray* field_list = osrfHashKeys( fields );
1400 while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
1402 osrfHash* field = osrfHashGet( fields, field_name );
1404 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1407 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1410 if (field_object && field_object->classname) {
1411 value = oilsFMGetString(
1413 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1416 value = jsonObjectToSimpleString( field_object );
1423 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1424 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1427 buffer_add(col_buf, field_name);
1429 if (!field_object || field_object->type == JSON_NULL) {
1430 buffer_add( val_buf, "DEFAULT" );
1432 } else if ( !strcmp(get_primitive( field ), "number") ) {
1433 const char* numtype = get_datatype( field );
1434 if ( !strcmp( numtype, "INT8") ) {
1435 buffer_fadd( val_buf, "%lld", atoll(value) );
1437 } else if ( !strcmp( numtype, "INT") ) {
1438 buffer_fadd( val_buf, "%d", atoi(value) );
1440 } else if ( !strcmp( numtype, "NUMERIC") ) {
1441 buffer_fadd( val_buf, "%f", atof(value) );
1444 if ( dbi_conn_quote_string(writehandle, &value) ) {
1445 OSRF_BUFFER_ADD( val_buf, value );
1448 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1449 osrfAppSessionStatus(
1451 OSRF_STATUS_INTERNALSERVERERROR,
1452 "osrfMethodException",
1454 "Error quoting string -- please see the error log for more details"
1457 buffer_free(table_buf);
1458 buffer_free(col_buf);
1459 buffer_free(val_buf);
1470 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1471 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1473 char* table_str = buffer_release(table_buf);
1474 char* col_str = buffer_release(col_buf);
1475 char* val_str = buffer_release(val_buf);
1476 growing_buffer* sql = buffer_init(128);
1477 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1482 char* query = buffer_release(sql);
1484 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1487 dbi_result result = dbi_conn_query(writehandle, query);
1489 jsonObject* obj = NULL;
1492 obj = jsonNewObject(NULL);
1495 "%s ERROR inserting %s object using query [%s]",
1497 osrfHashGet(meta, "fieldmapper"),
1500 osrfAppSessionStatus(
1502 OSRF_STATUS_INTERNALSERVERERROR,
1503 "osrfMethodException",
1505 "INSERT error -- please see the error log for more details"
1510 char* id = oilsFMGetString(target, pkey);
1512 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1513 growing_buffer* _id = buffer_init(10);
1514 buffer_fadd(_id, "%lld", new_id);
1515 id = buffer_release(_id);
1518 // Find quietness specification, if present
1519 const char* quiet_str = NULL;
1521 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1523 quiet_str = jsonObjectGetString( quiet_obj );
1526 if( str_is_true( quiet_str ) ) { // if quietness is specified
1527 obj = jsonNewObject(id);
1531 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1532 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1534 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1536 jsonObjectFree( where_clause );
1541 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1544 jsonObjectFree( list );
1557 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1567 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1569 const char* id = jsonObjectGetString(jsonObjectGetIndex(ctx->params, id_pos));
1570 jsonObject* order_hash = jsonObjectGetIndex(ctx->params, order_pos);
1574 "%s retrieving %s object with primary key value of %s",
1576 osrfHashGet(meta, "fieldmapper"),
1580 jsonObject* key_param = jsonNewObjectType( JSON_HASH );
1583 osrfHashGet( meta, "primarykey" ),
1584 jsonObjectClone( jsonObjectGetIndex(ctx->params, id_pos) )
1587 jsonObject* list = doFieldmapperSearch( ctx, meta, key_param, order_hash, err );
1590 jsonObjectFree( key_param );
1594 jsonObject* obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1596 jsonObjectFree( list );
1597 jsonObjectFree( key_param );
1600 if(!verifyObjectPCRUD(ctx, obj)) {
1601 jsonObjectFree(obj);
1604 growing_buffer* msg = buffer_init(128);
1605 OSRF_BUFFER_ADD( msg, MODULENAME );
1606 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1608 char* m = buffer_release(msg);
1609 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1620 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1621 growing_buffer* val_buf = buffer_init(32);
1622 const char* numtype = get_datatype( field );
1624 if ( !strncmp( numtype, "INT", 3 ) ) {
1625 if (value->type == JSON_NUMBER)
1626 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1627 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1629 //const char* val_str = jsonObjectGetString( value );
1630 //buffer_fadd( val_buf, "%ld", atol(val_str) );
1631 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1634 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
1635 if (value->type == JSON_NUMBER)
1636 //buffer_fadd( val_buf, "%f", jsonObjectGetNumber(value) );
1637 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1639 //const char* val_str = jsonObjectGetString( value );
1640 //buffer_fadd( val_buf, "%f", atof(val_str) );
1641 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1645 // Presumably this was really intended ot be a string, so quote it
1646 char* str = jsonObjectToSimpleString( value );
1647 if ( dbi_conn_quote_string(dbhandle, &str) ) {
1648 OSRF_BUFFER_ADD( val_buf, str );
1651 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
1653 buffer_free(val_buf);
1658 return buffer_release(val_buf);
1661 static char* searchINPredicate (const char* class, osrfHash* field,
1662 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1663 growing_buffer* sql_buf = buffer_init(32);
1669 osrfHashGet(field, "name")
1673 buffer_add(sql_buf, "IN (");
1674 } else if (!(strcasecmp(op,"not in"))) {
1675 buffer_add(sql_buf, "NOT IN (");
1677 buffer_add(sql_buf, "IN (");
1680 if (node->type == JSON_HASH) {
1681 // subquery predicate
1682 char* subpred = SELECT(
1684 jsonObjectGetKey( node, "select" ),
1685 jsonObjectGetKey( node, "from" ),
1686 jsonObjectGetKey( node, "where" ),
1687 jsonObjectGetKey( node, "having" ),
1688 jsonObjectGetKey( node, "order_by" ),
1689 jsonObjectGetKey( node, "limit" ),
1690 jsonObjectGetKey( node, "offset" ),
1695 buffer_add(sql_buf, subpred);
1698 buffer_free( sql_buf );
1702 } else if (node->type == JSON_ARRAY) {
1703 // literal value list
1704 int in_item_index = 0;
1705 int in_item_first = 1;
1706 const jsonObject* in_item;
1707 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1712 buffer_add(sql_buf, ", ");
1715 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1716 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1717 MODULENAME, json_type( in_item->type ) );
1718 buffer_free(sql_buf);
1722 // Append the literal value -- quoted if not a number
1723 if ( JSON_NUMBER == in_item->type ) {
1724 char* val = jsonNumberToDBString( field, in_item );
1725 OSRF_BUFFER_ADD( sql_buf, val );
1728 } else if ( !strcmp( get_primitive( field ), "number") ) {
1729 char* val = jsonNumberToDBString( field, in_item );
1730 OSRF_BUFFER_ADD( sql_buf, val );
1734 char* key_string = jsonObjectToSimpleString(in_item);
1735 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1736 OSRF_BUFFER_ADD( sql_buf, key_string );
1739 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1741 buffer_free(sql_buf);
1747 if( in_item_first ) {
1748 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1749 buffer_free( sql_buf );
1753 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1754 MODULENAME, json_type( node->type ) );
1755 buffer_free(sql_buf);
1759 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1761 return buffer_release(sql_buf);
1764 // Receive a JSON_ARRAY representing a function call. The first
1765 // entry in the array is the function name. The rest are parameters.
1766 static char* searchValueTransform( const jsonObject* array ) {
1768 if( array->size < 1 ) {
1769 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
1773 // Get the function name
1774 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
1775 if( func_item->type != JSON_STRING ) {
1776 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
1777 MODULENAME, json_type( func_item->type ) );
1781 growing_buffer* sql_buf = buffer_init(32);
1783 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1784 OSRF_BUFFER_ADD( sql_buf, "( " );
1786 // Get the parameters
1787 int func_item_index = 1; // We already grabbed the zeroth entry
1788 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1790 // Add a separator comma, if we need one
1791 if( func_item_index > 2 )
1792 buffer_add( sql_buf, ", " );
1794 // Add the current parameter
1795 if (func_item->type == JSON_NULL) {
1796 buffer_add( sql_buf, "NULL" );
1798 char* val = jsonObjectToSimpleString(func_item);
1799 if ( dbi_conn_quote_string(dbhandle, &val) ) {
1800 OSRF_BUFFER_ADD( sql_buf, val );
1803 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1804 buffer_free(sql_buf);
1811 buffer_add( sql_buf, " )" );
1813 return buffer_release(sql_buf);
1816 static char* searchFunctionPredicate (const char* class, osrfHash* field,
1817 const jsonObject* node, const char* op) {
1819 if( ! is_good_operator( op ) ) {
1820 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
1824 char* val = searchValueTransform(node);
1828 growing_buffer* sql_buf = buffer_init(32);
1833 osrfHashGet(field, "name"),
1840 return buffer_release(sql_buf);
1843 // class is a class name
1844 // field is a field definition as stored in the IDL
1845 // node comes from the method parameter, and may represent an entry in the SELECT list
1846 static char* searchFieldTransform (const char* class, osrfHash* field, const jsonObject* node) {
1847 growing_buffer* sql_buf = buffer_init(32);
1849 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
1850 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
1852 if(transform_subcolumn) {
1853 if( ! is_identifier( transform_subcolumn ) ) {
1854 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
1855 MODULENAME, transform_subcolumn );
1856 buffer_free( sql_buf );
1859 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
1862 if (field_transform) {
1864 if( ! is_identifier( field_transform ) ) {
1865 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
1866 MODULENAME, field_transform );
1867 buffer_free( sql_buf );
1871 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class, osrfHashGet(field, "name"));
1872 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
1875 if( array->type != JSON_ARRAY ) {
1876 osrfLogError( OSRF_LOG_MARK,
1877 "%s: Expected JSON_ARRAY for function params; found %s",
1878 MODULENAME, json_type( array->type ) );
1879 buffer_free( sql_buf );
1882 int func_item_index = 0;
1883 jsonObject* func_item;
1884 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1886 char* val = jsonObjectToSimpleString(func_item);
1889 buffer_add( sql_buf, ",NULL" );
1890 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
1891 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
1892 OSRF_BUFFER_ADD( sql_buf, val );
1894 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1896 buffer_free(sql_buf);
1903 buffer_add( sql_buf, " )" );
1906 buffer_fadd( sql_buf, "\"%s\".%s", class, osrfHashGet(field, "name"));
1909 if (transform_subcolumn)
1910 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
1912 return buffer_release(sql_buf);
1915 static char* searchFieldTransformPredicate (const char* class, osrfHash* field,
1916 const jsonObject* node, const char* op ) {
1918 if( ! is_good_operator( op ) ) {
1919 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
1923 char* field_transform = searchFieldTransform( class, field, node );
1924 if( ! field_transform )
1927 int extra_parens = 0; // boolean
1929 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
1930 if ( ! value_obj ) {
1931 value = searchWHERE( node, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1933 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
1934 free(field_transform);
1938 } else if ( value_obj->type == JSON_ARRAY ) {
1939 value = searchValueTransform( value_obj );
1941 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
1942 free( field_transform );
1945 } else if ( value_obj->type == JSON_HASH ) {
1946 value = searchWHERE( value_obj, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1948 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
1949 free(field_transform);
1953 } else if ( value_obj->type == JSON_NUMBER ) {
1954 value = jsonNumberToDBString( field, value_obj );
1955 } else if ( value_obj->type == JSON_NULL ) {
1956 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
1957 free(field_transform);
1959 } else if ( value_obj->type == JSON_BOOL ) {
1960 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
1961 free(field_transform);
1964 if ( !strcmp( get_primitive( field ), "number") ) {
1965 value = jsonNumberToDBString( field, value_obj );
1967 value = jsonObjectToSimpleString( value_obj );
1968 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
1969 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
1971 free(field_transform);
1977 const char* left_parens = "";
1978 const char* right_parens = "";
1980 if( extra_parens ) {
1985 growing_buffer* sql_buf = buffer_init(32);
1989 "%s%s %s %s %s %s%s",
2000 free(field_transform);
2002 return buffer_release(sql_buf);
2005 static char* searchSimplePredicate (const char* op, const char* class,
2006 osrfHash* field, const jsonObject* node) {
2008 if( ! is_good_operator( op ) ) {
2009 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2015 // Get the value to which we are comparing the specified column
2016 if (node->type != JSON_NULL) {
2017 if ( node->type == JSON_NUMBER ) {
2018 val = jsonNumberToDBString( field, node );
2019 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2020 val = jsonNumberToDBString( field, node );
2022 val = jsonObjectToSimpleString(node);
2027 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2028 // Value is not numeric; enclose it in quotes
2029 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2030 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2036 // Compare to a null value
2037 val = strdup( "NULL" );
2038 if (strcmp( op, "=" ))
2044 growing_buffer* sql_buf = buffer_init(32);
2045 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class, osrfHashGet(field, "name"), op, val );
2046 char* pred = buffer_release( sql_buf );
2053 static char* searchBETWEENPredicate (const char* class, osrfHash* field, const jsonObject* node) {
2055 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2056 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2058 if( NULL == y_node ) {
2059 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2062 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2063 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2070 if ( !strcmp( get_primitive( field ), "number") ) {
2071 x_string = jsonNumberToDBString(field, x_node);
2072 y_string = jsonNumberToDBString(field, y_node);
2075 x_string = jsonObjectToSimpleString(x_node);
2076 y_string = jsonObjectToSimpleString(y_node);
2077 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2078 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2079 MODULENAME, x_string, y_string);
2086 growing_buffer* sql_buf = buffer_init(32);
2087 buffer_fadd( sql_buf, "%s BETWEEN %s AND %s", osrfHashGet(field, "name"), x_string, y_string );
2091 return buffer_release(sql_buf);
2094 static char* searchPredicate ( const char* class, osrfHash* field,
2095 jsonObject* node, osrfMethodContext* ctx ) {
2098 if (node->type == JSON_ARRAY) { // equality IN search
2099 pred = searchINPredicate( class, field, node, NULL, ctx );
2100 } else if (node->type == JSON_HASH) { // other search
2101 jsonIterator* pred_itr = jsonNewIterator( node );
2102 if( !jsonIteratorHasNext( pred_itr ) ) {
2103 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2104 MODULENAME, osrfHashGet(field, "name") );
2106 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2108 // Verify that there are no additional predicates
2109 if( jsonIteratorHasNext( pred_itr ) ) {
2110 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2111 MODULENAME, osrfHashGet(field, "name") );
2112 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2113 pred = searchBETWEENPredicate( class, field, pred_node );
2114 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2115 pred = searchINPredicate( class, field, pred_node, pred_itr->key, ctx );
2116 else if ( pred_node->type == JSON_ARRAY )
2117 pred = searchFunctionPredicate( class, field, pred_node, pred_itr->key );
2118 else if ( pred_node->type == JSON_HASH )
2119 pred = searchFieldTransformPredicate( class, field, pred_node, pred_itr->key );
2121 pred = searchSimplePredicate( pred_itr->key, class, field, pred_node );
2123 jsonIteratorFree(pred_itr);
2125 } else if (node->type == JSON_NULL) { // IS NULL search
2126 growing_buffer* _p = buffer_init(64);
2129 "\"%s\".%s IS NULL",
2131 osrfHashGet(field, "name")
2133 pred = buffer_release(_p);
2134 } else { // equality search
2135 pred = searchSimplePredicate( "=", class, field, node );
2154 field : call_number,
2170 static char* searchJOIN ( const jsonObject* join_hash, osrfHash* leftmeta ) {
2172 const jsonObject* working_hash;
2173 jsonObject* freeable_hash = NULL;
2175 if (join_hash->type == JSON_STRING) {
2176 // create a wrapper around a copy of the original
2177 const char* _tmp = jsonObjectGetString( join_hash );
2178 freeable_hash = jsonNewObjectType(JSON_HASH);
2179 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2180 working_hash = freeable_hash;
2183 if( join_hash->type != JSON_HASH ) {
2186 "%s: JOIN failed; expected JSON object type not found",
2191 working_hash = join_hash;
2194 growing_buffer* join_buf = buffer_init(128);
2195 const char* leftclass = osrfHashGet(leftmeta, "classname");
2197 jsonObject* snode = NULL;
2198 jsonIterator* search_itr = jsonNewIterator( working_hash );
2200 while ( (snode = jsonIteratorNext( search_itr )) ) {
2201 const char* class = search_itr->key;
2202 osrfHash* idlClass = osrfHashGet( oilsIDL(), class );
2206 "%s: JOIN failed. No class \"%s\" defined in IDL",
2210 jsonIteratorFree( search_itr );
2211 buffer_free( join_buf );
2213 jsonObjectFree( freeable_hash );
2217 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2218 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2220 if (field && !fkey) {
2221 // Look up the corresponding join column in the IDL.
2222 // The link must be defined in the child table,
2223 // and point to the right parent table.
2224 osrfHash* idl_link = (osrfHash*) oilsIDLFindPath( "/%s/links/%s", class, field );
2225 const char* reltype = NULL;
2226 const char* other_class = NULL;
2227 reltype = osrfHashGet( idl_link, "reltype" );
2228 if( reltype && strcmp( reltype, "has_many" ) )
2229 other_class = osrfHashGet( idl_link, "class" );
2230 if( other_class && !strcmp( other_class, leftclass ) )
2231 fkey = osrfHashGet( idl_link, "key" );
2235 "%s: JOIN failed. No link defined from %s.%s to %s",
2241 buffer_free(join_buf);
2243 jsonObjectFree(freeable_hash);
2244 jsonIteratorFree(search_itr);
2248 } else if (!field && fkey) {
2249 // Look up the corresponding join column in the IDL.
2250 // The link must be defined in the child table,
2251 // and point to the right parent table.
2252 osrfHash* idl_link = (osrfHash*) oilsIDLFindPath( "/%s/links/%s", leftclass, fkey );
2253 const char* reltype = NULL;
2254 const char* other_class = NULL;
2255 reltype = osrfHashGet( idl_link, "reltype" );
2256 if( reltype && strcmp( reltype, "has_many" ) )
2257 other_class = osrfHashGet( idl_link, "class" );
2258 if( other_class && !strcmp( other_class, class ) )
2259 field = osrfHashGet( idl_link, "key" );
2263 "%s: JOIN failed. No link defined from %s.%s to %s",
2269 buffer_free(join_buf);
2271 jsonObjectFree(freeable_hash);
2272 jsonIteratorFree(search_itr);
2276 } else if (!field && !fkey) {
2277 osrfHash* _links = oilsIDL_links( leftclass );
2279 // For each link defined for the left class:
2280 // see if the link references the joined class
2281 osrfHashIterator* itr = osrfNewHashIterator( _links );
2282 osrfHash* curr_link = NULL;
2283 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2284 const char* other_class = osrfHashGet( curr_link, "class" );
2285 if( other_class && !strcmp( other_class, class ) ) {
2287 // In the IDL, the parent class doesn't know then names of the child
2288 // columns that are pointing to it, so don't use that end of the link
2289 const char* reltype = osrfHashGet( curr_link, "reltype" );
2290 if( reltype && strcmp( reltype, "has_many" ) ) {
2291 // Found a link between the classes
2292 fkey = osrfHashIteratorKey( itr );
2293 field = osrfHashGet( curr_link, "key" );
2298 osrfHashIteratorFree( itr );
2300 if (!field || !fkey) {
2301 // Do another such search, with the classes reversed
2302 _links = oilsIDL_links( class );
2304 // For each link defined for the joined class:
2305 // see if the link references the left class
2306 osrfHashIterator* itr = osrfNewHashIterator( _links );
2307 osrfHash* curr_link = NULL;
2308 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2309 const char* other_class = osrfHashGet( curr_link, "class" );
2310 if( other_class && !strcmp( other_class, leftclass ) ) {
2312 // In the IDL, the parent class doesn't know then names of the child
2313 // columns that are pointing to it, so don't use that end of the link
2314 const char* reltype = osrfHashGet( curr_link, "reltype" );
2315 if( reltype && strcmp( reltype, "has_many" ) ) {
2316 // Found a link between the classes
2317 field = osrfHashIteratorKey( itr );
2318 fkey = osrfHashGet( curr_link, "key" );
2323 osrfHashIteratorFree( itr );
2326 if (!field || !fkey) {
2329 "%s: JOIN failed. No link defined between %s and %s",
2334 buffer_free(join_buf);
2336 jsonObjectFree(freeable_hash);
2337 jsonIteratorFree(search_itr);
2343 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2345 if ( !strcasecmp(type,"left") ) {
2346 buffer_add(join_buf, " LEFT JOIN");
2347 } else if ( !strcasecmp(type,"right") ) {
2348 buffer_add(join_buf, " RIGHT JOIN");
2349 } else if ( !strcasecmp(type,"full") ) {
2350 buffer_add(join_buf, " FULL JOIN");
2352 buffer_add(join_buf, " INNER JOIN");
2355 buffer_add(join_buf, " INNER JOIN");
2358 char* table = getSourceDefinition(idlClass);
2360 jsonIteratorFree( search_itr );
2361 buffer_free( join_buf );
2363 jsonObjectFree( freeable_hash );
2367 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2368 table, class, class, field, leftclass, fkey);
2371 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2373 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2374 if ( filter_op && !strcasecmp("or",filter_op) ) {
2375 buffer_add( join_buf, " OR " );
2377 buffer_add( join_buf, " AND " );
2380 char* jpred = searchWHERE( filter, idlClass, AND_OP_JOIN, NULL );
2382 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2383 OSRF_BUFFER_ADD( join_buf, jpred );
2388 "%s: JOIN failed. Invalid conditional expression.",
2391 jsonIteratorFree( search_itr );
2392 buffer_free( join_buf );
2394 jsonObjectFree( freeable_hash );
2399 buffer_add(join_buf, " ) ");
2401 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2403 char* jpred = searchJOIN( join_filter, idlClass );
2405 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2406 OSRF_BUFFER_ADD( join_buf, jpred );
2409 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2410 jsonIteratorFree( search_itr );
2411 buffer_free( join_buf );
2413 jsonObjectFree( freeable_hash );
2420 jsonObjectFree(freeable_hash);
2421 jsonIteratorFree(search_itr);
2423 return buffer_release(join_buf);
2428 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2429 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2430 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2432 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2434 search_hash is the JSON expression of the conditions.
2435 meta is the class definition from the IDL, for the relevant table.
2436 opjoin_type indicates whether multiple conditions, if present, should be
2437 connected by AND or OR.
2438 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2439 to pass it to other functions -- and all they do with it is to use the session
2440 and request members to send error messages back to the client.
2444 static char* searchWHERE ( const jsonObject* search_hash, osrfHash* meta, int opjoin_type, osrfMethodContext* ctx ) {
2448 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2456 growing_buffer* sql_buf = buffer_init(128);
2458 jsonObject* node = NULL;
2461 if ( search_hash->type == JSON_ARRAY ) {
2462 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2463 jsonIterator* search_itr = jsonNewIterator( search_hash );
2464 if( !jsonIteratorHasNext( search_itr ) ) {
2467 "%s: Invalid predicate structure: empty JSON array",
2470 jsonIteratorFree( search_itr );
2471 buffer_free( sql_buf );
2475 while ( (node = jsonIteratorNext( search_itr )) ) {
2479 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2480 else buffer_add(sql_buf, " AND ");
2483 char* subpred = searchWHERE( node, meta, opjoin_type, ctx );
2485 buffer_fadd(sql_buf, "( %s )", subpred);
2488 jsonIteratorFree( search_itr );
2489 buffer_free( sql_buf );
2493 jsonIteratorFree(search_itr);
2495 } else if ( search_hash->type == JSON_HASH ) {
2496 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2497 jsonIterator* search_itr = jsonNewIterator( search_hash );
2498 if( !jsonIteratorHasNext( search_itr ) ) {
2501 "%s: Invalid predicate structure: empty JSON object",
2504 jsonIteratorFree( search_itr );
2505 buffer_free( sql_buf );
2509 while ( (node = jsonIteratorNext( search_itr )) ) {
2514 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2515 else buffer_add(sql_buf, " AND ");
2518 if ( '+' == search_itr->key[ 0 ] ) {
2519 if ( node->type == JSON_STRING ) {
2520 // Intended purpose; to allow reference to a Boolean column
2522 // Verify that the class alias is not empty
2523 if( '\0' == search_itr->key[ 1 ] ) {
2526 "%s: Table alias is empty",
2529 jsonIteratorFree( search_itr );
2530 buffer_free( sql_buf );
2534 // Verify that the string looks like an identifier.
2535 const char* subpred = jsonObjectGetString( node );
2536 if( ! is_identifier( subpred ) ) {
2539 "%s: Invalid boolean identifier in WHERE clause: \"%s\"",
2543 jsonIteratorFree( search_itr );
2544 buffer_free( sql_buf );
2548 buffer_fadd(sql_buf, " \"%s\".%s ", search_itr->key + 1, subpred);
2550 char* subpred = searchWHERE( node, osrfHashGet( oilsIDL(), search_itr->key + 1 ), AND_OP_JOIN, ctx );
2552 buffer_fadd(sql_buf, "( %s )", subpred);
2555 jsonIteratorFree( search_itr );
2556 buffer_free( sql_buf );
2560 } else if ( !strcasecmp("-or",search_itr->key) ) {
2561 char* subpred = searchWHERE( node, meta, OR_OP_JOIN, ctx );
2563 buffer_fadd(sql_buf, "( %s )", subpred);
2566 buffer_free( sql_buf );
2569 } else if ( !strcasecmp("-and",search_itr->key) ) {
2570 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2572 buffer_fadd(sql_buf, "( %s )", subpred);
2575 buffer_free( sql_buf );
2578 } else if ( !strcasecmp("-not",search_itr->key) ) {
2579 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2581 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2584 buffer_free( sql_buf );
2587 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2588 char* subpred = SELECT(
2590 jsonObjectGetKey( node, "select" ),
2591 jsonObjectGetKey( node, "from" ),
2592 jsonObjectGetKey( node, "where" ),
2593 jsonObjectGetKey( node, "having" ),
2594 jsonObjectGetKey( node, "order_by" ),
2595 jsonObjectGetKey( node, "limit" ),
2596 jsonObjectGetKey( node, "offset" ),
2601 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2604 buffer_free( sql_buf );
2607 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2608 char* subpred = SELECT(
2610 jsonObjectGetKey( node, "select" ),
2611 jsonObjectGetKey( node, "from" ),
2612 jsonObjectGetKey( node, "where" ),
2613 jsonObjectGetKey( node, "having" ),
2614 jsonObjectGetKey( node, "order_by" ),
2615 jsonObjectGetKey( node, "limit" ),
2616 jsonObjectGetKey( node, "offset" ),
2621 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2624 buffer_free( sql_buf );
2630 char* class = osrfHashGet(meta, "classname");
2631 osrfHash* fields = osrfHashGet(meta, "fields");
2632 osrfHash* field = osrfHashGet( fields, search_itr->key );
2636 char* table = getSourceDefinition(meta);
2638 table = strdup( "(?)" );
2641 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2647 buffer_free(sql_buf);
2649 jsonIteratorFree(search_itr);
2653 char* subpred = searchPredicate( class, field, node, ctx );
2655 buffer_add( sql_buf, subpred );
2658 buffer_free(sql_buf);
2659 jsonIteratorFree(search_itr);
2664 jsonIteratorFree(search_itr);
2667 // ERROR ... only hash and array allowed at this level
2668 char* predicate_string = jsonObjectToJSON( search_hash );
2671 "%s: Invalid predicate structure: %s",
2675 buffer_free(sql_buf);
2676 free(predicate_string);
2680 return buffer_release(sql_buf);
2683 // Return 1 if the class is in the FROM clause, or 0 if not
2684 static int is_joined( jsonObject* from_clause, const char* class ) {
2688 else if( from_clause->type == JSON_STRING ) {
2689 if( strcmp( class, jsonObjectGetString( from_clause ) ) )
2693 } else if( from_clause->type != JSON_HASH ) {
2695 } else { // Look at any subjoins
2696 jsonIterator* class_itr = jsonNewIterator( from_clause );
2697 jsonObject* curr_class;
2698 int rc = 0; // return code
2699 while ( ( curr_class = jsonIteratorNext( class_itr ) ) ) {
2700 if( ! strcmp( class_itr->key, class ) ) {
2704 jsonObject* subjoin = jsonObjectGetKey( curr_class, "join" );
2705 if( subjoin && is_joined( subjoin, class ) ) {
2711 jsonIteratorFree( class_itr );
2718 /* method context */ osrfMethodContext* ctx,
2720 /* SELECT */ jsonObject* selhash,
2721 /* FROM */ jsonObject* join_hash,
2722 /* WHERE */ jsonObject* search_hash,
2723 /* HAVING */ jsonObject* having_hash,
2724 /* ORDER BY */ jsonObject* order_hash,
2725 /* LIMIT */ jsonObject* limit,
2726 /* OFFSET */ jsonObject* offset,
2727 /* flags */ int flags
2729 const char* locale = osrf_message_get_last_locale();
2731 // in case we don't get a select list
2732 jsonObject* defaultselhash = NULL;
2734 // general tmp objects
2735 const jsonObject* tmp_const;
2736 jsonObject* selclass = NULL;
2737 jsonObject* selfield = NULL;
2738 jsonObject* snode = NULL;
2739 jsonObject* onode = NULL;
2741 char* string = NULL;
2742 int from_function = 0;
2747 // the core search class
2748 char* core_class = NULL;
2750 // metadata about the core search class
2751 osrfHash* core_meta = NULL;
2753 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale);
2755 // punt if there's no core class
2756 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2759 "%s: FROM clause is missing or empty",
2763 osrfAppSessionStatus(
2765 OSRF_STATUS_INTERNALSERVERERROR,
2766 "osrfMethodException",
2768 "FROM clause is missing or empty in JSON query"
2773 // get the core class -- the only key of the top level FROM clause, or a string
2774 if (join_hash->type == JSON_HASH) {
2775 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2776 snode = jsonIteratorNext( tmp_itr );
2778 core_class = strdup( tmp_itr->key );
2781 jsonObject* extra = jsonIteratorNext( tmp_itr );
2783 jsonIteratorFree( tmp_itr );
2786 // There shouldn't be more than one entry in join_hash
2790 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2794 osrfAppSessionStatus(
2796 OSRF_STATUS_INTERNALSERVERERROR,
2797 "osrfMethodException",
2799 "Malformed FROM clause in JSON query"
2802 return NULL; // Malformed join_hash; extra entry
2804 } else if (join_hash->type == JSON_ARRAY) {
2806 core_class = jsonObjectToSimpleString( jsonObjectGetIndex(join_hash, 0) );
2809 } else if (join_hash->type == JSON_STRING) {
2810 core_class = jsonObjectToSimpleString( join_hash );
2816 "%s: FROM clause is unexpected JSON type: %s",
2818 json_type( join_hash->type )
2821 osrfAppSessionStatus(
2823 OSRF_STATUS_INTERNALSERVERERROR,
2824 "osrfMethodException",
2826 "Ill-formed FROM clause in JSON query"
2832 if (!from_function) {
2833 // Get the IDL class definition for the core class
2834 core_meta = osrfHashGet( oilsIDL(), core_class );
2835 if( !core_meta ) { // Didn't find it?
2838 "%s: SELECT clause references undefined class: \"%s\"",
2843 osrfAppSessionStatus(
2845 OSRF_STATUS_INTERNALSERVERERROR,
2846 "osrfMethodException",
2848 "SELECT clause references undefined class in JSON query"
2854 // Make sure the class isn't virtual
2855 if( str_is_true( osrfHashGet( core_meta, "virtual" ) ) ) {
2858 "%s: Core class is virtual: \"%s\"",
2863 osrfAppSessionStatus(
2865 OSRF_STATUS_INTERNALSERVERERROR,
2866 "osrfMethodException",
2868 "FROM clause references virtual class in JSON query"
2875 // if the select list is empty, or the core class field list is '*',
2876 // build the default select list ...
2878 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
2879 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2880 } else if( selhash->type != JSON_HASH ) {
2883 "%s: Expected JSON_HASH for SELECT clause; found %s",
2885 json_type( selhash->type )
2889 osrfAppSessionStatus(
2891 OSRF_STATUS_INTERNALSERVERERROR,
2892 "osrfMethodException",
2894 "Malformed SELECT clause in JSON query"
2898 } else if ( (tmp_const = jsonObjectGetKeyConst( selhash, core_class )) && tmp_const->type == JSON_STRING ) {
2899 const char* _x = jsonObjectGetString( tmp_const );
2900 if (!strncmp( "*", _x, 1 )) {
2901 jsonObjectRemoveKey( selhash, core_class );
2902 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2907 growing_buffer* sql_buf = buffer_init(128);
2909 // temp buffers for the SELECT list and GROUP BY clause
2910 growing_buffer* select_buf = buffer_init(128);
2911 growing_buffer* group_buf = buffer_init(128);
2913 int aggregate_found = 0; // boolean
2915 // Build a select list
2916 if(from_function) // From a function we select everything
2917 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
2920 // If we need to build a default list, prepare to do so
2921 jsonObject* _tmp = jsonObjectGetKey( selhash, core_class );
2922 if ( _tmp && !_tmp->size ) {
2924 osrfHash* core_fields = osrfHashGet( core_meta, "fields" );
2926 osrfHashIterator* field_itr = osrfNewHashIterator( core_fields );
2927 osrfHash* field_def;
2928 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2929 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2930 // This field is not virtual, so add it to the list
2931 jsonObjectPush( _tmp, jsonNewObject( osrfHashIteratorKey( field_itr ) ) );
2934 osrfHashIteratorFree( field_itr );
2937 // Now build the actual select list
2941 jsonIterator* selclass_itr = jsonNewIterator( selhash );
2942 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
2944 const char* cname = selclass_itr->key;
2946 // Make sure the target relation is in the FROM clause.
2948 // At this point join_hash is a step down from the join_hash we
2949 // received as a parameter. If the original was a JSON_STRING,
2950 // then json_hash is now NULL. If the original was a JSON_HASH,
2951 // then json_hash is now the first (and only) entry in it,
2952 // denoting the core class. We've already excluded the
2953 // possibility that the original was a JSON_ARRAY, because in
2954 // that case from_function would be non-NULL, and we wouldn't
2957 // If the current class isn't the core class
2958 // and it isn't in the join tree, bail out
2959 if ( strcmp( core_class, cname ) && ! is_joined( join_hash, cname ) ) {
2962 "%s: SELECT clause references class not in FROM clause: \"%s\"",
2967 osrfAppSessionStatus(
2969 OSRF_STATUS_INTERNALSERVERERROR,
2970 "osrfMethodException",
2972 "Selected class not in FROM clause in JSON query"
2974 jsonIteratorFree( selclass_itr );
2975 buffer_free( sql_buf );
2976 buffer_free( select_buf );
2977 buffer_free( group_buf );
2978 if( defaultselhash ) jsonObjectFree( defaultselhash );
2983 // Look up some attributes of the current class, so that we
2984 // don't have to look them up again for each field
2985 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
2986 osrfHash* class_field_set = osrfHashGet( idlClass, "fields" );
2987 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
2988 const char* class_tname = osrfHashGet( idlClass, "tablename" );
2990 // stitch together the column list ...
2991 jsonIterator* select_itr = jsonNewIterator( selclass );
2992 while ( (selfield = jsonIteratorNext( select_itr )) ) { // for each SELECT column
2994 // If we need a separator comma, add one
2998 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3001 // ... if it's a string, just toss it on the pile
3002 if (selfield->type == JSON_STRING) {
3004 // Look up the field in the IDL
3005 const char* col_name = jsonObjectGetString( selfield );
3006 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3008 // No such field in current class
3011 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3017 osrfAppSessionStatus(
3019 OSRF_STATUS_INTERNALSERVERERROR,
3020 "osrfMethodException",
3022 "Selected column not defined in JSON query"
3024 jsonIteratorFree( select_itr );
3025 jsonIteratorFree( selclass_itr );
3026 buffer_free( sql_buf );
3027 buffer_free( select_buf );
3028 buffer_free( group_buf );
3029 if( defaultselhash ) jsonObjectFree( defaultselhash );
3032 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3033 // Virtual field not allowed
3036 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3042 osrfAppSessionStatus(
3044 OSRF_STATUS_INTERNALSERVERERROR,
3045 "osrfMethodException",
3047 "Selected column may not be virtual in JSON query"
3049 jsonIteratorFree( select_itr );
3050 jsonIteratorFree( selclass_itr );
3051 buffer_free( sql_buf );
3052 buffer_free( select_buf );
3053 buffer_free( group_buf );
3054 if( defaultselhash ) jsonObjectFree( defaultselhash );
3061 if (flags & DISABLE_I18N)
3064 i18n = osrfHashGet(field_def, "i18n");
3066 if( str_is_true( i18n ) ) {
3067 buffer_fadd( select_buf,
3068 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3069 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3071 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3074 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3077 // ... but it could be an object, in which case we check for a Field Transform
3078 } else if (selfield->type == JSON_HASH) {
3080 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3082 // Get the field definition from the IDL
3083 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3085 // No such field in current class
3088 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3094 osrfAppSessionStatus(
3096 OSRF_STATUS_INTERNALSERVERERROR,
3097 "osrfMethodException",
3099 "Selected column is not defined in JSON query"
3101 jsonIteratorFree( select_itr );
3102 jsonIteratorFree( selclass_itr );
3103 buffer_free( sql_buf );
3104 buffer_free( select_buf );
3105 buffer_free( group_buf );
3106 if( defaultselhash ) jsonObjectFree( defaultselhash );
3109 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3110 // No such field in current class
3113 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3119 osrfAppSessionStatus(
3121 OSRF_STATUS_INTERNALSERVERERROR,
3122 "osrfMethodException",
3124 "Selected column is virtual in JSON query"
3126 jsonIteratorFree( select_itr );
3127 jsonIteratorFree( selclass_itr );
3128 buffer_free( sql_buf );
3129 buffer_free( select_buf );
3130 buffer_free( group_buf );
3131 if( defaultselhash ) jsonObjectFree( defaultselhash );
3136 // Decide what to use as a column alias
3138 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3139 _alias = jsonObjectGetString( tmp_const );
3140 } else { // Use field name as the alias
3144 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3145 char* transform_str = searchFieldTransform(cname, field_def, selfield);
3146 if( transform_str ) {
3147 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3148 free(transform_str);
3151 osrfAppSessionStatus(
3153 OSRF_STATUS_INTERNALSERVERERROR,
3154 "osrfMethodException",
3156 "Unable to generate transform function in JSON query"
3158 jsonIteratorFree( select_itr );
3159 jsonIteratorFree( selclass_itr );
3160 buffer_free( sql_buf );
3161 buffer_free( select_buf );
3162 buffer_free( group_buf );
3163 if( defaultselhash ) jsonObjectFree( defaultselhash );
3171 if (flags & DISABLE_I18N)
3174 i18n = osrfHashGet(field_def, "i18n");
3176 if( str_is_true( i18n ) ) {
3177 buffer_fadd( select_buf,
3178 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3179 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3181 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3184 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3191 "%s: Selected item is unexpected JSON type: %s",
3193 json_type( selfield->type )
3196 osrfAppSessionStatus(
3198 OSRF_STATUS_INTERNALSERVERERROR,
3199 "osrfMethodException",
3201 "Ill-formed SELECT item in JSON query"
3203 jsonIteratorFree( select_itr );
3204 jsonIteratorFree( selclass_itr );
3205 buffer_free( sql_buf );
3206 buffer_free( select_buf );
3207 buffer_free( group_buf );
3208 if( defaultselhash ) jsonObjectFree( defaultselhash );
3213 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3214 if( obj_is_true( agg_obj ) )
3215 aggregate_found = 1;
3217 // Append a comma (except for the first one)
3218 // and add the column to a GROUP BY clause
3222 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3224 buffer_fadd(group_buf, " %d", sel_pos);
3228 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3230 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3231 if ( ! obj_is_true( aggregate_obj ) ) {
3235 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3238 buffer_fadd(group_buf, " %d", sel_pos);
3241 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3245 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3248 _column = searchFieldTransform(cname, field, selfield);
3249 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3250 OSRF_BUFFER_ADD(group_buf, _column);
3251 _column = searchFieldTransform(cname, field, selfield);
3258 } // end while -- iterating across SELECT columns
3260 jsonIteratorFree(select_itr);
3261 } // end while -- iterating across classes
3263 jsonIteratorFree(selclass_itr);
3267 char* col_list = buffer_release(select_buf);
3269 if (from_function) table = searchValueTransform(join_hash);
3270 else table = getSourceDefinition(core_meta);
3274 osrfAppSessionStatus(
3276 OSRF_STATUS_INTERNALSERVERERROR,
3277 "osrfMethodException",
3279 "Unable to identify table for core class"
3282 buffer_free( sql_buf );
3283 buffer_free( group_buf );
3284 if( defaultselhash ) jsonObjectFree( defaultselhash );
3289 // Put it all together
3290 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3294 char* order_by_list = NULL;
3295 char* having_buf = NULL;
3297 if (!from_function) {
3299 // Now, walk the join tree and add that clause
3301 char* join_clause = searchJOIN( join_hash, core_meta );
3303 buffer_add(sql_buf, join_clause);
3307 osrfAppSessionStatus(
3309 OSRF_STATUS_INTERNALSERVERERROR,
3310 "osrfMethodException",
3312 "Unable to construct JOIN clause(s)"
3314 buffer_free( sql_buf );
3315 buffer_free( group_buf );
3316 if( defaultselhash ) jsonObjectFree( defaultselhash );
3322 // Build a WHERE clause, if there is one
3323 if ( search_hash ) {
3324 buffer_add(sql_buf, " WHERE ");
3326 // and it's on the WHERE clause
3327 char* pred = searchWHERE( search_hash, core_meta, AND_OP_JOIN, ctx );
3330 buffer_add(sql_buf, pred);
3334 osrfAppSessionStatus(
3336 OSRF_STATUS_INTERNALSERVERERROR,
3337 "osrfMethodException",
3339 "Severe query error in WHERE predicate -- see error log for more details"
3343 buffer_free(group_buf);
3344 buffer_free(sql_buf);
3345 if (defaultselhash) jsonObjectFree(defaultselhash);
3350 // Build a HAVING clause, if there is one
3351 if ( having_hash ) {
3353 // and it's on the the WHERE clause
3354 having_buf = searchWHERE( having_hash, core_meta, AND_OP_JOIN, ctx );
3356 if( ! having_buf ) {
3358 osrfAppSessionStatus(
3360 OSRF_STATUS_INTERNALSERVERERROR,
3361 "osrfMethodException",
3363 "Severe query error in HAVING predicate -- see error log for more details"
3367 buffer_free(group_buf);
3368 buffer_free(sql_buf);
3369 if (defaultselhash) jsonObjectFree(defaultselhash);
3374 growing_buffer* order_buf = NULL; // to collect ORDER BY list
3376 // Build an ORDER BY clause, if there is one
3377 if( NULL == order_hash )
3378 ; // No ORDER BY? do nothing
3379 else if( JSON_ARRAY == order_hash->type ) {
3380 // Array of field specifications, each specification being a
3381 // hash to define the class, field, and other details
3383 jsonObject* order_spec;
3384 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
3386 if( JSON_HASH != order_spec->type ) {
3387 osrfLogError(OSRF_LOG_MARK,
3388 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
3389 MODULENAME, json_type( order_spec->type ) );
3391 osrfAppSessionStatus(
3393 OSRF_STATUS_INTERNALSERVERERROR,
3394 "osrfMethodException",
3396 "Malformed ORDER BY clause -- see error log for more details"
3398 buffer_free( order_buf );
3401 buffer_free(group_buf);
3402 buffer_free(sql_buf);
3403 if (defaultselhash) jsonObjectFree(defaultselhash);
3408 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
3410 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
3413 OSRF_BUFFER_ADD(order_buf, ", ");
3415 order_buf = buffer_init(128);
3417 if( !field || !class ) {
3418 osrfLogError(OSRF_LOG_MARK,
3419 "%s: Missing class or field name in field specification of ORDER BY clause",
3422 osrfAppSessionStatus(
3424 OSRF_STATUS_INTERNALSERVERERROR,
3425 "osrfMethodException",
3427 "Malformed ORDER BY clause -- see error log for more details"
3429 buffer_free( order_buf );
3432 buffer_free(group_buf);
3433 buffer_free(sql_buf);
3434 if (defaultselhash) jsonObjectFree(defaultselhash);
3438 if ( ! jsonObjectGetKeyConst( selhash, class )
3439 && strcmp( core_class, class )
3440 && ! is_joined( join_hash, class ) ) {
3441 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
3442 "not in FROM clause", MODULENAME, class );
3444 osrfAppSessionStatus(
3446 OSRF_STATUS_INTERNALSERVERERROR,
3447 "osrfMethodException",
3449 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3453 buffer_free(group_buf);
3454 buffer_free(sql_buf);
3455 if (defaultselhash) jsonObjectFree(defaultselhash);
3459 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s", class, field );
3461 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
3462 MODULENAME, class, field );
3464 osrfAppSessionStatus(
3466 OSRF_STATUS_INTERNALSERVERERROR,
3467 "osrfMethodException",
3469 "Invalid field referenced in ORDER BY clause -- see error log for more details"
3473 buffer_free(group_buf);
3474 buffer_free(sql_buf);
3475 if (defaultselhash) jsonObjectFree(defaultselhash);
3477 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3478 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3479 MODULENAME, field );
3481 osrfAppSessionStatus(
3483 OSRF_STATUS_INTERNALSERVERERROR,
3484 "osrfMethodException",
3486 "Virtual field in ORDER BY clause -- see error log for more details"
3488 buffer_free( order_buf );
3491 buffer_free(group_buf);
3492 buffer_free(sql_buf);
3493 if (defaultselhash) jsonObjectFree(defaultselhash);
3497 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
3498 char* transform_str = searchFieldTransform( class, field_def, order_spec );
3499 if( ! transform_str ) {
3501 osrfAppSessionStatus(
3503 OSRF_STATUS_INTERNALSERVERERROR,
3504 "osrfMethodException",
3506 "Severe query error in ORDER BY clause -- see error log for more details"
3508 buffer_free( order_buf );
3511 buffer_free(group_buf);
3512 buffer_free(sql_buf);
3513 if (defaultselhash) jsonObjectFree(defaultselhash);
3517 OSRF_BUFFER_ADD( order_buf, transform_str );
3518 free( transform_str );
3521 buffer_fadd( order_buf, "\"%s\".%s", class, field );
3523 const char* direction =
3524 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
3526 if( direction[ 0 ] || 'D' == direction[ 0 ] )
3527 OSRF_BUFFER_ADD( order_buf, " DESC" );
3529 OSRF_BUFFER_ADD( order_buf, " ASC" );
3532 } else if( JSON_HASH == order_hash->type ) {
3533 // This hash is keyed on class name. Each class has either
3534 // an array of field names or a hash keyed on field name.
3535 jsonIterator* class_itr = jsonNewIterator( order_hash );
3536 while ( (snode = jsonIteratorNext( class_itr )) ) {
3538 if ( ! jsonObjectGetKeyConst( selhash,class_itr->key )
3539 && strcmp( core_class, class_itr->key )
3540 && ! is_joined( join_hash, class_itr->key ) ) {
3541 osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
3542 MODULENAME, class_itr->key );
3544 osrfAppSessionStatus(
3546 OSRF_STATUS_INTERNALSERVERERROR,
3547 "osrfMethodException",
3549 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3551 jsonIteratorFree( class_itr );
3552 buffer_free( order_buf );
3555 buffer_free(group_buf);
3556 buffer_free(sql_buf);
3557 if (defaultselhash) jsonObjectFree(defaultselhash);
3561 osrfHash* field_list_def = oilsIDLFindPath( "/%s/fields", class_itr->key );
3563 if ( snode->type == JSON_HASH ) {
3565 // Hash is keyed on field names from the current class. For each field
3566 // there is another layer of hash to define the sorting details, if any,
3567 // or a string to indicate direction of sorting.
3568 jsonIterator* order_itr = jsonNewIterator( snode );
3569 while ( (onode = jsonIteratorNext( order_itr )) ) {
3571 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
3573 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3574 MODULENAME, order_itr->key );
3576 osrfAppSessionStatus(
3578 OSRF_STATUS_INTERNALSERVERERROR,
3579 "osrfMethodException",
3581 "Invalid field in ORDER BY clause -- see error log for more details"
3583 jsonIteratorFree( order_itr );
3584 jsonIteratorFree( class_itr );
3585 buffer_free( order_buf );
3588 buffer_free(group_buf);
3589 buffer_free(sql_buf);
3590 if (defaultselhash) jsonObjectFree(defaultselhash);
3592 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3593 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3594 MODULENAME, order_itr->key );
3596 osrfAppSessionStatus(
3598 OSRF_STATUS_INTERNALSERVERERROR,
3599 "osrfMethodException",
3601 "Virtual field in ORDER BY clause -- see error log for more details"
3603 jsonIteratorFree( order_itr );
3604 jsonIteratorFree( class_itr );
3605 buffer_free( order_buf );
3608 buffer_free(group_buf);
3609 buffer_free(sql_buf);
3610 if (defaultselhash) jsonObjectFree(defaultselhash);
3614 const char* direction = NULL;
3615 if ( onode->type == JSON_HASH ) {
3616 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3617 string = searchFieldTransform(
3619 osrfHashGet( field_list_def, order_itr->key ),
3623 if( ctx ) osrfAppSessionStatus(
3625 OSRF_STATUS_INTERNALSERVERERROR,
3626 "osrfMethodException",
3628 "Severe query error in ORDER BY clause -- see error log for more details"
3630 jsonIteratorFree( order_itr );
3631 jsonIteratorFree( class_itr );
3634 buffer_free(group_buf);
3635 buffer_free(order_buf);
3636 buffer_free(sql_buf);
3637 if (defaultselhash) jsonObjectFree(defaultselhash);
3641 growing_buffer* field_buf = buffer_init(16);
3642 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3643 string = buffer_release(field_buf);
3646 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3647 const char* dir = jsonObjectGetString(tmp_const);
3648 if (!strncasecmp(dir, "d", 1)) {
3649 direction = " DESC";
3655 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
3656 osrfLogError( OSRF_LOG_MARK,
3657 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
3658 MODULENAME, json_type( onode->type ) );
3660 osrfAppSessionStatus(
3662 OSRF_STATUS_INTERNALSERVERERROR,
3663 "osrfMethodException",
3665 "Malformed ORDER BY clause -- see error log for more details"
3667 jsonIteratorFree( order_itr );
3668 jsonIteratorFree( class_itr );
3671 buffer_free(group_buf);
3672 buffer_free(order_buf);
3673 buffer_free(sql_buf);
3674 if (defaultselhash) jsonObjectFree(defaultselhash);
3678 string = strdup(order_itr->key);
3679 const char* dir = jsonObjectGetString(onode);
3680 if (!strncasecmp(dir, "d", 1)) {
3681 direction = " DESC";
3688 OSRF_BUFFER_ADD(order_buf, ", ");
3690 order_buf = buffer_init(128);
3692 OSRF_BUFFER_ADD(order_buf, string);
3696 OSRF_BUFFER_ADD(order_buf, direction);
3700 jsonIteratorFree(order_itr);
3702 } else if ( snode->type == JSON_ARRAY ) {
3704 // Array is a list of fields from the current class
3705 unsigned long order_idx = 0;
3706 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
3708 const char* _f = jsonObjectGetString( onode );
3710 osrfHash* field_def = osrfHashGet( field_list_def, _f );
3712 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3715 osrfAppSessionStatus(
3717 OSRF_STATUS_INTERNALSERVERERROR,
3718 "osrfMethodException",
3720 "Invalid field in ORDER BY clause -- see error log for more details"
3722 jsonIteratorFree( class_itr );
3723 buffer_free( order_buf );
3726 buffer_free(group_buf);
3727 buffer_free(sql_buf);
3728 if (defaultselhash) jsonObjectFree(defaultselhash);
3730 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3731 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3734 osrfAppSessionStatus(
3736 OSRF_STATUS_INTERNALSERVERERROR,
3737 "osrfMethodException",
3739 "Virtual field in ORDER BY clause -- see error log for more details"
3741 jsonIteratorFree( class_itr );
3742 buffer_free( order_buf );
3745 buffer_free(group_buf);
3746 buffer_free(sql_buf);
3747 if (defaultselhash) jsonObjectFree(defaultselhash);
3752 OSRF_BUFFER_ADD(order_buf, ", ");
3754 order_buf = buffer_init(128);
3756 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
3760 // IT'S THE OOOOOOOOOOOLD STYLE!
3762 osrfLogError(OSRF_LOG_MARK,
3763 "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3765 osrfAppSessionStatus(
3767 OSRF_STATUS_INTERNALSERVERERROR,
3768 "osrfMethodException",
3770 "Severe query error -- see error log for more details"
3776 buffer_free(group_buf);
3777 buffer_free(order_buf);
3778 buffer_free(sql_buf);
3779 if (defaultselhash) jsonObjectFree(defaultselhash);
3780 jsonIteratorFree(class_itr);
3784 jsonIteratorFree( class_itr );
3786 osrfLogError(OSRF_LOG_MARK,
3787 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
3788 MODULENAME, json_type( order_hash->type ) );
3790 osrfAppSessionStatus(
3792 OSRF_STATUS_INTERNALSERVERERROR,
3793 "osrfMethodException",
3795 "Malformed ORDER BY clause -- see error log for more details"
3797 buffer_free( order_buf );
3800 buffer_free(group_buf);
3801 buffer_free(sql_buf);
3802 if (defaultselhash) jsonObjectFree(defaultselhash);
3807 order_by_list = buffer_release( order_buf );
3811 string = buffer_release(group_buf);
3813 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
3814 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3815 OSRF_BUFFER_ADD( sql_buf, string );
3820 if( having_buf && *having_buf ) {
3821 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3822 OSRF_BUFFER_ADD( sql_buf, having_buf );
3826 if( order_by_list ) {
3828 if ( *order_by_list ) {
3829 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3830 OSRF_BUFFER_ADD( sql_buf, order_by_list );
3833 free( order_by_list );
3837 const char* str = jsonObjectGetString(limit);
3838 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
3842 const char* str = jsonObjectGetString(offset);
3843 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
3846 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3849 if (defaultselhash) jsonObjectFree(defaultselhash);
3851 return buffer_release(sql_buf);
3855 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
3857 const char* locale = osrf_message_get_last_locale();
3859 osrfHash* fields = osrfHashGet(meta, "fields");
3860 char* core_class = osrfHashGet(meta, "classname");
3862 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
3864 jsonObject* node = NULL;
3865 jsonObject* snode = NULL;
3866 jsonObject* onode = NULL;
3867 const jsonObject* _tmp = NULL;
3868 jsonObject* selhash = NULL;
3869 jsonObject* defaultselhash = NULL;
3871 growing_buffer* sql_buf = buffer_init(128);
3872 growing_buffer* select_buf = buffer_init(128);
3874 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
3875 defaultselhash = jsonNewObjectType(JSON_HASH);
3876 selhash = defaultselhash;
3879 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
3880 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
3881 jsonObject* flist = jsonObjectGetKey( selhash, core_class );
3886 osrfStringArray* keys = osrfHashKeys( fields );
3887 while ( (field = osrfStringArrayGetString(keys, i++)) ) {
3888 if( ! str_is_true( osrfHashGet( osrfHashGet( fields, field ), "virtual" ) ) )
3889 jsonObjectPush( flist, jsonNewObject( field ) );
3891 osrfStringArrayFree(keys);
3895 jsonIterator* class_itr = jsonNewIterator( selhash );
3896 while ( (snode = jsonIteratorNext( class_itr )) ) {
3898 char* cname = class_itr->key;
3899 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
3900 if (!idlClass) continue;
3902 if (strcmp(core_class,class_itr->key)) {
3903 if (!join_hash) continue;
3905 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
3907 jsonObjectFree(found);
3911 jsonObjectFree(found);
3914 jsonIterator* select_itr = jsonNewIterator( snode );
3915 while ( (node = jsonIteratorNext( select_itr )) ) {
3916 const char* item_str = jsonObjectGetString( node );
3917 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
3918 char* fname = osrfHashGet(field, "name");
3920 if (!field) continue;
3925 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
3930 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
3931 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
3934 i18n = osrfHashGet(field, "i18n");
3936 if( str_is_true( i18n ) ) {
3937 char* pkey = osrfHashGet(idlClass, "primarykey");
3938 char* tname = osrfHashGet(idlClass, "tablename");
3940 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);
3942 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3945 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3949 jsonIteratorFree(select_itr);
3952 jsonIteratorFree(class_itr);
3954 char* col_list = buffer_release(select_buf);
3955 char* table = getSourceDefinition(meta);
3957 table = strdup( "(null)" );
3959 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
3964 char* join_clause = searchJOIN( join_hash, meta );
3965 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
3966 OSRF_BUFFER_ADD(sql_buf, join_clause);
3970 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
3971 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
3973 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
3975 char* pred = searchWHERE( search_hash, meta, AND_OP_JOIN, ctx );
3977 osrfAppSessionStatus(
3979 OSRF_STATUS_INTERNALSERVERERROR,
3980 "osrfMethodException",
3982 "Severe query error -- see error log for more details"
3984 buffer_free(sql_buf);
3985 if(defaultselhash) jsonObjectFree(defaultselhash);
3988 buffer_add(sql_buf, pred);
3993 char* string = NULL;
3994 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
3996 growing_buffer* order_buf = buffer_init(128);
3999 jsonIterator* class_itr = jsonNewIterator( _tmp );
4000 while ( (snode = jsonIteratorNext( class_itr )) ) {
4002 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4005 if ( snode->type == JSON_HASH ) {
4007 jsonIterator* order_itr = jsonNewIterator( snode );
4008 while ( (onode = jsonIteratorNext( order_itr )) ) {
4010 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4011 class_itr->key, order_itr->key );
4015 char* direction = NULL;
4016 if ( onode->type == JSON_HASH ) {
4017 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4018 string = searchFieldTransform( class_itr->key, field_def, onode );
4020 osrfAppSessionStatus(
4022 OSRF_STATUS_INTERNALSERVERERROR,
4023 "osrfMethodException",
4025 "Severe query error in ORDER BY clause -- see error log for more details"
4027 jsonIteratorFree( order_itr );
4028 jsonIteratorFree( class_itr );
4029 buffer_free( order_buf );
4030 buffer_free( sql_buf );
4031 if( defaultselhash ) jsonObjectFree( defaultselhash );
4035 growing_buffer* field_buf = buffer_init(16);
4036 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4037 string = buffer_release(field_buf);
4040 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4041 const char* dir = jsonObjectGetString(_tmp);
4042 if (!strncasecmp(dir, "d", 1)) {
4043 direction = " DESC";
4050 string = strdup(order_itr->key);
4051 const char* dir = jsonObjectGetString(onode);
4052 if (!strncasecmp(dir, "d", 1)) {
4053 direction = " DESC";
4062 buffer_add(order_buf, ", ");
4065 buffer_add(order_buf, string);
4069 buffer_add(order_buf, direction);
4074 jsonIteratorFree(order_itr);
4077 const char* str = jsonObjectGetString(snode);
4078 buffer_add(order_buf, str);
4084 jsonIteratorFree(class_itr);
4086 string = buffer_release(order_buf);
4089 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4090 OSRF_BUFFER_ADD( sql_buf, string );
4096 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4097 const char* str = jsonObjectGetString(_tmp);
4105 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4107 const char* str = jsonObjectGetString(_tmp);
4116 if (defaultselhash) jsonObjectFree(defaultselhash);
4118 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4119 return buffer_release(sql_buf);
4122 int doJSONSearch ( osrfMethodContext* ctx ) {
4123 if(osrfMethodVerifyContext( ctx )) {
4124 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4128 osrfLogDebug(OSRF_LOG_MARK, "Recieved query request");
4133 dbhandle = writehandle;
4135 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4139 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4140 flags |= SELECT_DISTINCT;
4142 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4143 flags |= DISABLE_I18N;
4145 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4148 jsonObjectGetKey( hash, "select" ),
4149 jsonObjectGetKey( hash, "from" ),
4150 jsonObjectGetKey( hash, "where" ),
4151 jsonObjectGetKey( hash, "having" ),
4152 jsonObjectGetKey( hash, "order_by" ),
4153 jsonObjectGetKey( hash, "limit" ),
4154 jsonObjectGetKey( hash, "offset" ),
4163 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4164 dbi_result result = dbi_conn_query(dbhandle, sql);
4167 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4169 if (dbi_result_first_row(result)) {
4170 /* JSONify the result */
4171 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4174 jsonObject* return_val = oilsMakeJSONFromResult( result );
4175 osrfAppRespond( ctx, return_val );
4176 jsonObjectFree( return_val );
4177 } while (dbi_result_next_row(result));
4180 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4183 osrfAppRespondComplete( ctx, NULL );
4185 /* clean up the query */
4186 dbi_result_free(result);
4190 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4191 osrfAppSessionStatus(
4193 OSRF_STATUS_INTERNALSERVERERROR,
4194 "osrfMethodException",
4196 "Severe query error -- see error log for more details"
4204 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4205 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4208 dbhandle = writehandle;
4210 osrfHash* links = osrfHashGet(meta, "links");
4211 osrfHash* fields = osrfHashGet(meta, "fields");
4212 char* core_class = osrfHashGet(meta, "classname");
4213 char* pkey = osrfHashGet(meta, "primarykey");
4215 const jsonObject* _tmp;
4218 char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4220 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4225 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4227 dbi_result result = dbi_conn_query(dbhandle, sql);
4228 if( NULL == result ) {
4229 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4230 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4231 osrfAppSessionStatus(
4233 OSRF_STATUS_INTERNALSERVERERROR,
4234 "osrfMethodException",
4236 "Severe query error -- see error log for more details"
4243 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4246 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
4247 osrfHash* dedup = osrfNewHash();
4249 if (dbi_result_first_row(result)) {
4250 /* JSONify the result */
4251 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4253 obj = oilsMakeFieldmapperFromResult( result, meta );
4254 char* pkey_val = oilsFMGetString( obj, pkey );
4255 if ( osrfHashGet( dedup, pkey_val ) ) {
4256 jsonObjectFree(obj);
4259 osrfHashSet( dedup, pkey_val, pkey_val );
4260 jsonObjectPush(res_list, obj);
4262 } while (dbi_result_next_row(result));
4264 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
4268 osrfHashFree(dedup);
4269 /* clean up the query */
4270 dbi_result_free(result);
4273 if (res_list->size && query_hash) {
4274 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
4276 int x = (int)jsonObjectGetNumber(_tmp);
4277 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
4279 const jsonObject* temp_blob;
4280 if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
4282 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
4283 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
4285 osrfStringArray* link_fields = NULL;
4288 if (flesh_fields->size == 1) {
4289 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
4290 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
4295 link_fields = osrfNewStringArray(1);
4296 jsonIterator* _i = jsonNewIterator( flesh_fields );
4297 while ((_f = jsonIteratorNext( _i ))) {
4298 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4300 jsonIteratorFree(_i);
4305 unsigned long res_idx = 0;
4306 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
4311 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4313 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4315 osrfHash* kid_link = osrfHashGet(links, link_field);
4316 if (!kid_link) continue;
4318 osrfHash* field = osrfHashGet(fields, link_field);
4319 if (!field) continue;
4321 osrfHash* value_field = field;
4323 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4324 if (!kid_idl) continue;
4326 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4327 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4330 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4331 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4334 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4336 if (link_map->size > 0) {
4337 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4340 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4345 osrfHashGet(kid_link, "class"),
4352 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4353 osrfHashGet(kid_link, "field"),
4354 osrfHashGet(kid_link, "class"),
4355 osrfHashGet(kid_link, "key"),
4356 osrfHashGet(kid_link, "reltype")
4359 const char* search_key = jsonObjectGetString(
4362 atoi( osrfHashGet(value_field, "array_position") )
4367 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4371 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
4373 // construct WHERE clause
4374 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
4377 osrfHashGet(kid_link, "key"),
4378 jsonNewObject( search_key )
4381 // construct the rest of the query
4382 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
4383 jsonObjectSetKey( rest_of_query, "flesh",
4384 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4388 jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
4390 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
4391 jsonObjectSetKey( rest_of_query, "order_by",
4392 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
4396 if (jsonObjectGetKeyConst(query_hash, "select")) {
4397 jsonObjectSetKey( rest_of_query, "select",
4398 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
4402 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
4403 where_clause, rest_of_query, err);
4405 jsonObjectFree( where_clause );
4406 jsonObjectFree( rest_of_query );
4409 osrfStringArrayFree(link_fields);
4410 jsonObjectFree(res_list);
4411 jsonObjectFree(flesh_blob);
4415 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4417 jsonObject* X = NULL;
4418 if ( link_map->size > 0 && kids->size > 0 ) {
4420 kids = jsonNewObjectType(JSON_ARRAY);
4422 jsonObject* _k_node;
4423 unsigned long res_idx = 0;
4424 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
4430 (unsigned long)atoi(
4436 osrfHashGet(kid_link, "class")
4440 osrfStringArrayGetString( link_map, 0 )
4448 } // end while loop traversing X
4451 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4452 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4455 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4456 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4460 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4461 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4464 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4465 jsonObjectClone( kids )
4470 jsonObjectFree(kids);
4474 jsonObjectFree( kids );
4476 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4477 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4480 } // end while loop traversing res_list
4481 jsonObjectFree( flesh_blob );
4482 osrfStringArrayFree(link_fields);
4491 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
4493 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4495 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
4497 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
4500 if (!verifyObjectClass(ctx, target)) {
4505 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4506 osrfAppSessionStatus(
4508 OSRF_STATUS_BADREQUEST,
4509 "osrfMethodException",
4511 "No active transaction -- required for UPDATE"
4517 // The following test is harmless but redundant. If a class is
4518 // readonly, we don't register an update method for it.
4519 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4520 osrfAppSessionStatus(
4522 OSRF_STATUS_BADREQUEST,
4523 "osrfMethodException",
4525 "Cannot UPDATE readonly class"
4531 dbhandle = writehandle;
4533 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4535 // Set the last_xact_id
4536 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4538 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
4539 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4542 char* pkey = osrfHashGet(meta, "primarykey");
4543 osrfHash* fields = osrfHashGet(meta, "fields");
4545 char* id = oilsFMGetString( target, pkey );
4549 "%s updating %s object with %s = %s",
4551 osrfHashGet(meta, "fieldmapper"),
4556 growing_buffer* sql = buffer_init(128);
4557 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4562 osrfStringArray* field_list = osrfHashKeys( fields );
4563 while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
4565 osrfHash* field = osrfHashGet( fields, field_name );
4567 if(!( strcmp( field_name, pkey ) )) continue;
4568 if( str_is_true( osrfHashGet(osrfHashGet(fields,field_name), "virtual") ) )
4571 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4573 int value_is_numeric = 0; // boolean
4575 if (field_object && field_object->classname) {
4576 value = oilsFMGetString(
4578 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4581 value = jsonObjectToSimpleString( field_object );
4582 if( field_object && JSON_NUMBER == field_object->type )
4583 value_is_numeric = 1;
4586 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s", osrfHashGet(meta, "fieldmapper"), field_name, value);
4588 if (!field_object || field_object->type == JSON_NULL) {
4589 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) ) && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
4590 if (first) first = 0;
4591 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4592 buffer_fadd( sql, " %s = NULL", field_name );
4595 } else if ( value_is_numeric || !strcmp( get_primitive( field ), "number") ) {
4596 if (first) first = 0;
4597 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4599 const char* numtype = get_datatype( field );
4600 if ( !strncmp( numtype, "INT", 3 ) ) {
4601 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
4602 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
4603 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
4605 // Must really be intended as a string, so quote it
4606 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4607 buffer_fadd( sql, " %s = %s", field_name, value );
4609 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4610 osrfAppSessionStatus(
4612 OSRF_STATUS_INTERNALSERVERERROR,
4613 "osrfMethodException",
4615 "Error quoting string -- please see the error log for more details"
4625 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
4628 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4629 if (first) first = 0;
4630 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4631 buffer_fadd( sql, " %s = %s", field_name, value );
4634 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4635 osrfAppSessionStatus(
4637 OSRF_STATUS_INTERNALSERVERERROR,
4638 "osrfMethodException",
4640 "Error quoting string -- please see the error log for more details"
4654 jsonObject* obj = jsonNewObject(id);
4656 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4657 dbi_conn_quote_string(dbhandle, &id);
4659 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
4661 char* query = buffer_release(sql);
4662 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
4664 dbi_result result = dbi_conn_query(dbhandle, query);
4668 jsonObjectFree(obj);
4669 obj = jsonNewObject(NULL);
4672 "%s ERROR updating %s object with %s = %s",
4674 osrfHashGet(meta, "fieldmapper"),
4685 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4687 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4689 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4690 osrfAppSessionStatus(
4692 OSRF_STATUS_BADREQUEST,
4693 "osrfMethodException",
4695 "No active transaction -- required for DELETE"
4701 // The following test is harmless but redundant. If a class is
4702 // readonly, we don't register a delete method for it.
4703 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4704 osrfAppSessionStatus(
4706 OSRF_STATUS_BADREQUEST,
4707 "osrfMethodException",
4709 "Cannot DELETE readonly class"
4715 dbhandle = writehandle;
4719 char* pkey = osrfHashGet(meta, "primarykey");
4727 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4728 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4733 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4736 if (!verifyObjectPCRUD( ctx, NULL )) {
4741 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4746 "%s deleting %s object with %s = %s",
4748 osrfHashGet(meta, "fieldmapper"),
4753 obj = jsonNewObject(id);
4755 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4756 dbi_conn_quote_string(writehandle, &id);
4758 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4761 jsonObjectFree(obj);
4762 obj = jsonNewObject(NULL);
4765 "%s ERROR deleting %s object with %s = %s",
4767 osrfHashGet(meta, "fieldmapper"),
4780 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4781 if(!(result && meta)) return jsonNULL;
4783 jsonObject* object = jsonNewObject(NULL);
4784 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4786 osrfHash* fields = osrfHashGet(meta, "fields");
4788 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4792 char dt_string[256];
4796 int columnIndex = 1;
4798 unsigned short type;
4799 const char* columnName;
4801 /* cycle through the column list */
4802 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
4804 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4806 fmIndex = -1; // reset the position
4808 /* determine the field type and storage attributes */
4809 type = dbi_result_get_field_type_idx(result, columnIndex);
4810 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
4812 /* fetch the fieldmapper index */
4813 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4815 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4818 const char* pos = (char*)osrfHashGet(_f, "array_position");
4819 if ( !pos ) continue;
4821 fmIndex = atoi( pos );
4822 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
4827 if (dbi_result_field_is_null_idx(result, columnIndex)) {
4828 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
4833 case DBI_TYPE_INTEGER :
4835 if( attr & DBI_INTEGER_SIZE8 )
4836 jsonObjectSetIndex( object, fmIndex,
4837 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
4839 jsonObjectSetIndex( object, fmIndex,
4840 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
4844 case DBI_TYPE_DECIMAL :
4845 jsonObjectSetIndex( object, fmIndex,
4846 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
4849 case DBI_TYPE_STRING :
4855 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
4860 case DBI_TYPE_DATETIME :
4862 memset(dt_string, '\0', sizeof(dt_string));
4863 memset(&gmdt, '\0', sizeof(gmdt));
4865 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
4868 if (!(attr & DBI_DATETIME_DATE)) {
4869 gmtime_r( &_tmp_dt, &gmdt );
4870 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4871 } else if (!(attr & DBI_DATETIME_TIME)) {
4872 localtime_r( &_tmp_dt, &gmdt );
4873 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4875 localtime_r( &_tmp_dt, &gmdt );
4876 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4879 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
4883 case DBI_TYPE_BINARY :
4884 osrfLogError( OSRF_LOG_MARK,
4885 "Can't do binary at column %s : index %d", columnName, columnIndex);
4894 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
4895 if(!result) return jsonNULL;
4897 jsonObject* object = jsonNewObject(NULL);
4900 char dt_string[256];
4904 int columnIndex = 1;
4906 unsigned short type;
4907 const char* columnName;
4909 /* cycle through the column list */
4910 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
4912 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4914 fmIndex = -1; // reset the position
4916 /* determine the field type and storage attributes */
4917 type = dbi_result_get_field_type_idx(result, columnIndex);
4918 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
4920 if (dbi_result_field_is_null_idx(result, columnIndex)) {
4921 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
4926 case DBI_TYPE_INTEGER :
4928 if( attr & DBI_INTEGER_SIZE8 )
4929 jsonObjectSetKey( object, columnName,
4930 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)) );
4932 jsonObjectSetKey( object, columnName,
4933 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
4936 case DBI_TYPE_DECIMAL :
4937 jsonObjectSetKey( object, columnName,
4938 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
4941 case DBI_TYPE_STRING :
4942 jsonObjectSetKey( object, columnName,
4943 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
4946 case DBI_TYPE_DATETIME :
4948 memset(dt_string, '\0', sizeof(dt_string));
4949 memset(&gmdt, '\0', sizeof(gmdt));
4951 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
4954 if (!(attr & DBI_DATETIME_DATE)) {
4955 gmtime_r( &_tmp_dt, &gmdt );
4956 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4957 } else if (!(attr & DBI_DATETIME_TIME)) {
4958 localtime_r( &_tmp_dt, &gmdt );
4959 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4961 localtime_r( &_tmp_dt, &gmdt );
4962 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4965 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
4968 case DBI_TYPE_BINARY :
4969 osrfLogError( OSRF_LOG_MARK,
4970 "Can't do binary at column %s : index %d", columnName, columnIndex );
4974 } // end while loop traversing result
4979 // Interpret a string as true or false
4980 static int str_is_true( const char* str ) {
4981 if( NULL == str || strcasecmp( str, "true" ) )
4987 // Interpret a jsonObject as true or false
4988 static int obj_is_true( const jsonObject* obj ) {
4991 else switch( obj->type )
4999 if( strcasecmp( obj->value.s, "true" ) )
5003 case JSON_NUMBER : // Support 1/0 for perl's sake
5004 if( jsonObjectGetNumber( obj ) == 1.0 )
5013 // Translate a numeric code into a text string identifying a type of
5014 // jsonObject. To be used for building error messages.
5015 static const char* json_type( int code ) {
5021 return "JSON_ARRAY";
5023 return "JSON_STRING";
5025 return "JSON_NUMBER";
5031 return "(unrecognized)";
5035 // Extract the "primitive" attribute from an IDL field definition.
5036 // If we haven't initialized the app, then we must be running in
5037 // some kind of testbed. In that case, default to "string".
5038 static const char* get_primitive( osrfHash* field ) {
5039 const char* s = osrfHashGet( field, "primitive" );
5041 if( child_initialized )
5044 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5046 osrfHashGet( field, "name" )
5054 // Extract the "datatype" attribute from an IDL field definition.
5055 // If we haven't initialized the app, then we must be running in
5056 // some kind of testbed. In that case, default to to NUMERIC,
5057 // since we look at the datatype only for numbers.
5058 static const char* get_datatype( osrfHash* field ) {
5059 const char* s = osrfHashGet( field, "datatype" );
5061 if( child_initialized )
5064 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5066 osrfHashGet( field, "name" )
5075 If the input string is potentially a valid SQL identifier, return 1.
5078 Purpose: to prevent certain kinds of SQL injection. To that end we
5079 don't necessarily need to follow all the rules exactly, such as requiring
5080 that the first character not be a digit.
5082 We allow leading and trailing white space. In between, we do not allow
5083 punctuation (except for underscores and dollar signs), control
5084 characters, or embedded white space.
5086 More pedantically we should allow quoted identifiers containing arbitrary
5087 characters, but for the foreseeable future such quoted identifiers are not
5088 likely to be an issue.
5090 static int is_identifier( const char* s) {
5094 // Skip leading white space
5095 while( isspace( (unsigned char) *s ) )
5099 return 0; // Nothing but white space? Not okay.
5101 // Check each character until we reach white space or
5102 // end-of-string. Letters, digits, underscores, and
5103 // dollar signs are okay. With the exception of periods
5104 // (as in schema.identifier), control characters and other
5105 // punctuation characters are not okay. Anything else
5106 // is okay -- it could for example be part of a multibyte
5107 // UTF8 character such as a letter with diacritical marks,
5108 // and those are allowed.
5110 if( isalnum( (unsigned char) *s )
5114 ; // Fine; keep going
5115 else if( ispunct( (unsigned char) *s )
5116 || iscntrl( (unsigned char) *s ) )
5119 } while( *s && ! isspace( (unsigned char) *s ) );
5121 // If we found any white space in the above loop,
5122 // the rest had better be all white space.
5124 while( isspace( (unsigned char) *s ) )
5128 return 0; // White space was embedded within non-white space
5134 Determine whether to accept a character string as a comparison operator.
5135 Return 1 if it's good, or 0 if it's bad.
5137 We don't validate it for real. We just make sure that it doesn't contain
5138 any semicolons or white space (with a special exception for the
5139 "SIMILAR TO" operator). The idea is to block certain kinds of SQL
5140 injection. If it has no semicolons or white space but it's still not a
5141 valid operator, then the database will complain.
5143 Another approach would be to compare the string against a short list of
5144 approved operators. We don't do that because we want to allow custom
5145 operators like ">100*", which would be difficult or impossible to
5146 express otherwise in a JSON query.
5148 static int is_good_operator( const char* op ) {
5149 if( !op ) return 0; // Sanity check
5153 if( isspace( (unsigned char) *s ) ) {
5154 // Special exception for SIMILAR TO. Someday we might make
5155 // exceptions for IS DISTINCT FROM and IS NOT DISTINCT FROM.
5156 if( !strcasecmp( op, "similar to" ) )
5161 else if( ';' == *s )