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* );
89 static int child_initialized = 0; /* boolean */
91 static dbi_conn writehandle; /* our MASTER db connection */
92 static dbi_conn dbhandle; /* our CURRENT db connection */
93 //static osrfHash * readHandles;
94 static jsonObject* jsonNULL = NULL; //
95 static int max_flesh_depth = 100;
97 /* called when this process is about to exit */
98 void osrfAppChildExit() {
99 osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
102 if (writehandle == dbhandle) same = 1;
104 dbi_conn_query(writehandle, "ROLLBACK;");
105 dbi_conn_close(writehandle);
108 if (dbhandle && !same)
109 dbi_conn_close(dbhandle);
111 // XXX add cleanup of readHandles whenever that gets used
116 int osrfAppInitialize() {
118 osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
119 osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
121 if (!oilsIDLInit( osrf_settings_host_value("/IDL") )) return 1; /* return non-zero to indicate error */
123 growing_buffer* method_name = buffer_init(64);
125 // Generic search thingy
126 buffer_add(method_name, MODULENAME);
127 buffer_add(method_name, ".json_query");
128 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
129 "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
132 // first we register all the transaction and savepoint methods
133 buffer_reset(method_name);
134 OSRF_BUFFER_ADD(method_name, MODULENAME);
135 OSRF_BUFFER_ADD(method_name, ".transaction.begin");
136 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
137 "beginTransaction", "", 0, 0 );
139 buffer_reset(method_name);
140 OSRF_BUFFER_ADD(method_name, MODULENAME);
141 OSRF_BUFFER_ADD(method_name, ".transaction.commit");
142 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
143 "commitTransaction", "", 0, 0 );
145 buffer_reset(method_name);
146 OSRF_BUFFER_ADD(method_name, MODULENAME);
147 OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
148 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
149 "rollbackTransaction", "", 0, 0 );
151 buffer_reset(method_name);
152 OSRF_BUFFER_ADD(method_name, MODULENAME);
153 OSRF_BUFFER_ADD(method_name, ".savepoint.set");
154 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
155 "setSavepoint", "", 1, 0 );
157 buffer_reset(method_name);
158 OSRF_BUFFER_ADD(method_name, MODULENAME);
159 OSRF_BUFFER_ADD(method_name, ".savepoint.release");
160 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
161 "releaseSavepoint", "", 1, 0 );
163 buffer_reset(method_name);
164 OSRF_BUFFER_ADD(method_name, MODULENAME);
165 OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
166 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
167 "rollbackSavepoint", "", 1, 0 );
169 buffer_free(method_name);
171 static const char* global_method[] = {
179 const int global_method_count
180 = sizeof( global_method ) / sizeof ( global_method[0] );
184 osrfStringArray* classes = osrfHashKeys( oilsIDL() );
185 osrfLogDebug(OSRF_LOG_MARK, "%d classes loaded", classes->size );
186 osrfLogDebug(OSRF_LOG_MARK,
187 "At least %d methods will be generated", classes->size * global_method_count);
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 const char* readonly = osrfHashGet(idlClass, "readonly");
208 osrfHash* idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
212 for( i = 0; i < global_method_count; ++i ) {
213 const char* method_type = global_method[ i ];
214 osrfLogDebug(OSRF_LOG_MARK,
215 "Using files to build %s class methods for %s", method_type, classname);
217 if (!idlClass_fieldmapper) continue;
220 if (!idlClass_permacrud) continue;
222 const char* tmp_method = method_type;
223 if ( *tmp_method == 'i' || *tmp_method == 's') {
224 tmp_method = "retrieve";
226 if (!osrfHashGet( idlClass_permacrud, tmp_method )) continue;
229 if ( str_is_true( readonly ) &&
230 ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd')
233 osrfHash* method_meta = osrfNewHash();
234 osrfHashSet(method_meta, idlClass, "class");
236 method_name = buffer_init(64);
238 buffer_fadd(method_name, "%s.%s.%s", MODULENAME, method_type, classname);
242 char* _fm = strdup( idlClass_fieldmapper );
243 part = strtok_r(_fm, ":", &st_tmp);
245 buffer_fadd(method_name, "%s.direct.%s", MODULENAME, part);
247 while ((part = strtok_r(NULL, ":", &st_tmp))) {
248 OSRF_BUFFER_ADD_CHAR(method_name, '.');
249 OSRF_BUFFER_ADD(method_name, part);
251 OSRF_BUFFER_ADD_CHAR(method_name, '.');
252 OSRF_BUFFER_ADD(method_name, method_type);
256 char* method = buffer_release(method_name);
258 osrfHashSet( method_meta, method, "methodname" );
259 osrfHashSet( method_meta, strdup(method_type), "methodtype" );
262 if (*method_type == 'i' || *method_type == 's') {
263 flags = flags | OSRF_METHOD_STREAMING;
266 osrfAppRegisterExtendedMethod(
269 "dispatchCRUDMethod",
283 static char* getSourceDefinition( osrfHash* class ) {
285 char* tabledef = osrfHashGet(class, "tablename");
288 tabledef = strdup(tabledef);
290 tabledef = osrfHashGet(class, "source_definition");
292 growing_buffer* tablebuf = buffer_init(128);
293 buffer_fadd( tablebuf, "(%s)", tabledef );
294 tabledef = buffer_release(tablebuf);
296 const char* classname = osrfHashGet( class, "classname" );
301 "%s ERROR No tablename or source_definition for class \"%s\"",
312 * Connects to the database
314 int osrfAppChildInit() {
316 osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
317 dbi_initialize(NULL);
318 osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
320 char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
321 char* user = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
322 char* host = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
323 char* port = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
324 char* db = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
325 char* pw = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
326 char* md = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion", MODULENAME);
328 osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
329 writehandle = dbi_conn_new(driver);
332 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
335 osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
337 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
338 "port=%s, user=%s, pw=%s, db=%s", MODULENAME, host, port, user, pw, db );
340 if(host) dbi_conn_set_option(writehandle, "host", host );
341 if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
342 if(user) dbi_conn_set_option(writehandle, "username", user);
343 if(pw) dbi_conn_set_option(writehandle, "password", pw );
344 if(db) dbi_conn_set_option(writehandle, "dbname", db );
346 if(md) max_flesh_depth = atoi(md);
347 if(max_flesh_depth < 0) max_flesh_depth = 1;
348 if(max_flesh_depth > 1000) max_flesh_depth = 1000;
357 if (dbi_conn_connect(writehandle) < 0) {
359 if (dbi_conn_connect(writehandle) < 0) {
360 dbi_conn_error(writehandle, &err);
361 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
366 osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
371 osrfStringArray* classes = osrfHashKeys( oilsIDL() );
373 while ( (classname = osrfStringArrayGetString(classes, i++)) ) {
374 osrfHash* class = osrfHashGet( oilsIDL(), classname );
375 osrfHash* fields = osrfHashGet( class, "fields" );
377 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
378 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
382 char* tabledef = getSourceDefinition(class);
384 tabledef = strdup( "(null)" );
386 growing_buffer* sql_buf = buffer_init(32);
387 buffer_fadd( sql_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
391 char* sql = buffer_release(sql_buf);
392 osrfLogDebug(OSRF_LOG_MARK, "%s Investigatory SQL = %s", MODULENAME, sql);
394 dbi_result result = dbi_conn_query(writehandle, sql);
400 const char* columnName;
402 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
404 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
406 /* fetch the fieldmapper index */
407 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
409 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", (char*)columnName);
411 /* determine the field type and storage attributes */
412 type = dbi_result_get_field_type(result, columnName);
416 case DBI_TYPE_INTEGER : {
418 if ( !osrfHashGet(_f, "primitive") )
419 osrfHashSet(_f,"number", "primitive");
421 int attr = dbi_result_get_field_attribs(result, columnName);
422 if( attr & DBI_INTEGER_SIZE8 )
423 osrfHashSet(_f,"INT8", "datatype");
425 osrfHashSet(_f,"INT", "datatype");
428 case DBI_TYPE_DECIMAL :
429 if ( !osrfHashGet(_f, "primitive") )
430 osrfHashSet(_f,"number", "primitive");
432 osrfHashSet(_f,"NUMERIC", "datatype");
435 case DBI_TYPE_STRING :
436 if ( !osrfHashGet(_f, "primitive") )
437 osrfHashSet(_f,"string", "primitive");
438 osrfHashSet(_f,"TEXT", "datatype");
441 case DBI_TYPE_DATETIME :
442 if ( !osrfHashGet(_f, "primitive") )
443 osrfHashSet(_f,"string", "primitive");
445 osrfHashSet(_f,"TIMESTAMP", "datatype");
448 case DBI_TYPE_BINARY :
449 if ( !osrfHashGet(_f, "primitive") )
450 osrfHashSet(_f,"string", "primitive");
452 osrfHashSet(_f,"BYTEA", "datatype");
457 "Setting [%s] to primitive [%s] and datatype [%s]...",
459 osrfHashGet(_f, "primitive"),
460 osrfHashGet(_f, "datatype")
464 dbi_result_free(result);
466 osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", (char*)classname);
470 osrfStringArrayFree(classes);
472 child_initialized = 1;
477 This function is a sleazy hack intended *only* for testing and
478 debugging. Any real server process should initialize the
479 database connection by calling osrfAppChildInit().
481 void set_cstore_dbi_conn( dbi_conn conn ) {
482 dbhandle = writehandle = conn;
485 void userDataFree( void* blob ) {
486 osrfHashFree( (osrfHash*)blob );
490 static void sessionDataFree( char* key, void* item ) {
491 if (!(strcmp(key,"xact_id"))) {
493 dbi_conn_query(writehandle, "ROLLBACK;");
500 int beginTransaction ( osrfMethodContext* ctx ) {
501 if(osrfMethodVerifyContext( ctx )) {
502 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
507 jsonObject* user = verifyUserPCRUD( ctx );
508 if (!user) return -1;
509 jsonObjectFree(user);
512 dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
514 osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
515 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error starting transaction" );
518 jsonObject* ret = jsonNewObject(ctx->session->session_id);
519 osrfAppRespondComplete( ctx, ret );
522 if (!ctx->session->userData) {
523 ctx->session->userData = osrfNewHash();
524 osrfHashSetCallback((osrfHash*)ctx->session->userData, &sessionDataFree);
527 osrfHashSet( (osrfHash*)ctx->session->userData, strdup( ctx->session->session_id ), "xact_id" );
528 ctx->session->userDataFree = &userDataFree;
534 int setSavepoint ( osrfMethodContext* ctx ) {
535 if(osrfMethodVerifyContext( ctx )) {
536 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
543 jsonObject* user = verifyUserPCRUD( ctx );
544 if (!user) return -1;
545 jsonObjectFree(user);
548 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
549 osrfAppSessionStatus(
551 OSRF_STATUS_INTERNALSERVERERROR,
552 "osrfMethodException",
554 "No active transaction -- required for savepoints"
559 const char* spName = jsonObjectGetString(jsonObjectGetIndex(ctx->params, spNamePos));
561 dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
565 "%s: Error creating savepoint %s in transaction %s",
568 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
570 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
571 "osrfMethodException", ctx->request, "Error creating savepoint" );
574 jsonObject* ret = jsonNewObject(spName);
575 osrfAppRespondComplete( ctx, ret );
581 int releaseSavepoint ( osrfMethodContext* ctx ) {
582 if(osrfMethodVerifyContext( ctx )) {
583 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
590 jsonObject* user = verifyUserPCRUD( ctx );
591 if (!user) return -1;
592 jsonObjectFree(user);
595 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
596 osrfAppSessionStatus(
598 OSRF_STATUS_INTERNALSERVERERROR,
599 "osrfMethodException",
601 "No active transaction -- required for savepoints"
606 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
608 dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
612 "%s: Error releasing savepoint %s in transaction %s",
615 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
617 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
618 "osrfMethodException", ctx->request, "Error releasing savepoint" );
621 jsonObject* ret = jsonNewObject(spName);
622 osrfAppRespondComplete( ctx, ret );
628 int rollbackSavepoint ( osrfMethodContext* ctx ) {
629 if(osrfMethodVerifyContext( ctx )) {
630 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
637 jsonObject* user = verifyUserPCRUD( ctx );
638 if (!user) return -1;
639 jsonObjectFree(user);
642 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
643 osrfAppSessionStatus(
645 OSRF_STATUS_INTERNALSERVERERROR,
646 "osrfMethodException",
648 "No active transaction -- required for savepoints"
653 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
655 dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
659 "%s: Error rolling back savepoint %s in transaction %s",
662 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
664 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
665 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
668 jsonObject* ret = jsonNewObject(spName);
669 osrfAppRespondComplete( ctx, ret );
675 int commitTransaction ( osrfMethodContext* ctx ) {
676 if(osrfMethodVerifyContext( ctx )) {
677 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
682 jsonObject* user = verifyUserPCRUD( ctx );
683 if (!user) return -1;
684 jsonObjectFree(user);
687 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
688 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to commit" );
692 dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
694 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
695 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error committing transaction" );
698 osrfHashRemove(ctx->session->userData, "xact_id");
699 jsonObject* ret = jsonNewObject(ctx->session->session_id);
700 osrfAppRespondComplete( ctx, ret );
706 int rollbackTransaction ( osrfMethodContext* ctx ) {
707 if(osrfMethodVerifyContext( ctx )) {
708 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
713 jsonObject* user = verifyUserPCRUD( ctx );
714 if (!user) return -1;
715 jsonObjectFree(user);
718 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
719 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to roll back" );
723 dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
725 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
726 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error rolling back transaction" );
729 osrfHashRemove(ctx->session->userData, "xact_id");
730 jsonObject* ret = jsonNewObject(ctx->session->session_id);
731 osrfAppRespondComplete( ctx, ret );
737 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
738 if(osrfMethodVerifyContext( ctx )) {
739 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
743 osrfHash* meta = (osrfHash*) ctx->method->userData;
744 osrfHash* class_obj = osrfHashGet( meta, "class" );
748 const char* methodtype = osrfHashGet(meta, "methodtype");
749 jsonObject * obj = NULL;
751 if (!strcmp(methodtype, "create")) {
752 obj = doCreate(ctx, &err);
753 osrfAppRespondComplete( ctx, obj );
755 else if (!strcmp(methodtype, "retrieve")) {
756 obj = doRetrieve(ctx, &err);
757 osrfAppRespondComplete( ctx, obj );
759 else if (!strcmp(methodtype, "update")) {
760 obj = doUpdate(ctx, &err);
761 osrfAppRespondComplete( ctx, obj );
763 else if (!strcmp(methodtype, "delete")) {
764 obj = doDelete(ctx, &err);
765 osrfAppRespondComplete( ctx, obj );
767 else if (!strcmp(methodtype, "search")) {
769 jsonObject* where_clause;
770 jsonObject* rest_of_query;
773 where_clause = jsonObjectGetIndex( ctx->params, 1 );
774 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
776 where_clause = jsonObjectGetIndex( ctx->params, 0 );
777 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
780 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
785 jsonIterator* itr = jsonNewIterator( obj );
786 while ((cur = jsonIteratorNext( itr ))) {
788 if(!verifyObjectPCRUD(ctx, cur)) continue;
790 osrfAppRespond( ctx, cur );
792 jsonIteratorFree(itr);
793 osrfAppRespondComplete( ctx, NULL );
795 } else if (!strcmp(methodtype, "id_list")) {
797 jsonObject* where_clause;
798 jsonObject* rest_of_query;
800 // We use the where clause without change. But we need
801 // to massage the rest of the query, so we work with a copy
802 // of it instead of modifying the original.
804 where_clause = jsonObjectGetIndex( ctx->params, 1 );
805 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
807 where_clause = jsonObjectGetIndex( ctx->params, 0 );
808 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
811 if ( rest_of_query ) {
812 jsonObjectRemoveKey( rest_of_query, "select" );
813 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
814 jsonObjectRemoveKey( rest_of_query, "flesh" );
815 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
817 rest_of_query = jsonNewObjectType( JSON_HASH );
820 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
826 "{ \"%s\":[\"%s\"] }",
827 osrfHashGet( class_obj, "classname" ),
828 osrfHashGet( class_obj, "primarykey" )
832 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
834 jsonObjectFree( rest_of_query );
838 jsonIterator* itr = jsonNewIterator( obj );
839 while ((cur = jsonIteratorNext( itr ))) {
841 if(!verifyObjectPCRUD(ctx, cur)) continue;
845 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
848 jsonIteratorFree(itr);
849 osrfAppRespondComplete( ctx, NULL );
852 osrfAppRespondComplete( ctx, obj );
860 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
863 osrfHash* meta = (osrfHash*) ctx->method->userData;
864 osrfHash* class = osrfHashGet( meta, "class" );
866 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
868 growing_buffer* msg = buffer_init(128);
871 "%s: %s method for type %s was passed a %s",
873 osrfHashGet(meta, "methodtype"),
874 osrfHashGet(class, "classname"),
878 char* m = buffer_release(msg);
879 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException", ctx->request, m );
887 ret = verifyObjectPCRUD( ctx, param );
895 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
896 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
897 jsonObject* auth_object = jsonNewObject(auth);
898 jsonObject* user = oilsUtilsQuickReq("open-ils.auth","open-ils.auth.session.retrieve", auth_object);
899 jsonObjectFree(auth_object);
901 if (!user->classname || strcmp(user->classname, "au")) {
903 growing_buffer* msg = buffer_init(128);
906 "%s: permacrud received a bad auth token: %s",
911 char* m = buffer_release(msg);
912 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException", ctx->request, m );
915 jsonObjectFree(user);
923 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
925 dbhandle = writehandle;
927 osrfHash* meta = (osrfHash*) ctx->method->userData;
928 osrfHash* class = osrfHashGet( meta, "class" );
929 char* method_type = strdup( osrfHashGet(meta, "methodtype") );
932 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
934 method_type = strdup("retrieve"); // search and id_list are equivelant to retrieve for this
935 } else if ( *method_type == 'u' || *method_type == 'd' ) {
936 fetch = 1; // MUST go to the db for the object for update and delete
939 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
943 // No permacrud for this method type on this class
945 growing_buffer* msg = buffer_init(128);
948 "%s: %s on class %s has no permacrud IDL entry",
950 osrfHashGet(meta, "methodtype"),
951 osrfHashGet(class, "classname")
954 char* m = buffer_release(msg);
955 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN, "osrfMethodException", ctx->request, m );
962 jsonObject* user = verifyUserPCRUD( ctx );
965 int userid = atoi( oilsFMGetString( user, "id" ) );
966 jsonObjectFree(user);
968 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
969 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
970 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
972 osrfStringArray* context_org_array = osrfNewStringArray(1);
975 char* pkey_value = NULL;
976 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
977 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions required, fetching top of the org tree" );
979 // check for perm at top of org tree
980 jsonObject* _tmp_params = jsonParseString("{\"parent_ou\":null}");
981 jsonObject* _list = doFieldmapperSearch(ctx, osrfHashGet( oilsIDL(), "aou" ),
982 _tmp_params, NULL, &err);
984 jsonObject* _tree_top = jsonObjectGetIndex(_list, 0);
987 jsonObjectFree(_tmp_params);
988 jsonObjectFree(_list);
990 growing_buffer* msg = buffer_init(128);
991 OSRF_BUFFER_ADD( msg, MODULENAME );
992 OSRF_BUFFER_ADD( msg,
993 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
995 char* m = buffer_release(msg);
996 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1002 osrfStringArrayAdd( context_org_array, oilsFMGetString( _tree_top, "id" ) );
1003 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", osrfStringArrayGetString(context_org_array, 0) );
1005 jsonObjectFree(_tmp_params);
1006 jsonObjectFree(_list);
1009 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, fetching context org ids" );
1010 char* pkey = osrfHashGet(class, "primarykey");
1011 jsonObject *param = NULL;
1013 if (obj->classname) {
1014 pkey_value = oilsFMGetString( obj, pkey );
1015 if (!fetch) param = jsonObjectClone(obj);
1016 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s", pkey_value );
1018 pkey_value = jsonObjectToSimpleString( obj );
1020 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value of %s and retrieving from the database", pkey_value );
1024 jsonObject* _tmp_params = jsonParseStringFmt("{\"%s\":\"%s\"}", pkey, pkey_value);
1025 jsonObject* _list = doFieldmapperSearch(
1026 ctx, class, _tmp_params, NULL, &err );
1028 param = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1030 jsonObjectFree(_tmp_params);
1031 jsonObjectFree(_list);
1035 osrfLogDebug( OSRF_LOG_MARK, "Object not found in the database with primary key %s of %s", pkey, pkey_value );
1037 growing_buffer* msg = buffer_init(128);
1040 "%s: no object found with primary key %s of %s",
1046 char* m = buffer_release(msg);
1047 osrfAppSessionStatus(
1049 OSRF_STATUS_INTERNALSERVERERROR,
1050 "osrfMethodException",
1056 if (pkey_value) free(pkey_value);
1061 if (local_context->size > 0) {
1062 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified", local_context->size);
1064 char* lcontext = NULL;
1065 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1066 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1069 "adding class-local field %s (value: %s) to the context org list",
1071 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1076 osrfStringArray* class_list;
1078 if (foreign_context) {
1079 class_list = osrfHashKeys( foreign_context );
1080 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_list->size);
1082 if (class_list->size > 0) {
1085 char* class_name = NULL;
1086 while ( (class_name = osrfStringArrayGetString(class_list, i++)) ) {
1087 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1091 "%d foreign context fields(s) specified for class %s",
1092 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1096 char* foreign_pkey = osrfHashGet(fcontext, "field");
1097 char* foreign_pkey_value = oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1099 jsonObject* _tmp_params = jsonParseStringFmt(
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 = jsonParseStringFmt(
1131 _list = doFieldmapperSearch(
1133 osrfHashGet( oilsIDL(), osrfHashGet( foreign_link_hash, "class" ) ),
1139 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1140 jsonObjectFree(_tmp_params);
1141 jsonObjectFree(_list);
1148 growing_buffer* msg = buffer_init(128);
1151 "%s: no object found with primary key %s of %s",
1157 char* m = buffer_release(msg);
1158 osrfAppSessionStatus(
1160 OSRF_STATUS_INTERNALSERVERERROR,
1161 "osrfMethodException",
1167 osrfStringArrayFree(class_list);
1168 free(foreign_pkey_value);
1169 jsonObjectFree(param);
1174 free(foreign_pkey_value);
1177 char* foreign_field = NULL;
1178 while ( (foreign_field = osrfStringArrayGetString(osrfHashGet(fcontext,"context"), j++)) ) {
1179 osrfStringArrayAdd( context_org_array, oilsFMGetString( _fparam, foreign_field ) );
1182 "adding foreign class %s field %s (value: %s) to the context org list",
1185 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1189 jsonObjectFree(_fparam);
1192 osrfStringArrayFree(class_list);
1196 jsonObjectFree(param);
1199 char* context_org = NULL;
1203 if (permission->size == 0) {
1204 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1209 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1211 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1217 "Checking object permission [%s] for user %d on object %s (class %s) at org %d",
1221 osrfHashGet(class, "classname"),
1225 result = dbi_conn_queryf(
1227 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1230 osrfHashGet(class, "classname"),
1238 "Recieved a result for object permission [%s] for user %d on object %s (class %s) at org %d",
1242 osrfHashGet(class, "classname"),
1246 if (dbi_result_first_row(result)) {
1247 jsonObject* return_val = oilsMakeJSONFromResult( result );
1248 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1252 "Status of object permission [%s] for user %d on object %s (class %s) at org %d is %s",
1256 osrfHashGet(class, "classname"),
1261 if ( *has_perm == 't' ) OK = 1;
1262 jsonObjectFree(return_val);
1265 dbi_result_free(result);
1270 osrfLogDebug( OSRF_LOG_MARK, "Checking non-object permission [%s] for user %d at org %d", perm, userid, atoi(context_org) );
1271 result = dbi_conn_queryf(
1273 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1280 osrfLogDebug( OSRF_LOG_MARK, "Received a result for permission [%s] for user %d at org %d",
1281 perm, userid, atoi(context_org) );
1282 if ( dbi_result_first_row(result) ) {
1283 jsonObject* return_val = oilsMakeJSONFromResult( result );
1284 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1285 osrfLogDebug( OSRF_LOG_MARK, "Status of permission [%s] for user %d at org %d is [%s]",
1286 perm, userid, atoi(context_org), has_perm );
1287 if ( *has_perm == 't' ) OK = 1;
1288 jsonObjectFree(return_val);
1291 dbi_result_free(result);
1299 if (pkey_value) free(pkey_value);
1300 osrfStringArrayFree(context_org_array);
1307 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1309 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1311 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1312 jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1314 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1315 jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1318 if (!verifyObjectClass(ctx, target)) {
1323 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1325 char* trans_id = NULL;
1326 if( ctx->session && ctx->session->userData )
1327 trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
1330 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1332 osrfAppSessionStatus(
1334 OSRF_STATUS_BADREQUEST,
1335 "osrfMethodException",
1337 "No active transaction -- required for CREATE"
1343 // The following test is harmless but redundant. If a class is
1344 // readonly, we don't register a create method for it.
1345 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1346 osrfAppSessionStatus(
1348 OSRF_STATUS_BADREQUEST,
1349 "osrfMethodException",
1351 "Cannot INSERT readonly class"
1357 // Set the last_xact_id
1358 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1360 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
1361 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1364 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1366 dbhandle = writehandle;
1368 osrfHash* fields = osrfHashGet(meta, "fields");
1369 char* pkey = osrfHashGet(meta, "primarykey");
1370 char* seq = osrfHashGet(meta, "sequence");
1372 growing_buffer* table_buf = buffer_init(128);
1373 growing_buffer* col_buf = buffer_init(128);
1374 growing_buffer* val_buf = buffer_init(128);
1376 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1377 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1378 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1379 buffer_add(val_buf,"VALUES (");
1385 osrfStringArray* field_list = osrfHashKeys( fields );
1386 while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
1388 osrfHash* field = osrfHashGet( fields, field_name );
1390 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1393 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1396 if (field_object && field_object->classname) {
1397 value = oilsFMGetString(
1399 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1402 value = jsonObjectToSimpleString( field_object );
1409 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1410 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1413 buffer_add(col_buf, field_name);
1415 if (!field_object || field_object->type == JSON_NULL) {
1416 buffer_add( val_buf, "DEFAULT" );
1418 } else if ( !strcmp(get_primitive( field ), "number") ) {
1419 const char* numtype = get_datatype( field );
1420 if ( !strcmp( numtype, "INT8") ) {
1421 buffer_fadd( val_buf, "%lld", atoll(value) );
1423 } else if ( !strcmp( numtype, "INT") ) {
1424 buffer_fadd( val_buf, "%d", atoi(value) );
1426 } else if ( !strcmp( numtype, "NUMERIC") ) {
1427 buffer_fadd( val_buf, "%f", atof(value) );
1430 if ( dbi_conn_quote_string(writehandle, &value) ) {
1431 OSRF_BUFFER_ADD( val_buf, value );
1434 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1435 osrfAppSessionStatus(
1437 OSRF_STATUS_INTERNALSERVERERROR,
1438 "osrfMethodException",
1440 "Error quoting string -- please see the error log for more details"
1443 buffer_free(table_buf);
1444 buffer_free(col_buf);
1445 buffer_free(val_buf);
1456 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1457 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1459 char* table_str = buffer_release(table_buf);
1460 char* col_str = buffer_release(col_buf);
1461 char* val_str = buffer_release(val_buf);
1462 growing_buffer* sql = buffer_init(128);
1463 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1468 char* query = buffer_release(sql);
1470 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1473 dbi_result result = dbi_conn_query(writehandle, query);
1475 jsonObject* obj = NULL;
1478 obj = jsonNewObject(NULL);
1481 "%s ERROR inserting %s object using query [%s]",
1483 osrfHashGet(meta, "fieldmapper"),
1486 osrfAppSessionStatus(
1488 OSRF_STATUS_INTERNALSERVERERROR,
1489 "osrfMethodException",
1491 "INSERT error -- please see the error log for more details"
1496 char* id = oilsFMGetString(target, pkey);
1498 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1499 growing_buffer* _id = buffer_init(10);
1500 buffer_fadd(_id, "%lld", new_id);
1501 id = buffer_release(_id);
1504 // Find quietness specification, if present
1505 const char* quiet_str = NULL;
1507 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1509 quiet_str = jsonObjectGetString( quiet_obj );
1512 if( str_is_true( quiet_str ) ) { // if quietness is specified
1513 obj = jsonNewObject(id);
1517 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1518 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1520 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1522 jsonObjectFree( where_clause );
1527 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1530 jsonObjectFree( list );
1543 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1553 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1555 const char* id = jsonObjectGetString(jsonObjectGetIndex(ctx->params, id_pos));
1556 jsonObject* order_hash = jsonObjectGetIndex(ctx->params, order_pos);
1560 "%s retrieving %s object with primary key value of %s",
1562 osrfHashGet(meta, "fieldmapper"),
1566 jsonObject* key_param = jsonNewObjectType( JSON_HASH );
1569 osrfHashGet( meta, "primarykey" ),
1570 jsonObjectClone( jsonObjectGetIndex(ctx->params, id_pos) )
1573 jsonObject* list = doFieldmapperSearch( ctx, meta, key_param, order_hash, err );
1576 jsonObjectFree( key_param );
1580 jsonObject* obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1582 jsonObjectFree( list );
1583 jsonObjectFree( key_param );
1586 if(!verifyObjectPCRUD(ctx, obj)) {
1587 jsonObjectFree(obj);
1590 growing_buffer* msg = buffer_init(128);
1591 OSRF_BUFFER_ADD( msg, MODULENAME );
1592 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1594 char* m = buffer_release(msg);
1595 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1606 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1607 growing_buffer* val_buf = buffer_init(32);
1608 const char* numtype = get_datatype( field );
1610 if ( !strncmp( numtype, "INT", 3 ) ) {
1611 if (value->type == JSON_NUMBER)
1612 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1613 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1615 //const char* val_str = jsonObjectGetString( value );
1616 //buffer_fadd( val_buf, "%ld", atol(val_str) );
1617 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1620 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
1621 if (value->type == JSON_NUMBER)
1622 //buffer_fadd( val_buf, "%f", jsonObjectGetNumber(value) );
1623 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1625 //const char* val_str = jsonObjectGetString( value );
1626 //buffer_fadd( val_buf, "%f", atof(val_str) );
1627 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1631 // Presumably this was really intended ot be a string, so quote it
1632 char* str = jsonObjectToSimpleString( value );
1633 if ( dbi_conn_quote_string(dbhandle, &str) ) {
1634 OSRF_BUFFER_ADD( val_buf, str );
1637 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
1639 buffer_free(val_buf);
1644 return buffer_release(val_buf);
1647 static char* searchINPredicate (const char* class, osrfHash* field,
1648 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1649 growing_buffer* sql_buf = buffer_init(32);
1655 osrfHashGet(field, "name")
1659 buffer_add(sql_buf, "IN (");
1660 } else if (!(strcasecmp(op,"not in"))) {
1661 buffer_add(sql_buf, "NOT IN (");
1663 buffer_add(sql_buf, "IN (");
1666 if (node->type == JSON_HASH) {
1667 // subquery predicate
1668 char* subpred = SELECT(
1670 jsonObjectGetKey( node, "select" ),
1671 jsonObjectGetKey( node, "from" ),
1672 jsonObjectGetKey( node, "where" ),
1673 jsonObjectGetKey( node, "having" ),
1674 jsonObjectGetKey( node, "order_by" ),
1675 jsonObjectGetKey( node, "limit" ),
1676 jsonObjectGetKey( node, "offset" ),
1681 buffer_add(sql_buf, subpred);
1684 buffer_free( sql_buf );
1688 } else if (node->type == JSON_ARRAY) {
1689 // literal value list
1690 int in_item_index = 0;
1691 int in_item_first = 1;
1692 const jsonObject* in_item;
1693 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1698 buffer_add(sql_buf, ", ");
1701 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1702 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1703 MODULENAME, json_type( in_item->type ) );
1704 buffer_free(sql_buf);
1708 // Append the literal value -- quoted if not a number
1709 if ( JSON_NUMBER == in_item->type ) {
1710 char* val = jsonNumberToDBString( field, in_item );
1711 OSRF_BUFFER_ADD( sql_buf, val );
1714 } else if ( !strcmp( get_primitive( field ), "number") ) {
1715 char* val = jsonNumberToDBString( field, in_item );
1716 OSRF_BUFFER_ADD( sql_buf, val );
1720 char* key_string = jsonObjectToSimpleString(in_item);
1721 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1722 OSRF_BUFFER_ADD( sql_buf, key_string );
1725 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1727 buffer_free(sql_buf);
1733 if( in_item_first ) {
1734 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1735 buffer_free( sql_buf );
1739 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1740 MODULENAME, json_type( node->type ) );
1741 buffer_free(sql_buf);
1745 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1747 return buffer_release(sql_buf);
1750 // Receive a JSON_ARRAY representing a function call. The first
1751 // entry in the array is the function name. The rest are parameters.
1752 static char* searchValueTransform( const jsonObject* array ) {
1754 if( array->size < 1 ) {
1755 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
1759 // Get the function name
1760 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
1761 if( func_item->type != JSON_STRING ) {
1762 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
1763 MODULENAME, json_type( func_item->type ) );
1767 growing_buffer* sql_buf = buffer_init(32);
1769 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1770 OSRF_BUFFER_ADD( sql_buf, "( " );
1772 // Get the parameters
1773 int func_item_index = 1; // We already grabbed the zeroth entry
1774 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1776 // Add a separator comma, if we need one
1777 if( func_item_index > 2 )
1778 buffer_add( sql_buf, ", " );
1780 // Add the current parameter
1781 if (func_item->type == JSON_NULL) {
1782 buffer_add( sql_buf, "NULL" );
1784 char* val = jsonObjectToSimpleString(func_item);
1785 if ( dbi_conn_quote_string(dbhandle, &val) ) {
1786 OSRF_BUFFER_ADD( sql_buf, val );
1789 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1790 buffer_free(sql_buf);
1797 buffer_add( sql_buf, " )" );
1799 return buffer_release(sql_buf);
1802 static char* searchFunctionPredicate (const char* class, osrfHash* field,
1803 const jsonObject* node, const char* op) {
1805 if( ! is_good_operator( op ) ) {
1806 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
1810 char* val = searchValueTransform(node);
1814 growing_buffer* sql_buf = buffer_init(32);
1819 osrfHashGet(field, "name"),
1826 return buffer_release(sql_buf);
1829 // class is a class name
1830 // field is a field definition as stored in the IDL
1831 // node comes from the method parameter, and may represent an entry in the SELECT list
1832 static char* searchFieldTransform (const char* class, osrfHash* field, const jsonObject* node) {
1833 growing_buffer* sql_buf = buffer_init(32);
1835 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
1836 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
1838 if(transform_subcolumn) {
1839 if( ! is_identifier( transform_subcolumn ) ) {
1840 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
1841 MODULENAME, transform_subcolumn );
1842 buffer_free( sql_buf );
1845 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
1848 if (field_transform) {
1850 if( ! is_identifier( field_transform ) ) {
1851 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
1852 MODULENAME, field_transform );
1853 buffer_free( sql_buf );
1857 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class, osrfHashGet(field, "name"));
1858 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
1861 if( array->type != JSON_ARRAY ) {
1862 osrfLogError( OSRF_LOG_MARK,
1863 "%s: Expected JSON_ARRAY for function params; found %s",
1864 MODULENAME, json_type( array->type ) );
1865 buffer_free( sql_buf );
1868 int func_item_index = 0;
1869 jsonObject* func_item;
1870 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1872 char* val = jsonObjectToSimpleString(func_item);
1875 buffer_add( sql_buf, ",NULL" );
1876 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
1877 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
1878 OSRF_BUFFER_ADD( sql_buf, val );
1880 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1882 buffer_free(sql_buf);
1889 buffer_add( sql_buf, " )" );
1892 buffer_fadd( sql_buf, "\"%s\".%s", class, osrfHashGet(field, "name"));
1895 if (transform_subcolumn)
1896 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
1898 return buffer_release(sql_buf);
1901 static char* searchFieldTransformPredicate (const char* class, osrfHash* field,
1902 const jsonObject* node, const char* op ) {
1904 if( ! is_good_operator( op ) ) {
1905 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
1909 char* field_transform = searchFieldTransform( class, field, node );
1910 if( ! field_transform )
1913 int extra_parens = 0; // boolean
1915 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
1916 if ( ! value_obj ) {
1917 value = searchWHERE( node, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1919 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
1920 free(field_transform);
1924 } else if ( value_obj->type == JSON_ARRAY ) {
1925 value = searchValueTransform( value_obj );
1927 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
1928 free( field_transform );
1931 } else if ( value_obj->type == JSON_HASH ) {
1932 value = searchWHERE( value_obj, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1934 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
1935 free(field_transform);
1939 } else if ( value_obj->type == JSON_NUMBER ) {
1940 value = jsonNumberToDBString( field, value_obj );
1941 } else if ( value_obj->type == JSON_NULL ) {
1942 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
1943 free(field_transform);
1945 } else if ( value_obj->type == JSON_BOOL ) {
1946 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
1947 free(field_transform);
1950 if ( !strcmp( get_primitive( field ), "number") ) {
1951 value = jsonNumberToDBString( field, value_obj );
1953 value = jsonObjectToSimpleString( value_obj );
1954 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
1955 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
1957 free(field_transform);
1963 const char* left_parens = "";
1964 const char* right_parens = "";
1966 if( extra_parens ) {
1971 growing_buffer* sql_buf = buffer_init(32);
1975 "%s%s %s %s %s %s%s",
1986 free(field_transform);
1988 return buffer_release(sql_buf);
1991 static char* searchSimplePredicate (const char* op, const char* class,
1992 osrfHash* field, const jsonObject* node) {
1994 if( ! is_good_operator( op ) ) {
1995 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2001 // Get the value to which we are comparing the specified column
2002 if (node->type != JSON_NULL) {
2003 if ( node->type == JSON_NUMBER ) {
2004 val = jsonNumberToDBString( field, node );
2005 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2006 val = jsonNumberToDBString( field, node );
2008 val = jsonObjectToSimpleString(node);
2013 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2014 // Value is not numeric; enclose it in quotes
2015 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2016 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2022 // Compare to a null value
2023 val = strdup( "NULL" );
2024 if (strcmp( op, "=" ))
2030 growing_buffer* sql_buf = buffer_init(32);
2031 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class, osrfHashGet(field, "name"), op, val );
2032 char* pred = buffer_release( sql_buf );
2039 static char* searchBETWEENPredicate (const char* class, osrfHash* field, const jsonObject* node) {
2041 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2042 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2044 if( NULL == y_node ) {
2045 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2048 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2049 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2056 if ( !strcmp( get_primitive( field ), "number") ) {
2057 x_string = jsonNumberToDBString(field, x_node);
2058 y_string = jsonNumberToDBString(field, y_node);
2061 x_string = jsonObjectToSimpleString(x_node);
2062 y_string = jsonObjectToSimpleString(y_node);
2063 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2064 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2065 MODULENAME, x_string, y_string);
2072 growing_buffer* sql_buf = buffer_init(32);
2073 buffer_fadd( sql_buf, "%s BETWEEN %s AND %s", osrfHashGet(field, "name"), x_string, y_string );
2077 return buffer_release(sql_buf);
2080 static char* searchPredicate ( const char* class, osrfHash* field,
2081 jsonObject* node, osrfMethodContext* ctx ) {
2084 if (node->type == JSON_ARRAY) { // equality IN search
2085 pred = searchINPredicate( class, field, node, NULL, ctx );
2086 } else if (node->type == JSON_HASH) { // other search
2087 jsonIterator* pred_itr = jsonNewIterator( node );
2088 if( !jsonIteratorHasNext( pred_itr ) ) {
2089 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2090 MODULENAME, osrfHashGet(field, "name") );
2092 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2094 // Verify that there are no additional predicates
2095 if( jsonIteratorHasNext( pred_itr ) ) {
2096 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2097 MODULENAME, osrfHashGet(field, "name") );
2098 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2099 pred = searchBETWEENPredicate( class, field, pred_node );
2100 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2101 pred = searchINPredicate( class, field, pred_node, pred_itr->key, ctx );
2102 else if ( pred_node->type == JSON_ARRAY )
2103 pred = searchFunctionPredicate( class, field, pred_node, pred_itr->key );
2104 else if ( pred_node->type == JSON_HASH )
2105 pred = searchFieldTransformPredicate( class, field, pred_node, pred_itr->key );
2107 pred = searchSimplePredicate( pred_itr->key, class, field, pred_node );
2109 jsonIteratorFree(pred_itr);
2111 } else if (node->type == JSON_NULL) { // IS NULL search
2112 growing_buffer* _p = buffer_init(64);
2115 "\"%s\".%s IS NULL",
2117 osrfHashGet(field, "name")
2119 pred = buffer_release(_p);
2120 } else { // equality search
2121 pred = searchSimplePredicate( "=", class, field, node );
2140 field : call_number,
2156 static char* searchJOIN ( const jsonObject* join_hash, osrfHash* leftmeta ) {
2158 const jsonObject* working_hash;
2159 jsonObject* freeable_hash = NULL;
2161 if (join_hash->type == JSON_STRING) {
2162 // create a wrapper around a copy of the original
2163 const char* _tmp = jsonObjectGetString( join_hash );
2164 freeable_hash = jsonNewObjectType(JSON_HASH);
2165 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2166 working_hash = freeable_hash;
2169 if( join_hash->type != JSON_HASH ) {
2172 "%s: JOIN failed; expected JSON object type not found",
2177 working_hash = join_hash;
2180 growing_buffer* join_buf = buffer_init(128);
2181 const char* leftclass = osrfHashGet(leftmeta, "classname");
2183 jsonObject* snode = NULL;
2184 jsonIterator* search_itr = jsonNewIterator( working_hash );
2186 while ( (snode = jsonIteratorNext( search_itr )) ) {
2187 const char* class = search_itr->key;
2188 osrfHash* idlClass = osrfHashGet( oilsIDL(), class );
2192 "%s: JOIN failed. No class \"%s\" defined in IDL",
2196 jsonIteratorFree( search_itr );
2197 buffer_free( join_buf );
2199 jsonObjectFree( freeable_hash );
2203 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2204 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2206 if (field && !fkey) {
2207 // Look up the corresponding join column in the IDL.
2208 // The link must be defined in the child table,
2209 // and point to the right parent table.
2210 osrfHash* idl_link = (osrfHash*) oilsIDLFindPath( "/%s/links/%s", class, field );
2211 const char* reltype = NULL;
2212 const char* other_class = NULL;
2213 reltype = osrfHashGet( idl_link, "reltype" );
2214 if( reltype && strcmp( reltype, "has_many" ) )
2215 other_class = osrfHashGet( idl_link, "class" );
2216 if( other_class && !strcmp( other_class, leftclass ) )
2217 fkey = osrfHashGet( idl_link, "key" );
2221 "%s: JOIN failed. No link defined from %s.%s to %s",
2227 buffer_free(join_buf);
2229 jsonObjectFree(freeable_hash);
2230 jsonIteratorFree(search_itr);
2234 } else if (!field && fkey) {
2235 // Look up the corresponding join column in the IDL.
2236 // The link must be defined in the child table,
2237 // and point to the right parent table.
2238 osrfHash* idl_link = (osrfHash*) oilsIDLFindPath( "/%s/links/%s", leftclass, fkey );
2239 const char* reltype = NULL;
2240 const char* other_class = NULL;
2241 reltype = osrfHashGet( idl_link, "reltype" );
2242 if( reltype && strcmp( reltype, "has_many" ) )
2243 other_class = osrfHashGet( idl_link, "class" );
2244 if( other_class && !strcmp( other_class, class ) )
2245 field = osrfHashGet( idl_link, "key" );
2249 "%s: JOIN failed. No link defined from %s.%s to %s",
2255 buffer_free(join_buf);
2257 jsonObjectFree(freeable_hash);
2258 jsonIteratorFree(search_itr);
2262 } else if (!field && !fkey) {
2263 osrfHash* _links = oilsIDL_links( leftclass );
2265 // For each link defined for the left class:
2266 // see if the link references the joined class
2267 osrfHashIterator* itr = osrfNewHashIterator( _links );
2268 osrfHash* curr_link = NULL;
2269 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2270 const char* other_class = osrfHashGet( curr_link, "class" );
2271 if( other_class && !strcmp( other_class, class ) ) {
2273 // In the IDL, the parent class doesn't know then names of the child
2274 // columns that are pointing to it, so don't use that end of the link
2275 const char* reltype = osrfHashGet( curr_link, "reltype" );
2276 if( reltype && strcmp( reltype, "has_many" ) ) {
2277 // Found a link between the classes
2278 fkey = osrfHashIteratorKey( itr );
2279 field = osrfHashGet( curr_link, "key" );
2284 osrfHashIteratorFree( itr );
2286 if (!field || !fkey) {
2287 // Do another such search, with the classes reversed
2288 _links = oilsIDL_links( class );
2290 // For each link defined for the joined class:
2291 // see if the link references the left class
2292 osrfHashIterator* itr = osrfNewHashIterator( _links );
2293 osrfHash* curr_link = NULL;
2294 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2295 const char* other_class = osrfHashGet( curr_link, "class" );
2296 if( other_class && !strcmp( other_class, leftclass ) ) {
2298 // In the IDL, the parent class doesn't know then names of the child
2299 // columns that are pointing to it, so don't use that end of the link
2300 const char* reltype = osrfHashGet( curr_link, "reltype" );
2301 if( reltype && strcmp( reltype, "has_many" ) ) {
2302 // Found a link between the classes
2303 field = osrfHashIteratorKey( itr );
2304 fkey = osrfHashGet( curr_link, "key" );
2309 osrfHashIteratorFree( itr );
2312 if (!field || !fkey) {
2315 "%s: JOIN failed. No link defined between %s and %s",
2320 buffer_free(join_buf);
2322 jsonObjectFree(freeable_hash);
2323 jsonIteratorFree(search_itr);
2329 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2331 if ( !strcasecmp(type,"left") ) {
2332 buffer_add(join_buf, " LEFT JOIN");
2333 } else if ( !strcasecmp(type,"right") ) {
2334 buffer_add(join_buf, " RIGHT JOIN");
2335 } else if ( !strcasecmp(type,"full") ) {
2336 buffer_add(join_buf, " FULL JOIN");
2338 buffer_add(join_buf, " INNER JOIN");
2341 buffer_add(join_buf, " INNER JOIN");
2344 char* table = getSourceDefinition(idlClass);
2346 jsonIteratorFree( search_itr );
2347 buffer_free( join_buf );
2349 jsonObjectFree( freeable_hash );
2353 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2354 table, class, class, field, leftclass, fkey);
2357 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2359 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2360 if ( filter_op && !strcasecmp("or",filter_op) ) {
2361 buffer_add( join_buf, " OR " );
2363 buffer_add( join_buf, " AND " );
2366 char* jpred = searchWHERE( filter, idlClass, AND_OP_JOIN, NULL );
2368 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2369 OSRF_BUFFER_ADD( join_buf, jpred );
2374 "%s: JOIN failed. Invalid conditional expression.",
2377 jsonIteratorFree( search_itr );
2378 buffer_free( join_buf );
2380 jsonObjectFree( freeable_hash );
2385 buffer_add(join_buf, " ) ");
2387 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2389 char* jpred = searchJOIN( join_filter, idlClass );
2391 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2392 OSRF_BUFFER_ADD( join_buf, jpred );
2395 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2396 jsonIteratorFree( search_itr );
2397 buffer_free( join_buf );
2399 jsonObjectFree( freeable_hash );
2406 jsonObjectFree(freeable_hash);
2407 jsonIteratorFree(search_itr);
2409 return buffer_release(join_buf);
2414 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2415 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2416 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2418 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2420 search_hash is the JSON expression of the conditions.
2421 meta is the class definition from the IDL, for the relevant table.
2422 opjoin_type indicates whether multiple conditions, if present, should be
2423 connected by AND or OR.
2424 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2425 to pass it to other functions -- and all they do with it is to use the session
2426 and request members to send error messages back to the client.
2430 static char* searchWHERE ( const jsonObject* search_hash, osrfHash* meta, int opjoin_type, osrfMethodContext* ctx ) {
2434 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2442 growing_buffer* sql_buf = buffer_init(128);
2444 jsonObject* node = NULL;
2447 if ( search_hash->type == JSON_ARRAY ) {
2448 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2449 jsonIterator* search_itr = jsonNewIterator( search_hash );
2450 if( !jsonIteratorHasNext( search_itr ) ) {
2453 "%s: Invalid predicate structure: empty JSON array",
2456 jsonIteratorFree( search_itr );
2457 buffer_free( sql_buf );
2461 while ( (node = jsonIteratorNext( search_itr )) ) {
2465 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2466 else buffer_add(sql_buf, " AND ");
2469 char* subpred = searchWHERE( node, meta, opjoin_type, ctx );
2471 buffer_fadd(sql_buf, "( %s )", subpred);
2474 jsonIteratorFree( search_itr );
2475 buffer_free( sql_buf );
2479 jsonIteratorFree(search_itr);
2481 } else if ( search_hash->type == JSON_HASH ) {
2482 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2483 jsonIterator* search_itr = jsonNewIterator( search_hash );
2484 if( !jsonIteratorHasNext( search_itr ) ) {
2487 "%s: Invalid predicate structure: empty JSON object",
2490 jsonIteratorFree( search_itr );
2491 buffer_free( sql_buf );
2495 while ( (node = jsonIteratorNext( search_itr )) ) {
2500 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2501 else buffer_add(sql_buf, " AND ");
2504 if ( '+' == search_itr->key[ 0 ] ) {
2505 if ( node->type == JSON_STRING ) {
2506 // Intended purpose; to allow reference to a Boolean column
2508 // Verify that the class alias is not empty
2509 if( '\0' == search_itr->key[ 1 ] ) {
2512 "%s: Table alias is empty",
2515 jsonIteratorFree( search_itr );
2516 buffer_free( sql_buf );
2520 // Verify that the string looks like an identifier.
2521 const char* subpred = jsonObjectGetString( node );
2522 if( ! is_identifier( subpred ) ) {
2525 "%s: Invalid boolean identifier in WHERE clause: \"%s\"",
2529 jsonIteratorFree( search_itr );
2530 buffer_free( sql_buf );
2534 buffer_fadd(sql_buf, " \"%s\".%s ", search_itr->key + 1, subpred);
2536 char* subpred = searchWHERE( node, osrfHashGet( oilsIDL(), search_itr->key + 1 ), AND_OP_JOIN, ctx );
2538 buffer_fadd(sql_buf, "( %s )", subpred);
2541 jsonIteratorFree( search_itr );
2542 buffer_free( sql_buf );
2546 } else if ( !strcasecmp("-or",search_itr->key) ) {
2547 char* subpred = searchWHERE( node, meta, OR_OP_JOIN, ctx );
2549 buffer_fadd(sql_buf, "( %s )", subpred);
2552 buffer_free( sql_buf );
2555 } else if ( !strcasecmp("-and",search_itr->key) ) {
2556 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2558 buffer_fadd(sql_buf, "( %s )", subpred);
2561 buffer_free( sql_buf );
2564 } else if ( !strcasecmp("-not",search_itr->key) ) {
2565 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2567 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2570 buffer_free( sql_buf );
2573 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2574 char* subpred = SELECT(
2576 jsonObjectGetKey( node, "select" ),
2577 jsonObjectGetKey( node, "from" ),
2578 jsonObjectGetKey( node, "where" ),
2579 jsonObjectGetKey( node, "having" ),
2580 jsonObjectGetKey( node, "order_by" ),
2581 jsonObjectGetKey( node, "limit" ),
2582 jsonObjectGetKey( node, "offset" ),
2587 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2590 buffer_free( sql_buf );
2593 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2594 char* subpred = SELECT(
2596 jsonObjectGetKey( node, "select" ),
2597 jsonObjectGetKey( node, "from" ),
2598 jsonObjectGetKey( node, "where" ),
2599 jsonObjectGetKey( node, "having" ),
2600 jsonObjectGetKey( node, "order_by" ),
2601 jsonObjectGetKey( node, "limit" ),
2602 jsonObjectGetKey( node, "offset" ),
2607 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2610 buffer_free( sql_buf );
2616 char* class = osrfHashGet(meta, "classname");
2617 osrfHash* fields = osrfHashGet(meta, "fields");
2618 osrfHash* field = osrfHashGet( fields, search_itr->key );
2622 char* table = getSourceDefinition(meta);
2624 table = strdup( "(?)" );
2627 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2633 buffer_free(sql_buf);
2635 jsonIteratorFree(search_itr);
2639 char* subpred = searchPredicate( class, field, node, ctx );
2641 buffer_add( sql_buf, subpred );
2644 buffer_free(sql_buf);
2645 jsonIteratorFree(search_itr);
2650 jsonIteratorFree(search_itr);
2653 // ERROR ... only hash and array allowed at this level
2654 char* predicate_string = jsonObjectToJSON( search_hash );
2657 "%s: Invalid predicate structure: %s",
2661 buffer_free(sql_buf);
2662 free(predicate_string);
2666 return buffer_release(sql_buf);
2669 // Return 1 if the class is in the FROM clause, or 0 if not
2670 static int is_joined( jsonObject* from_clause, const char* class ) {
2674 else if( from_clause->type == JSON_STRING ) {
2675 if( strcmp( class, jsonObjectGetString( from_clause ) ) )
2679 } else if( from_clause->type != JSON_HASH ) {
2681 } else { // Look at any subjoins
2682 jsonIterator* class_itr = jsonNewIterator( from_clause );
2683 jsonObject* curr_class;
2684 int rc = 0; // return code
2685 while ( ( curr_class = jsonIteratorNext( class_itr ) ) ) {
2686 if( ! strcmp( class_itr->key, class ) ) {
2690 jsonObject* subjoin = jsonObjectGetKey( curr_class, "join" );
2691 if( subjoin && is_joined( subjoin, class ) ) {
2697 jsonIteratorFree( class_itr );
2704 /* method context */ osrfMethodContext* ctx,
2706 /* SELECT */ jsonObject* selhash,
2707 /* FROM */ jsonObject* join_hash,
2708 /* WHERE */ jsonObject* search_hash,
2709 /* HAVING */ jsonObject* having_hash,
2710 /* ORDER BY */ jsonObject* order_hash,
2711 /* LIMIT */ jsonObject* limit,
2712 /* OFFSET */ jsonObject* offset,
2713 /* flags */ int flags
2715 const char* locale = osrf_message_get_last_locale();
2717 // in case we don't get a select list
2718 jsonObject* defaultselhash = NULL;
2720 // general tmp objects
2721 const jsonObject* tmp_const;
2722 jsonObject* selclass = NULL;
2723 jsonObject* selfield = NULL;
2724 jsonObject* snode = NULL;
2725 jsonObject* onode = NULL;
2727 char* string = NULL;
2728 int from_function = 0;
2733 // the core search class
2734 char* core_class = NULL;
2736 // metadata about the core search class
2737 osrfHash* core_meta = NULL;
2739 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale);
2741 // punt if there's no core class
2742 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2745 "%s: FROM clause is missing or empty",
2749 osrfAppSessionStatus(
2751 OSRF_STATUS_INTERNALSERVERERROR,
2752 "osrfMethodException",
2754 "FROM clause is missing or empty in JSON query"
2759 // get the core class -- the only key of the top level FROM clause, or a string
2760 if (join_hash->type == JSON_HASH) {
2761 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2762 snode = jsonIteratorNext( tmp_itr );
2764 core_class = strdup( tmp_itr->key );
2767 jsonObject* extra = jsonIteratorNext( tmp_itr );
2769 jsonIteratorFree( tmp_itr );
2772 // There shouldn't be more than one entry in join_hash
2776 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2780 osrfAppSessionStatus(
2782 OSRF_STATUS_INTERNALSERVERERROR,
2783 "osrfMethodException",
2785 "Malformed FROM clause in JSON query"
2788 return NULL; // Malformed join_hash; extra entry
2790 } else if (join_hash->type == JSON_ARRAY) {
2792 core_class = jsonObjectToSimpleString( jsonObjectGetIndex(join_hash, 0) );
2795 } else if (join_hash->type == JSON_STRING) {
2796 core_class = jsonObjectToSimpleString( join_hash );
2802 "%s: FROM clause is unexpected JSON type: %s",
2804 json_type( join_hash->type )
2807 osrfAppSessionStatus(
2809 OSRF_STATUS_INTERNALSERVERERROR,
2810 "osrfMethodException",
2812 "Ill-formed FROM clause in JSON query"
2818 if (!from_function) {
2819 // Get the IDL class definition for the core class
2820 core_meta = osrfHashGet( oilsIDL(), core_class );
2821 if( !core_meta ) { // Didn't find it?
2824 "%s: SELECT clause references undefined class: \"%s\"",
2829 osrfAppSessionStatus(
2831 OSRF_STATUS_INTERNALSERVERERROR,
2832 "osrfMethodException",
2834 "SELECT clause references undefined class in JSON query"
2840 // Make sure the class isn't virtual
2841 if( str_is_true( osrfHashGet( core_meta, "virtual" ) ) ) {
2844 "%s: Core class is virtual: \"%s\"",
2849 osrfAppSessionStatus(
2851 OSRF_STATUS_INTERNALSERVERERROR,
2852 "osrfMethodException",
2854 "FROM clause references virtual class in JSON query"
2861 // if the select list is empty, or the core class field list is '*',
2862 // build the default select list ...
2864 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
2865 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2866 } else if( selhash->type != JSON_HASH ) {
2869 "%s: Expected JSON_HASH for SELECT clause; found %s",
2871 json_type( selhash->type )
2875 osrfAppSessionStatus(
2877 OSRF_STATUS_INTERNALSERVERERROR,
2878 "osrfMethodException",
2880 "Malformed SELECT clause in JSON query"
2884 } else if ( (tmp_const = jsonObjectGetKeyConst( selhash, core_class )) && tmp_const->type == JSON_STRING ) {
2885 const char* _x = jsonObjectGetString( tmp_const );
2886 if (!strncmp( "*", _x, 1 )) {
2887 jsonObjectRemoveKey( selhash, core_class );
2888 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2893 growing_buffer* sql_buf = buffer_init(128);
2895 // temp buffers for the SELECT list and GROUP BY clause
2896 growing_buffer* select_buf = buffer_init(128);
2897 growing_buffer* group_buf = buffer_init(128);
2899 int aggregate_found = 0; // boolean
2901 // Build a select list
2902 if(from_function) // From a function we select everything
2903 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
2906 // If we need to build a default list, prepare to do so
2907 jsonObject* _tmp = jsonObjectGetKey( selhash, core_class );
2908 if ( _tmp && !_tmp->size ) {
2910 osrfHash* core_fields = osrfHashGet( core_meta, "fields" );
2912 osrfHashIterator* field_itr = osrfNewHashIterator( core_fields );
2913 osrfHash* field_def;
2914 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2915 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2916 // This field is not virtual, so add it to the list
2917 jsonObjectPush( _tmp, jsonNewObject( osrfHashIteratorKey( field_itr ) ) );
2920 osrfHashIteratorFree( field_itr );
2923 // Now build the actual select list
2927 jsonIterator* selclass_itr = jsonNewIterator( selhash );
2928 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
2930 const char* cname = selclass_itr->key;
2932 // Make sure the target relation is in the FROM clause.
2934 // At this point join_hash is a step down from the join_hash we
2935 // received as a parameter. If the original was a JSON_STRING,
2936 // then json_hash is now NULL. If the original was a JSON_HASH,
2937 // then json_hash is now the first (and only) entry in it,
2938 // denoting the core class. We've already excluded the
2939 // possibility that the original was a JSON_ARRAY, because in
2940 // that case from_function would be non-NULL, and we wouldn't
2943 // If the current class isn't the core class
2944 // and it isn't in the join tree, bail out
2945 if ( strcmp( core_class, cname ) && ! is_joined( join_hash, cname ) ) {
2948 "%s: SELECT clause references class not in FROM clause: \"%s\"",
2953 osrfAppSessionStatus(
2955 OSRF_STATUS_INTERNALSERVERERROR,
2956 "osrfMethodException",
2958 "Selected class not in FROM clause in JSON query"
2960 jsonIteratorFree( selclass_itr );
2961 buffer_free( sql_buf );
2962 buffer_free( select_buf );
2963 buffer_free( group_buf );
2964 if( defaultselhash ) jsonObjectFree( defaultselhash );
2969 // Look up some attributes of the current class, so that we
2970 // don't have to look them up again for each field
2971 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
2972 osrfHash* class_field_set = osrfHashGet( idlClass, "fields" );
2973 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
2974 const char* class_tname = osrfHashGet( idlClass, "tablename" );
2976 // stitch together the column list ...
2977 jsonIterator* select_itr = jsonNewIterator( selclass );
2978 while ( (selfield = jsonIteratorNext( select_itr )) ) { // for each SELECT column
2980 // If we need a separator comma, add one
2984 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
2987 // ... if it's a string, just toss it on the pile
2988 if (selfield->type == JSON_STRING) {
2990 // Look up the field in the IDL
2991 const char* col_name = jsonObjectGetString( selfield );
2992 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
2994 // No such field in current class
2997 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3003 osrfAppSessionStatus(
3005 OSRF_STATUS_INTERNALSERVERERROR,
3006 "osrfMethodException",
3008 "Selected column not defined in JSON query"
3010 jsonIteratorFree( select_itr );
3011 jsonIteratorFree( selclass_itr );
3012 buffer_free( sql_buf );
3013 buffer_free( select_buf );
3014 buffer_free( group_buf );
3015 if( defaultselhash ) jsonObjectFree( defaultselhash );
3018 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3019 // Virtual field not allowed
3022 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3028 osrfAppSessionStatus(
3030 OSRF_STATUS_INTERNALSERVERERROR,
3031 "osrfMethodException",
3033 "Selected column may not be virtual in JSON query"
3035 jsonIteratorFree( select_itr );
3036 jsonIteratorFree( selclass_itr );
3037 buffer_free( sql_buf );
3038 buffer_free( select_buf );
3039 buffer_free( group_buf );
3040 if( defaultselhash ) jsonObjectFree( defaultselhash );
3047 if (flags & DISABLE_I18N)
3050 i18n = osrfHashGet(field_def, "i18n");
3052 if( str_is_true( i18n ) ) {
3053 buffer_fadd( select_buf,
3054 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3055 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3057 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3060 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3063 // ... but it could be an object, in which case we check for a Field Transform
3064 } else if (selfield->type == JSON_HASH) {
3066 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3068 // Get the field definition from the IDL
3069 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3071 // No such field in current class
3074 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3080 osrfAppSessionStatus(
3082 OSRF_STATUS_INTERNALSERVERERROR,
3083 "osrfMethodException",
3085 "Selected column is not defined in JSON query"
3087 jsonIteratorFree( select_itr );
3088 jsonIteratorFree( selclass_itr );
3089 buffer_free( sql_buf );
3090 buffer_free( select_buf );
3091 buffer_free( group_buf );
3092 if( defaultselhash ) jsonObjectFree( defaultselhash );
3095 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3096 // No such field in current class
3099 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3105 osrfAppSessionStatus(
3107 OSRF_STATUS_INTERNALSERVERERROR,
3108 "osrfMethodException",
3110 "Selected column is virtual in JSON query"
3112 jsonIteratorFree( select_itr );
3113 jsonIteratorFree( selclass_itr );
3114 buffer_free( sql_buf );
3115 buffer_free( select_buf );
3116 buffer_free( group_buf );
3117 if( defaultselhash ) jsonObjectFree( defaultselhash );
3122 // Decide what to use as a column alias
3124 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3125 _alias = jsonObjectGetString( tmp_const );
3126 } else { // Use field name as the alias
3130 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3131 char* transform_str = searchFieldTransform(cname, field_def, selfield);
3132 if( transform_str ) {
3133 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3134 free(transform_str);
3137 osrfAppSessionStatus(
3139 OSRF_STATUS_INTERNALSERVERERROR,
3140 "osrfMethodException",
3142 "Unable to generate transform function in JSON query"
3144 jsonIteratorFree( select_itr );
3145 jsonIteratorFree( selclass_itr );
3146 buffer_free( sql_buf );
3147 buffer_free( select_buf );
3148 buffer_free( group_buf );
3149 if( defaultselhash ) jsonObjectFree( defaultselhash );
3157 if (flags & DISABLE_I18N)
3160 i18n = osrfHashGet(field_def, "i18n");
3162 if( str_is_true( i18n ) ) {
3163 buffer_fadd( select_buf,
3164 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3165 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3167 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3170 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3177 "%s: Selected item is unexpected JSON type: %s",
3179 json_type( selfield->type )
3182 osrfAppSessionStatus(
3184 OSRF_STATUS_INTERNALSERVERERROR,
3185 "osrfMethodException",
3187 "Ill-formed SELECT item in JSON query"
3189 jsonIteratorFree( select_itr );
3190 jsonIteratorFree( selclass_itr );
3191 buffer_free( sql_buf );
3192 buffer_free( select_buf );
3193 buffer_free( group_buf );
3194 if( defaultselhash ) jsonObjectFree( defaultselhash );
3199 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3200 if( obj_is_true( agg_obj ) )
3201 aggregate_found = 1;
3203 // Append a comma (except for the first one)
3204 // and add the column to a GROUP BY clause
3208 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3210 buffer_fadd(group_buf, " %d", sel_pos);
3214 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3216 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3217 if ( ! obj_is_true( aggregate_obj ) ) {
3221 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3224 buffer_fadd(group_buf, " %d", sel_pos);
3227 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3231 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3234 _column = searchFieldTransform(cname, field, selfield);
3235 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3236 OSRF_BUFFER_ADD(group_buf, _column);
3237 _column = searchFieldTransform(cname, field, selfield);
3244 } // end while -- iterating across SELECT columns
3246 jsonIteratorFree(select_itr);
3247 } // end while -- iterating across classes
3249 jsonIteratorFree(selclass_itr);
3253 char* col_list = buffer_release(select_buf);
3255 if (from_function) table = searchValueTransform(join_hash);
3256 else table = getSourceDefinition(core_meta);
3260 osrfAppSessionStatus(
3262 OSRF_STATUS_INTERNALSERVERERROR,
3263 "osrfMethodException",
3265 "Unable to identify table for core class"
3268 buffer_free( sql_buf );
3269 buffer_free( group_buf );
3270 if( defaultselhash ) jsonObjectFree( defaultselhash );
3275 // Put it all together
3276 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3280 char* order_by_list = NULL;
3281 char* having_buf = NULL;
3283 if (!from_function) {
3285 // Now, walk the join tree and add that clause
3287 char* join_clause = searchJOIN( join_hash, core_meta );
3289 buffer_add(sql_buf, join_clause);
3293 osrfAppSessionStatus(
3295 OSRF_STATUS_INTERNALSERVERERROR,
3296 "osrfMethodException",
3298 "Unable to construct JOIN clause(s)"
3300 buffer_free( sql_buf );
3301 buffer_free( group_buf );
3302 if( defaultselhash ) jsonObjectFree( defaultselhash );
3308 // Build a WHERE clause, if there is one
3309 if ( search_hash ) {
3310 buffer_add(sql_buf, " WHERE ");
3312 // and it's on the WHERE clause
3313 char* pred = searchWHERE( search_hash, core_meta, AND_OP_JOIN, ctx );
3316 buffer_add(sql_buf, pred);
3320 osrfAppSessionStatus(
3322 OSRF_STATUS_INTERNALSERVERERROR,
3323 "osrfMethodException",
3325 "Severe query error in WHERE predicate -- see error log for more details"
3329 buffer_free(group_buf);
3330 buffer_free(sql_buf);
3331 if (defaultselhash) jsonObjectFree(defaultselhash);
3336 // Build a HAVING clause, if there is one
3337 if ( having_hash ) {
3339 // and it's on the the WHERE clause
3340 having_buf = searchWHERE( having_hash, core_meta, AND_OP_JOIN, ctx );
3342 if( ! having_buf ) {
3344 osrfAppSessionStatus(
3346 OSRF_STATUS_INTERNALSERVERERROR,
3347 "osrfMethodException",
3349 "Severe query error in HAVING predicate -- see error log for more details"
3353 buffer_free(group_buf);
3354 buffer_free(sql_buf);
3355 if (defaultselhash) jsonObjectFree(defaultselhash);
3360 growing_buffer* order_buf = NULL; // to collect ORDER BY list
3362 // Build an ORDER BY clause, if there is one
3363 if( NULL == order_hash )
3364 ; // No ORDER BY? do nothing
3365 else if( JSON_ARRAY == order_hash->type ) {
3366 // Array of field specifications, each specification being a
3367 // hash to define the class, field, and other details
3369 jsonObject* order_spec;
3370 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
3372 if( JSON_HASH != order_spec->type ) {
3373 osrfLogError(OSRF_LOG_MARK,
3374 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
3375 MODULENAME, json_type( order_spec->type ) );
3377 osrfAppSessionStatus(
3379 OSRF_STATUS_INTERNALSERVERERROR,
3380 "osrfMethodException",
3382 "Malformed ORDER BY clause -- see error log for more details"
3384 buffer_free( order_buf );
3387 buffer_free(group_buf);
3388 buffer_free(sql_buf);
3389 if (defaultselhash) jsonObjectFree(defaultselhash);
3394 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
3396 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
3399 OSRF_BUFFER_ADD(order_buf, ", ");
3401 order_buf = buffer_init(128);
3403 if( !field || !class ) {
3404 osrfLogError(OSRF_LOG_MARK,
3405 "%s: Missing class or field name in field specification of ORDER BY clause",
3408 osrfAppSessionStatus(
3410 OSRF_STATUS_INTERNALSERVERERROR,
3411 "osrfMethodException",
3413 "Malformed ORDER BY clause -- see error log for more details"
3415 buffer_free( order_buf );
3418 buffer_free(group_buf);
3419 buffer_free(sql_buf);
3420 if (defaultselhash) jsonObjectFree(defaultselhash);
3424 if ( ! jsonObjectGetKeyConst( selhash, class )
3425 && strcmp( core_class, class )
3426 && ! is_joined( join_hash, class ) ) {
3427 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
3428 "not in FROM clause", MODULENAME, class );
3430 osrfAppSessionStatus(
3432 OSRF_STATUS_INTERNALSERVERERROR,
3433 "osrfMethodException",
3435 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3439 buffer_free(group_buf);
3440 buffer_free(sql_buf);
3441 if (defaultselhash) jsonObjectFree(defaultselhash);
3445 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s", class, field );
3447 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
3448 MODULENAME, class, field );
3450 osrfAppSessionStatus(
3452 OSRF_STATUS_INTERNALSERVERERROR,
3453 "osrfMethodException",
3455 "Invalid field referenced in ORDER BY clause -- see error log for more details"
3459 buffer_free(group_buf);
3460 buffer_free(sql_buf);
3461 if (defaultselhash) jsonObjectFree(defaultselhash);
3463 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3464 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3465 MODULENAME, field );
3467 osrfAppSessionStatus(
3469 OSRF_STATUS_INTERNALSERVERERROR,
3470 "osrfMethodException",
3472 "Virtual field in ORDER BY clause -- see error log for more details"
3474 buffer_free( order_buf );
3477 buffer_free(group_buf);
3478 buffer_free(sql_buf);
3479 if (defaultselhash) jsonObjectFree(defaultselhash);
3483 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
3484 char* transform_str = searchFieldTransform( class, field_def, order_spec );
3485 if( ! transform_str ) {
3487 osrfAppSessionStatus(
3489 OSRF_STATUS_INTERNALSERVERERROR,
3490 "osrfMethodException",
3492 "Severe query error in ORDER BY clause -- see error log for more details"
3494 buffer_free( order_buf );
3497 buffer_free(group_buf);
3498 buffer_free(sql_buf);
3499 if (defaultselhash) jsonObjectFree(defaultselhash);
3503 OSRF_BUFFER_ADD( order_buf, transform_str );
3504 free( transform_str );
3507 buffer_fadd( order_buf, "\"%s\".%s", class, field );
3509 const char* direction =
3510 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
3512 if( direction[ 0 ] || 'D' == direction[ 0 ] )
3513 OSRF_BUFFER_ADD( order_buf, " DESC" );
3515 OSRF_BUFFER_ADD( order_buf, " ASC" );
3518 } else if( JSON_HASH == order_hash->type ) {
3519 // This hash is keyed on class name. Each class has either
3520 // an array of field names or a hash keyed on field name.
3521 jsonIterator* class_itr = jsonNewIterator( order_hash );
3522 while ( (snode = jsonIteratorNext( class_itr )) ) {
3524 if ( ! jsonObjectGetKeyConst( selhash,class_itr->key )
3525 && strcmp( core_class, class_itr->key )
3526 && ! is_joined( join_hash, class_itr->key ) ) {
3527 osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
3528 MODULENAME, class_itr->key );
3530 osrfAppSessionStatus(
3532 OSRF_STATUS_INTERNALSERVERERROR,
3533 "osrfMethodException",
3535 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3537 jsonIteratorFree( class_itr );
3538 buffer_free( order_buf );
3541 buffer_free(group_buf);
3542 buffer_free(sql_buf);
3543 if (defaultselhash) jsonObjectFree(defaultselhash);
3547 osrfHash* field_list_def = oilsIDLFindPath( "/%s/fields", class_itr->key );
3549 if ( snode->type == JSON_HASH ) {
3551 // Hash is keyed on field names from the current class. For each field
3552 // there is another layer of hash to define the sorting details, if any,
3553 // or a string to indicate direction of sorting.
3554 jsonIterator* order_itr = jsonNewIterator( snode );
3555 while ( (onode = jsonIteratorNext( order_itr )) ) {
3557 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
3559 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3560 MODULENAME, order_itr->key );
3562 osrfAppSessionStatus(
3564 OSRF_STATUS_INTERNALSERVERERROR,
3565 "osrfMethodException",
3567 "Invalid field in ORDER BY clause -- see error log for more details"
3569 jsonIteratorFree( order_itr );
3570 jsonIteratorFree( class_itr );
3571 buffer_free( order_buf );
3574 buffer_free(group_buf);
3575 buffer_free(sql_buf);
3576 if (defaultselhash) jsonObjectFree(defaultselhash);
3578 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3579 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3580 MODULENAME, order_itr->key );
3582 osrfAppSessionStatus(
3584 OSRF_STATUS_INTERNALSERVERERROR,
3585 "osrfMethodException",
3587 "Virtual field in ORDER BY clause -- see error log for more details"
3589 jsonIteratorFree( order_itr );
3590 jsonIteratorFree( class_itr );
3591 buffer_free( order_buf );
3594 buffer_free(group_buf);
3595 buffer_free(sql_buf);
3596 if (defaultselhash) jsonObjectFree(defaultselhash);
3600 const char* direction = NULL;
3601 if ( onode->type == JSON_HASH ) {
3602 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3603 string = searchFieldTransform(
3605 osrfHashGet( field_list_def, order_itr->key ),
3609 if( ctx ) osrfAppSessionStatus(
3611 OSRF_STATUS_INTERNALSERVERERROR,
3612 "osrfMethodException",
3614 "Severe query error in ORDER BY clause -- see error log for more details"
3616 jsonIteratorFree( order_itr );
3617 jsonIteratorFree( class_itr );
3620 buffer_free(group_buf);
3621 buffer_free(order_buf);
3622 buffer_free(sql_buf);
3623 if (defaultselhash) jsonObjectFree(defaultselhash);
3627 growing_buffer* field_buf = buffer_init(16);
3628 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3629 string = buffer_release(field_buf);
3632 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3633 const char* dir = jsonObjectGetString(tmp_const);
3634 if (!strncasecmp(dir, "d", 1)) {
3635 direction = " DESC";
3641 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
3642 osrfLogError( OSRF_LOG_MARK,
3643 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
3644 MODULENAME, json_type( onode->type ) );
3646 osrfAppSessionStatus(
3648 OSRF_STATUS_INTERNALSERVERERROR,
3649 "osrfMethodException",
3651 "Malformed ORDER BY clause -- see error log for more details"
3653 jsonIteratorFree( order_itr );
3654 jsonIteratorFree( class_itr );
3657 buffer_free(group_buf);
3658 buffer_free(order_buf);
3659 buffer_free(sql_buf);
3660 if (defaultselhash) jsonObjectFree(defaultselhash);
3664 string = strdup(order_itr->key);
3665 const char* dir = jsonObjectGetString(onode);
3666 if (!strncasecmp(dir, "d", 1)) {
3667 direction = " DESC";
3674 OSRF_BUFFER_ADD(order_buf, ", ");
3676 order_buf = buffer_init(128);
3678 OSRF_BUFFER_ADD(order_buf, string);
3682 OSRF_BUFFER_ADD(order_buf, direction);
3686 jsonIteratorFree(order_itr);
3688 } else if ( snode->type == JSON_ARRAY ) {
3690 // Array is a list of fields from the current class
3691 jsonIterator* order_itr = jsonNewIterator( snode );
3692 while ( (onode = jsonIteratorNext( order_itr )) ) {
3694 const char* _f = jsonObjectGetString( onode );
3696 osrfHash* field_def = osrfHashGet( field_list_def, _f );
3698 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3701 osrfAppSessionStatus(
3703 OSRF_STATUS_INTERNALSERVERERROR,
3704 "osrfMethodException",
3706 "Invalid field in ORDER BY clause -- see error log for more details"
3708 jsonIteratorFree( order_itr );
3709 jsonIteratorFree( class_itr );
3710 buffer_free( order_buf );
3713 buffer_free(group_buf);
3714 buffer_free(sql_buf);
3715 if (defaultselhash) jsonObjectFree(defaultselhash);
3717 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3718 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3721 osrfAppSessionStatus(
3723 OSRF_STATUS_INTERNALSERVERERROR,
3724 "osrfMethodException",
3726 "Virtual field in ORDER BY clause -- see error log for more details"
3728 jsonIteratorFree( order_itr );
3729 jsonIteratorFree( class_itr );
3730 buffer_free( order_buf );
3733 buffer_free(group_buf);
3734 buffer_free(sql_buf);
3735 if (defaultselhash) jsonObjectFree(defaultselhash);
3740 OSRF_BUFFER_ADD(order_buf, ", ");
3742 order_buf = buffer_init(128);
3744 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
3747 // jsonIteratorFree(order_itr);
3750 // IT'S THE OOOOOOOOOOOLD STYLE!
3752 osrfLogError(OSRF_LOG_MARK,
3753 "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3755 osrfAppSessionStatus(
3757 OSRF_STATUS_INTERNALSERVERERROR,
3758 "osrfMethodException",
3760 "Severe query error -- see error log for more details"
3766 buffer_free(group_buf);
3767 buffer_free(order_buf);
3768 buffer_free(sql_buf);
3769 if (defaultselhash) jsonObjectFree(defaultselhash);
3770 jsonIteratorFree(class_itr);
3775 osrfLogError(OSRF_LOG_MARK,
3776 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
3777 MODULENAME, json_type( order_hash->type ) );
3779 osrfAppSessionStatus(
3781 OSRF_STATUS_INTERNALSERVERERROR,
3782 "osrfMethodException",
3784 "Malformed ORDER BY clause -- see error log for more details"
3786 buffer_free( order_buf );
3789 buffer_free(group_buf);
3790 buffer_free(sql_buf);
3791 if (defaultselhash) jsonObjectFree(defaultselhash);
3794 // jsonIteratorFree(class_itr);
3797 order_by_list = buffer_release( order_buf );
3801 string = buffer_release(group_buf);
3803 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
3804 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3805 OSRF_BUFFER_ADD( sql_buf, string );
3810 if( having_buf && *having_buf ) {
3811 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3812 OSRF_BUFFER_ADD( sql_buf, having_buf );
3816 if( order_by_list ) {
3818 if ( *order_by_list ) {
3819 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3820 OSRF_BUFFER_ADD( sql_buf, order_by_list );
3823 free( order_by_list );
3827 const char* str = jsonObjectGetString(limit);
3828 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
3832 const char* str = jsonObjectGetString(offset);
3833 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
3836 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3839 if (defaultselhash) jsonObjectFree(defaultselhash);
3841 return buffer_release(sql_buf);
3845 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
3847 const char* locale = osrf_message_get_last_locale();
3849 osrfHash* fields = osrfHashGet(meta, "fields");
3850 char* core_class = osrfHashGet(meta, "classname");
3852 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
3854 jsonObject* node = NULL;
3855 jsonObject* snode = NULL;
3856 jsonObject* onode = NULL;
3857 const jsonObject* _tmp = NULL;
3858 jsonObject* selhash = NULL;
3859 jsonObject* defaultselhash = NULL;
3861 growing_buffer* sql_buf = buffer_init(128);
3862 growing_buffer* select_buf = buffer_init(128);
3864 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
3865 defaultselhash = jsonNewObjectType(JSON_HASH);
3866 selhash = defaultselhash;
3869 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
3870 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
3871 jsonObject* flist = jsonObjectGetKey( selhash, core_class );
3876 osrfStringArray* keys = osrfHashKeys( fields );
3877 while ( (field = osrfStringArrayGetString(keys, i++)) ) {
3878 if( ! str_is_true( osrfHashGet( osrfHashGet( fields, field ), "virtual" ) ) )
3879 jsonObjectPush( flist, jsonNewObject( field ) );
3881 osrfStringArrayFree(keys);
3885 jsonIterator* class_itr = jsonNewIterator( selhash );
3886 while ( (snode = jsonIteratorNext( class_itr )) ) {
3888 char* cname = class_itr->key;
3889 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
3890 if (!idlClass) continue;
3892 if (strcmp(core_class,class_itr->key)) {
3893 if (!join_hash) continue;
3895 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
3897 jsonObjectFree(found);
3901 jsonObjectFree(found);
3904 jsonIterator* select_itr = jsonNewIterator( snode );
3905 while ( (node = jsonIteratorNext( select_itr )) ) {
3906 const char* item_str = jsonObjectGetString( node );
3907 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
3908 char* fname = osrfHashGet(field, "name");
3910 if (!field) continue;
3915 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
3920 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
3921 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
3924 i18n = osrfHashGet(field, "i18n");
3926 if( str_is_true( i18n ) ) {
3927 char* pkey = osrfHashGet(idlClass, "primarykey");
3928 char* tname = osrfHashGet(idlClass, "tablename");
3930 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);
3932 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3935 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3939 jsonIteratorFree(select_itr);
3942 jsonIteratorFree(class_itr);
3944 char* col_list = buffer_release(select_buf);
3945 char* table = getSourceDefinition(meta);
3947 table = strdup( "(null)" );
3949 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
3954 char* join_clause = searchJOIN( join_hash, meta );
3955 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
3956 OSRF_BUFFER_ADD(sql_buf, join_clause);
3960 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
3961 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
3963 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
3965 char* pred = searchWHERE( search_hash, meta, AND_OP_JOIN, ctx );
3967 osrfAppSessionStatus(
3969 OSRF_STATUS_INTERNALSERVERERROR,
3970 "osrfMethodException",
3972 "Severe query error -- see error log for more details"
3974 buffer_free(sql_buf);
3975 if(defaultselhash) jsonObjectFree(defaultselhash);
3978 buffer_add(sql_buf, pred);
3983 char* string = NULL;
3984 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
3986 growing_buffer* order_buf = buffer_init(128);
3989 jsonIterator* class_itr = jsonNewIterator( _tmp );
3990 while ( (snode = jsonIteratorNext( class_itr )) ) {
3992 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
3995 if ( snode->type == JSON_HASH ) {
3997 jsonIterator* order_itr = jsonNewIterator( snode );
3998 while ( (onode = jsonIteratorNext( order_itr )) ) {
4000 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4001 class_itr->key, order_itr->key );
4005 char* direction = NULL;
4006 if ( onode->type == JSON_HASH ) {
4007 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4008 string = searchFieldTransform( class_itr->key, field_def, onode );
4010 osrfAppSessionStatus(
4012 OSRF_STATUS_INTERNALSERVERERROR,
4013 "osrfMethodException",
4015 "Severe query error in ORDER BY clause -- see error log for more details"
4017 jsonIteratorFree( order_itr );
4018 jsonIteratorFree( class_itr );
4019 buffer_free( order_buf );
4020 buffer_free( sql_buf );
4021 if( defaultselhash ) jsonObjectFree( defaultselhash );
4025 growing_buffer* field_buf = buffer_init(16);
4026 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4027 string = buffer_release(field_buf);
4030 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4031 const char* dir = jsonObjectGetString(_tmp);
4032 if (!strncasecmp(dir, "d", 1)) {
4033 direction = " DESC";
4040 string = strdup(order_itr->key);
4041 const char* dir = jsonObjectGetString(onode);
4042 if (!strncasecmp(dir, "d", 1)) {
4043 direction = " DESC";
4052 buffer_add(order_buf, ", ");
4055 buffer_add(order_buf, string);
4059 buffer_add(order_buf, direction);
4064 jsonIteratorFree(order_itr);
4067 const char* str = jsonObjectGetString(snode);
4068 buffer_add(order_buf, str);
4074 jsonIteratorFree(class_itr);
4076 string = buffer_release(order_buf);
4079 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4080 OSRF_BUFFER_ADD( sql_buf, string );
4086 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4087 const char* str = jsonObjectGetString(_tmp);
4095 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4097 const char* str = jsonObjectGetString(_tmp);
4106 if (defaultselhash) jsonObjectFree(defaultselhash);
4108 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4109 return buffer_release(sql_buf);
4112 int doJSONSearch ( osrfMethodContext* ctx ) {
4113 if(osrfMethodVerifyContext( ctx )) {
4114 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4118 osrfLogDebug(OSRF_LOG_MARK, "Recieved query request");
4123 dbhandle = writehandle;
4125 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4129 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4130 flags |= SELECT_DISTINCT;
4132 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4133 flags |= DISABLE_I18N;
4135 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4138 jsonObjectGetKey( hash, "select" ),
4139 jsonObjectGetKey( hash, "from" ),
4140 jsonObjectGetKey( hash, "where" ),
4141 jsonObjectGetKey( hash, "having" ),
4142 jsonObjectGetKey( hash, "order_by" ),
4143 jsonObjectGetKey( hash, "limit" ),
4144 jsonObjectGetKey( hash, "offset" ),
4153 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4154 dbi_result result = dbi_conn_query(dbhandle, sql);
4157 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4159 if (dbi_result_first_row(result)) {
4160 /* JSONify the result */
4161 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4164 jsonObject* return_val = oilsMakeJSONFromResult( result );
4165 osrfAppRespond( ctx, return_val );
4166 jsonObjectFree( return_val );
4167 } while (dbi_result_next_row(result));
4170 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4173 osrfAppRespondComplete( ctx, NULL );
4175 /* clean up the query */
4176 dbi_result_free(result);
4180 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4181 osrfAppSessionStatus(
4183 OSRF_STATUS_INTERNALSERVERERROR,
4184 "osrfMethodException",
4186 "Severe query error -- see error log for more details"
4194 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4195 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4198 dbhandle = writehandle;
4200 osrfHash* links = osrfHashGet(meta, "links");
4201 osrfHash* fields = osrfHashGet(meta, "fields");
4202 char* core_class = osrfHashGet(meta, "classname");
4203 char* pkey = osrfHashGet(meta, "primarykey");
4205 const jsonObject* _tmp;
4208 char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4210 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4215 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4217 dbi_result result = dbi_conn_query(dbhandle, sql);
4218 if( NULL == result ) {
4219 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4220 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4221 osrfAppSessionStatus(
4223 OSRF_STATUS_INTERNALSERVERERROR,
4224 "osrfMethodException",
4226 "Severe query error -- see error log for more details"
4233 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4236 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
4237 osrfHash* dedup = osrfNewHash();
4239 if (dbi_result_first_row(result)) {
4240 /* JSONify the result */
4241 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4243 obj = oilsMakeFieldmapperFromResult( result, meta );
4244 char* pkey_val = oilsFMGetString( obj, pkey );
4245 if ( osrfHashGet( dedup, pkey_val ) ) {
4246 jsonObjectFree(obj);
4249 osrfHashSet( dedup, pkey_val, pkey_val );
4250 jsonObjectPush(res_list, obj);
4252 } while (dbi_result_next_row(result));
4254 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
4258 osrfHashFree(dedup);
4259 /* clean up the query */
4260 dbi_result_free(result);
4263 if (res_list->size && query_hash) {
4264 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
4266 int x = (int)jsonObjectGetNumber(_tmp);
4267 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
4269 const jsonObject* temp_blob;
4270 if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
4272 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
4273 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
4275 osrfStringArray* link_fields = NULL;
4278 if (flesh_fields->size == 1) {
4279 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
4280 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
4285 link_fields = osrfNewStringArray(1);
4286 jsonIterator* _i = jsonNewIterator( flesh_fields );
4287 while ((_f = jsonIteratorNext( _i ))) {
4288 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4290 jsonIteratorFree(_i);
4295 jsonIterator* itr = jsonNewIterator( res_list );
4296 while ((cur = jsonIteratorNext( itr ))) {
4301 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4303 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4305 osrfHash* kid_link = osrfHashGet(links, link_field);
4306 if (!kid_link) continue;
4308 osrfHash* field = osrfHashGet(fields, link_field);
4309 if (!field) continue;
4311 osrfHash* value_field = field;
4313 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4314 if (!kid_idl) continue;
4316 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4317 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4320 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4321 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4324 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4326 if (link_map->size > 0) {
4327 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4330 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4335 osrfHashGet(kid_link, "class"),
4342 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4343 osrfHashGet(kid_link, "field"),
4344 osrfHashGet(kid_link, "class"),
4345 osrfHashGet(kid_link, "key"),
4346 osrfHashGet(kid_link, "reltype")
4349 const char* search_key = jsonObjectGetString(
4352 atoi( osrfHashGet(value_field, "array_position") )
4357 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4361 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
4363 // construct WHERE clause
4364 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
4367 osrfHashGet(kid_link, "key"),
4368 jsonNewObject( search_key )
4371 // construct the rest of the query
4372 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
4373 jsonObjectSetKey( rest_of_query, "flesh",
4374 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4378 jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
4380 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
4381 jsonObjectSetKey( rest_of_query, "order_by",
4382 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
4386 if (jsonObjectGetKeyConst(query_hash, "select")) {
4387 jsonObjectSetKey( rest_of_query, "select",
4388 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
4392 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
4393 where_clause, rest_of_query, err);
4395 jsonObjectFree( where_clause );
4396 jsonObjectFree( rest_of_query );
4399 osrfStringArrayFree(link_fields);
4400 jsonIteratorFree(itr);
4401 jsonObjectFree(res_list);
4402 jsonObjectFree(flesh_blob);
4406 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4408 jsonObject* X = NULL;
4409 if ( link_map->size > 0 && kids->size > 0 ) {
4411 kids = jsonNewObjectType(JSON_ARRAY);
4413 jsonObject* _k_node;
4414 jsonIterator* _k = jsonNewIterator( X );
4415 while ((_k_node = jsonIteratorNext( _k ))) {
4421 (unsigned long)atoi(
4427 osrfHashGet(kid_link, "class")
4431 osrfStringArrayGetString( link_map, 0 )
4440 jsonIteratorFree(_k);
4443 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4444 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4447 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4448 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4452 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4453 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4456 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4457 jsonObjectClone( kids )
4462 jsonObjectFree(kids);
4466 jsonObjectFree( kids );
4468 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4469 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4473 jsonObjectFree( flesh_blob );
4474 osrfStringArrayFree(link_fields);
4475 jsonIteratorFree(itr);
4484 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
4486 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4488 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
4490 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
4493 if (!verifyObjectClass(ctx, target)) {
4498 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4499 osrfAppSessionStatus(
4501 OSRF_STATUS_BADREQUEST,
4502 "osrfMethodException",
4504 "No active transaction -- required for UPDATE"
4510 // The following test is harmless but redundant. If a class is
4511 // readonly, we don't register an update method for it.
4512 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4513 osrfAppSessionStatus(
4515 OSRF_STATUS_BADREQUEST,
4516 "osrfMethodException",
4518 "Cannot UPDATE readonly class"
4524 dbhandle = writehandle;
4526 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4528 // Set the last_xact_id
4529 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4531 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
4532 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4535 char* pkey = osrfHashGet(meta, "primarykey");
4536 osrfHash* fields = osrfHashGet(meta, "fields");
4538 char* id = oilsFMGetString( target, pkey );
4542 "%s updating %s object with %s = %s",
4544 osrfHashGet(meta, "fieldmapper"),
4549 growing_buffer* sql = buffer_init(128);
4550 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4555 osrfStringArray* field_list = osrfHashKeys( fields );
4556 while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
4558 osrfHash* field = osrfHashGet( fields, field_name );
4560 if(!( strcmp( field_name, pkey ) )) continue;
4561 if( str_is_true( osrfHashGet(osrfHashGet(fields,field_name), "virtual") ) )
4564 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4566 int value_is_numeric = 0; // boolean
4568 if (field_object && field_object->classname) {
4569 value = oilsFMGetString(
4571 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4574 value = jsonObjectToSimpleString( field_object );
4575 if( field_object && JSON_NUMBER == field_object->type )
4576 value_is_numeric = 1;
4579 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s", osrfHashGet(meta, "fieldmapper"), field_name, value);
4581 if (!field_object || field_object->type == JSON_NULL) {
4582 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) ) && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
4583 if (first) first = 0;
4584 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4585 buffer_fadd( sql, " %s = NULL", field_name );
4588 } else if ( value_is_numeric || !strcmp( get_primitive( field ), "number") ) {
4589 if (first) first = 0;
4590 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4592 const char* numtype = get_datatype( field );
4593 if ( !strncmp( numtype, "INT", 3 ) ) {
4594 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
4595 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
4596 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
4598 // Must really be intended as a string, so quote it
4599 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4600 buffer_fadd( sql, " %s = %s", field_name, value );
4602 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4603 osrfAppSessionStatus(
4605 OSRF_STATUS_INTERNALSERVERERROR,
4606 "osrfMethodException",
4608 "Error quoting string -- please see the error log for more details"
4618 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
4621 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4622 if (first) first = 0;
4623 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4624 buffer_fadd( sql, " %s = %s", field_name, value );
4627 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4628 osrfAppSessionStatus(
4630 OSRF_STATUS_INTERNALSERVERERROR,
4631 "osrfMethodException",
4633 "Error quoting string -- please see the error log for more details"
4647 jsonObject* obj = jsonNewObject(id);
4649 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4650 dbi_conn_quote_string(dbhandle, &id);
4652 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
4654 char* query = buffer_release(sql);
4655 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
4657 dbi_result result = dbi_conn_query(dbhandle, query);
4661 jsonObjectFree(obj);
4662 obj = jsonNewObject(NULL);
4665 "%s ERROR updating %s object with %s = %s",
4667 osrfHashGet(meta, "fieldmapper"),
4678 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4680 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4682 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4683 osrfAppSessionStatus(
4685 OSRF_STATUS_BADREQUEST,
4686 "osrfMethodException",
4688 "No active transaction -- required for DELETE"
4694 // The following test is harmless but redundant. If a class is
4695 // readonly, we don't register a delete method for it.
4696 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4697 osrfAppSessionStatus(
4699 OSRF_STATUS_BADREQUEST,
4700 "osrfMethodException",
4702 "Cannot DELETE readonly class"
4708 dbhandle = writehandle;
4712 char* pkey = osrfHashGet(meta, "primarykey");
4720 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4721 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4726 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4729 if (!verifyObjectPCRUD( ctx, NULL )) {
4734 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4739 "%s deleting %s object with %s = %s",
4741 osrfHashGet(meta, "fieldmapper"),
4746 obj = jsonNewObject(id);
4748 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4749 dbi_conn_quote_string(writehandle, &id);
4751 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4754 jsonObjectFree(obj);
4755 obj = jsonNewObject(NULL);
4758 "%s ERROR deleting %s object with %s = %s",
4760 osrfHashGet(meta, "fieldmapper"),
4773 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4774 if(!(result && meta)) return jsonNULL;
4776 jsonObject* object = jsonNewObject(NULL);
4777 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4779 osrfHash* fields = osrfHashGet(meta, "fields");
4781 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4785 char dt_string[256];
4789 int columnIndex = 1;
4791 unsigned short type;
4792 const char* columnName;
4794 /* cycle through the column list */
4795 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4797 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4799 fmIndex = -1; // reset the position
4801 /* determine the field type and storage attributes */
4802 type = dbi_result_get_field_type(result, columnName);
4803 attr = dbi_result_get_field_attribs(result, columnName);
4805 /* fetch the fieldmapper index */
4806 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4808 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4811 const char* pos = (char*)osrfHashGet(_f, "array_position");
4812 if ( !pos ) continue;
4814 fmIndex = atoi( pos );
4815 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
4820 if (dbi_result_field_is_null(result, columnName)) {
4821 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
4826 case DBI_TYPE_INTEGER :
4828 if( attr & DBI_INTEGER_SIZE8 )
4829 jsonObjectSetIndex( object, fmIndex,
4830 jsonNewNumberObject(dbi_result_get_longlong(result, columnName)));
4832 jsonObjectSetIndex( object, fmIndex,
4833 jsonNewNumberObject(dbi_result_get_int(result, columnName)));
4837 case DBI_TYPE_DECIMAL :
4838 jsonObjectSetIndex( object, fmIndex,
4839 jsonNewNumberObject(dbi_result_get_double(result, columnName)));
4842 case DBI_TYPE_STRING :
4848 jsonNewObject( dbi_result_get_string(result, columnName) )
4853 case DBI_TYPE_DATETIME :
4855 memset(dt_string, '\0', sizeof(dt_string));
4856 memset(&gmdt, '\0', sizeof(gmdt));
4858 _tmp_dt = dbi_result_get_datetime(result, columnName);
4861 if (!(attr & DBI_DATETIME_DATE)) {
4862 gmtime_r( &_tmp_dt, &gmdt );
4863 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4864 } else if (!(attr & DBI_DATETIME_TIME)) {
4865 localtime_r( &_tmp_dt, &gmdt );
4866 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4868 localtime_r( &_tmp_dt, &gmdt );
4869 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4872 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
4876 case DBI_TYPE_BINARY :
4877 osrfLogError( OSRF_LOG_MARK,
4878 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4886 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
4887 if(!result) return jsonNULL;
4889 jsonObject* object = jsonNewObject(NULL);
4892 char dt_string[256];
4896 int columnIndex = 1;
4898 unsigned short type;
4899 const char* columnName;
4901 /* cycle through the column list */
4902 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4904 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4906 fmIndex = -1; // reset the position
4908 /* determine the field type and storage attributes */
4909 type = dbi_result_get_field_type(result, columnName);
4910 attr = dbi_result_get_field_attribs(result, columnName);
4912 if (dbi_result_field_is_null(result, columnName)) {
4913 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
4918 case DBI_TYPE_INTEGER :
4920 if( attr & DBI_INTEGER_SIZE8 )
4921 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_longlong(result, columnName)) );
4923 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_int(result, columnName)) );
4926 case DBI_TYPE_DECIMAL :
4927 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_double(result, columnName)) );
4930 case DBI_TYPE_STRING :
4931 jsonObjectSetKey( object, columnName, jsonNewObject(dbi_result_get_string(result, columnName)) );
4934 case DBI_TYPE_DATETIME :
4936 memset(dt_string, '\0', sizeof(dt_string));
4937 memset(&gmdt, '\0', sizeof(gmdt));
4939 _tmp_dt = dbi_result_get_datetime(result, columnName);
4942 if (!(attr & DBI_DATETIME_DATE)) {
4943 gmtime_r( &_tmp_dt, &gmdt );
4944 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4945 } else if (!(attr & DBI_DATETIME_TIME)) {
4946 localtime_r( &_tmp_dt, &gmdt );
4947 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4949 localtime_r( &_tmp_dt, &gmdt );
4950 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4953 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
4956 case DBI_TYPE_BINARY :
4957 osrfLogError( OSRF_LOG_MARK,
4958 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4966 // Interpret a string as true or false
4967 static int str_is_true( const char* str ) {
4968 if( NULL == str || strcasecmp( str, "true" ) )
4974 // Interpret a jsonObject as true or false
4975 static int obj_is_true( const jsonObject* obj ) {
4978 else switch( obj->type )
4986 if( strcasecmp( obj->value.s, "true" ) )
4990 case JSON_NUMBER : // Support 1/0 for perl's sake
4991 if( jsonObjectGetNumber( obj ) == 1.0 )
5000 // Translate a numeric code into a text string identifying a type of
5001 // jsonObject. To be used for building error messages.
5002 static const char* json_type( int code ) {
5008 return "JSON_ARRAY";
5010 return "JSON_STRING";
5012 return "JSON_NUMBER";
5018 return "(unrecognized)";
5022 // Extract the "primitive" attribute from an IDL field definition.
5023 // If we haven't initialized the app, then we must be running in
5024 // some kind of testbed. In that case, default to "string".
5025 static const char* get_primitive( osrfHash* field ) {
5026 const char* s = osrfHashGet( field, "primitive" );
5028 if( child_initialized )
5031 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5033 osrfHashGet( field, "name" )
5041 // Extract the "datatype" attribute from an IDL field definition.
5042 // If we haven't initialized the app, then we must be running in
5043 // some kind of testbed. In that case, default to to NUMERIC,
5044 // since we look at the datatype only for numbers.
5045 static const char* get_datatype( osrfHash* field ) {
5046 const char* s = osrfHashGet( field, "datatype" );
5048 if( child_initialized )
5051 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5053 osrfHashGet( field, "name" )
5062 If the input string is potentially a valid SQL identifier, return 1.
5065 Purpose: to prevent certain kinds of SQL injection. To that end we
5066 don't necessarily need to follow all the rules exactly, such as requiring
5067 that the first character not be a digit.
5069 We allow leading and trailing white space. In between, we do not allow
5070 punctuation (except for underscores and dollar signs), control
5071 characters, or embedded white space.
5073 More pedantically we should allow quoted identifiers containing arbitrary
5074 characters, but for the foreseeable future such quoted identifiers are not
5075 likely to be an issue.
5077 static int is_identifier( const char* s) {
5081 // Skip leading white space
5082 while( isspace( (unsigned char) *s ) )
5086 return 0; // Nothing but white space? Not okay.
5088 // Check each character until we reach white space or
5089 // end-of-string. Letters, digits, underscores, and
5090 // dollar signs are okay. With the exception of periods
5091 // (as in schema.identifier), control characters and other
5092 // punctuation characters are not okay. Anything else
5093 // is okay -- it could for example be part of a multibyte
5094 // UTF8 character such as a letter with diacritical marks,
5095 // and those are allowed.
5097 if( isalnum( (unsigned char) *s )
5101 ; // Fine; keep going
5102 else if( ispunct( (unsigned char) *s )
5103 || iscntrl( (unsigned char) *s ) )
5106 } while( *s && ! isspace( (unsigned char) *s ) );
5108 // If we found any white space in the above loop,
5109 // the rest had better be all white space.
5111 while( isspace( (unsigned char) *s ) )
5115 return 0; // White space was embedded within non-white space
5121 Determine whether to accept a character string as a comparison operator.
5122 Return 1 if it's good, or 0 if it's bad.
5124 We don't validate it for real. We just make sure that it doesn't contain
5125 any semicolons or white space (with a special exception for the
5126 "SIMILAR TO" operator). The idea is to block certain kinds of SQL
5127 injection. If it has no semicolons or white space but it's still not a
5128 valid operator, then the database will complain.
5130 Another approach would be to compare the string against a short list of
5131 approved operators. We don't do that because we want to allow custom
5132 operators like ">100*", which would be difficult or impossible to
5133 express otherwise in a JSON query.
5135 static int is_good_operator( const char* op ) {
5136 if( !op ) return 0; // Sanity check
5140 if( isspace( (unsigned char) *s ) ) {
5141 // Special exception for SIMILAR TO. Someday we might make
5142 // exceptions for IS DISTINCT FROM and IS NOT DISTINCT FROM.
5143 if( !strcasecmp( op, "similar to" ) )
5148 else if( ';' == *s )