2 #include "opensrf/osrf_application.h"
3 #include "opensrf/osrf_settings.h"
4 #include "opensrf/osrf_message.h"
5 #include "opensrf/utils.h"
6 #include "opensrf/osrf_json.h"
7 #include "opensrf/log.h"
8 #include "openils/oils_utils.h"
17 # define MODULENAME "open-ils.reporter-store"
20 # define MODULENAME "open-ils.pcrud"
22 # define MODULENAME "open-ils.cstore"
27 #define DISABLE_I18N 2
28 #define SELECT_DISTINCT 1
32 int osrfAppChildInit();
33 int osrfAppInitialize();
34 void osrfAppChildExit();
36 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
38 int beginTransaction ( osrfMethodContext* );
39 int commitTransaction ( osrfMethodContext* );
40 int rollbackTransaction ( osrfMethodContext* );
42 int setSavepoint ( osrfMethodContext* );
43 int releaseSavepoint ( osrfMethodContext* );
44 int rollbackSavepoint ( osrfMethodContext* );
46 int doJSONSearch ( osrfMethodContext* );
48 int dispatchCRUDMethod ( osrfMethodContext* );
49 static jsonObject* doCreate ( osrfMethodContext*, int* );
50 static jsonObject* doRetrieve ( osrfMethodContext*, int* );
51 static jsonObject* doUpdate ( osrfMethodContext*, int* );
52 static jsonObject* doDelete ( osrfMethodContext*, int* );
53 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
54 jsonObject* where_hash, jsonObject* query_hash, int* err );
55 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
56 static jsonObject* oilsMakeJSONFromResult( dbi_result );
58 static char* searchSimplePredicate ( const char* op, const char* class,
59 osrfHash* field, const jsonObject* node );
60 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
61 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject*);
62 static char* searchFieldTransformPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
63 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
64 static char* searchINPredicate ( const char*, osrfHash*,
65 jsonObject*, const char*, osrfMethodContext* );
66 static char* searchPredicate ( const char*, osrfHash*, jsonObject*, osrfMethodContext* );
67 static char* searchJOIN ( const jsonObject*, osrfHash* );
68 static char* searchWHERE ( const jsonObject*, osrfHash*, int, osrfMethodContext* );
69 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
71 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
73 void userDataFree( void* );
74 static void sessionDataFree( char*, void* );
75 static char* getSourceDefinition( osrfHash* );
76 static int str_is_true( const char* str );
77 static int obj_is_true( const jsonObject* obj );
78 static const char* json_type( int code );
79 static const char* get_primitive( osrfHash* field );
80 static const char* get_datatype( osrfHash* field );
81 static int is_identifier( const char* s);
82 static int is_good_operator( const char* op );
85 static jsonObject* verifyUserPCRUD( osrfMethodContext* );
86 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
87 static jsonObject* single_hash( const char* key, const char* value );
90 static int child_initialized = 0; /* boolean */
92 static dbi_conn writehandle; /* our MASTER db connection */
93 static dbi_conn dbhandle; /* our CURRENT db connection */
94 //static osrfHash * readHandles;
95 static jsonObject* jsonNULL = NULL; //
96 static int max_flesh_depth = 100;
98 /* called when this process is about to exit */
99 void osrfAppChildExit() {
100 osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
103 if (writehandle == dbhandle) same = 1;
105 dbi_conn_query(writehandle, "ROLLBACK;");
106 dbi_conn_close(writehandle);
109 if (dbhandle && !same)
110 dbi_conn_close(dbhandle);
112 // XXX add cleanup of readHandles whenever that gets used
117 int osrfAppInitialize() {
119 osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
120 osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
122 if (!oilsIDLInit( osrf_settings_host_value("/IDL") )) return 1; /* return non-zero to indicate error */
124 growing_buffer* method_name = buffer_init(64);
126 // Generic search thingy
127 buffer_add(method_name, MODULENAME);
128 buffer_add(method_name, ".json_query");
129 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
130 "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
133 // first we register all the transaction and savepoint methods
134 buffer_reset(method_name);
135 OSRF_BUFFER_ADD(method_name, MODULENAME);
136 OSRF_BUFFER_ADD(method_name, ".transaction.begin");
137 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
138 "beginTransaction", "", 0, 0 );
140 buffer_reset(method_name);
141 OSRF_BUFFER_ADD(method_name, MODULENAME);
142 OSRF_BUFFER_ADD(method_name, ".transaction.commit");
143 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
144 "commitTransaction", "", 0, 0 );
146 buffer_reset(method_name);
147 OSRF_BUFFER_ADD(method_name, MODULENAME);
148 OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
149 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
150 "rollbackTransaction", "", 0, 0 );
152 buffer_reset(method_name);
153 OSRF_BUFFER_ADD(method_name, MODULENAME);
154 OSRF_BUFFER_ADD(method_name, ".savepoint.set");
155 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
156 "setSavepoint", "", 1, 0 );
158 buffer_reset(method_name);
159 OSRF_BUFFER_ADD(method_name, MODULENAME);
160 OSRF_BUFFER_ADD(method_name, ".savepoint.release");
161 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
162 "releaseSavepoint", "", 1, 0 );
164 buffer_reset(method_name);
165 OSRF_BUFFER_ADD(method_name, MODULENAME);
166 OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
167 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
168 "rollbackSavepoint", "", 1, 0 );
170 buffer_free(method_name);
172 static const char* global_method[] = {
180 const int global_method_count
181 = sizeof( global_method ) / sizeof ( global_method[0] );
185 osrfStringArray* classes = osrfHashKeys( oilsIDL() );
186 osrfLogDebug(OSRF_LOG_MARK, "%d classes loaded", classes->size );
187 osrfLogDebug(OSRF_LOG_MARK,
188 "At least %d methods will be generated", classes->size * global_method_count);
190 // For each class in IDL...
191 while ( (classname = osrfStringArrayGetString(classes, c_index++)) ) {
192 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
194 osrfHash* idlClass = osrfHashGet(oilsIDL(), classname);
196 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), MODULENAME )) {
197 osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on", MODULENAME, classname);
201 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
202 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
206 // Look up some other attributes of the current class
207 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
208 if( !idlClass_fieldmapper ) {
209 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL", classname );
214 osrfHash* idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
215 if (!idlClass_permacrud) {
216 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no permacrud in IDL", classname );
220 const char* readonly = osrfHashGet(idlClass, "readonly");
223 for( i = 0; i < global_method_count; ++i ) { // for each global method
224 const char* method_type = global_method[ i ];
225 osrfLogDebug(OSRF_LOG_MARK,
226 "Using files to build %s class methods for %s", method_type, classname);
229 const char* tmp_method = method_type;
230 if ( *tmp_method == 'i' || *tmp_method == 's') {
231 tmp_method = "retrieve";
233 if (!osrfHashGet( idlClass_permacrud, tmp_method )) continue;
236 if ( str_is_true( readonly ) &&
237 ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd')
240 osrfHash* method_meta = osrfNewHash();
241 osrfHashSet(method_meta, idlClass, "class");
243 method_name = buffer_init(64);
245 buffer_fadd(method_name, "%s.%s.%s", MODULENAME, method_type, classname);
249 char* _fm = strdup( idlClass_fieldmapper );
250 part = strtok_r(_fm, ":", &st_tmp);
252 buffer_fadd(method_name, "%s.direct.%s", MODULENAME, part);
254 while ((part = strtok_r(NULL, ":", &st_tmp))) {
255 OSRF_BUFFER_ADD_CHAR(method_name, '.');
256 OSRF_BUFFER_ADD(method_name, part);
258 OSRF_BUFFER_ADD_CHAR(method_name, '.');
259 OSRF_BUFFER_ADD(method_name, method_type);
263 char* method = buffer_release(method_name);
265 osrfHashSet( method_meta, method, "methodname" );
266 osrfHashSet( method_meta, strdup(method_type), "methodtype" );
269 if (*method_type == 'i' || *method_type == 's') {
270 flags = flags | OSRF_METHOD_STREAMING;
273 osrfAppRegisterExtendedMethod(
276 "dispatchCRUDMethod",
284 } // end for each global method
285 } // end for each class in IDL
287 osrfStringArrayFree( classes );
292 static char* getSourceDefinition( osrfHash* class ) {
294 char* tabledef = osrfHashGet(class, "tablename");
297 tabledef = strdup(tabledef);
299 tabledef = osrfHashGet(class, "source_definition");
301 growing_buffer* tablebuf = buffer_init(128);
302 buffer_fadd( tablebuf, "(%s)", tabledef );
303 tabledef = buffer_release(tablebuf);
305 const char* classname = osrfHashGet( class, "classname" );
310 "%s ERROR No tablename or source_definition for class \"%s\"",
321 * Connects to the database
323 int osrfAppChildInit() {
325 osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
326 dbi_initialize(NULL);
327 osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
329 char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
330 char* user = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
331 char* host = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
332 char* port = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
333 char* db = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
334 char* pw = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
335 char* md = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion", MODULENAME);
337 osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
338 writehandle = dbi_conn_new(driver);
341 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
344 osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
346 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
347 "port=%s, user=%s, pw=%s, db=%s", MODULENAME, host, port, user, pw, db );
349 if(host) dbi_conn_set_option(writehandle, "host", host );
350 if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
351 if(user) dbi_conn_set_option(writehandle, "username", user);
352 if(pw) dbi_conn_set_option(writehandle, "password", pw );
353 if(db) dbi_conn_set_option(writehandle, "dbname", db );
355 if(md) max_flesh_depth = atoi(md);
356 if(max_flesh_depth < 0) max_flesh_depth = 1;
357 if(max_flesh_depth > 1000) max_flesh_depth = 1000;
366 if (dbi_conn_connect(writehandle) < 0) {
368 if (dbi_conn_connect(writehandle) < 0) {
369 dbi_conn_error(writehandle, &err);
370 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
375 osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
380 osrfStringArray* classes = osrfHashKeys( oilsIDL() );
382 while ( (classname = osrfStringArrayGetString(classes, i++)) ) {
383 osrfHash* class = osrfHashGet( oilsIDL(), classname );
384 osrfHash* fields = osrfHashGet( class, "fields" );
386 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
387 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
391 char* tabledef = getSourceDefinition(class);
393 tabledef = strdup( "(null)" );
395 growing_buffer* sql_buf = buffer_init(32);
396 buffer_fadd( sql_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
400 char* sql = buffer_release(sql_buf);
401 osrfLogDebug(OSRF_LOG_MARK, "%s Investigatory SQL = %s", MODULENAME, sql);
403 dbi_result result = dbi_conn_query(writehandle, sql);
409 const char* columnName;
411 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
413 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
415 /* fetch the fieldmapper index */
416 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
418 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", (char*)columnName);
420 /* determine the field type and storage attributes */
421 type = dbi_result_get_field_type(result, columnName);
425 case DBI_TYPE_INTEGER : {
427 if ( !osrfHashGet(_f, "primitive") )
428 osrfHashSet(_f,"number", "primitive");
430 int attr = dbi_result_get_field_attribs(result, columnName);
431 if( attr & DBI_INTEGER_SIZE8 )
432 osrfHashSet(_f,"INT8", "datatype");
434 osrfHashSet(_f,"INT", "datatype");
437 case DBI_TYPE_DECIMAL :
438 if ( !osrfHashGet(_f, "primitive") )
439 osrfHashSet(_f,"number", "primitive");
441 osrfHashSet(_f,"NUMERIC", "datatype");
444 case DBI_TYPE_STRING :
445 if ( !osrfHashGet(_f, "primitive") )
446 osrfHashSet(_f,"string", "primitive");
447 osrfHashSet(_f,"TEXT", "datatype");
450 case DBI_TYPE_DATETIME :
451 if ( !osrfHashGet(_f, "primitive") )
452 osrfHashSet(_f,"string", "primitive");
454 osrfHashSet(_f,"TIMESTAMP", "datatype");
457 case DBI_TYPE_BINARY :
458 if ( !osrfHashGet(_f, "primitive") )
459 osrfHashSet(_f,"string", "primitive");
461 osrfHashSet(_f,"BYTEA", "datatype");
466 "Setting [%s] to primitive [%s] and datatype [%s]...",
468 osrfHashGet(_f, "primitive"),
469 osrfHashGet(_f, "datatype")
473 dbi_result_free(result);
475 osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", (char*)classname);
479 osrfStringArrayFree(classes);
481 child_initialized = 1;
486 This function is a sleazy hack intended *only* for testing and
487 debugging. Any real server process should initialize the
488 database connection by calling osrfAppChildInit().
490 void set_cstore_dbi_conn( dbi_conn conn ) {
491 dbhandle = writehandle = conn;
494 void userDataFree( void* blob ) {
495 osrfHashFree( (osrfHash*)blob );
499 static void sessionDataFree( char* key, void* item ) {
500 if (!(strcmp(key,"xact_id"))) {
502 dbi_conn_query(writehandle, "ROLLBACK;");
509 int beginTransaction ( osrfMethodContext* ctx ) {
510 if(osrfMethodVerifyContext( ctx )) {
511 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
516 jsonObject* user = verifyUserPCRUD( ctx );
517 if (!user) return -1;
518 jsonObjectFree(user);
521 dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
523 osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
524 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error starting transaction" );
527 jsonObject* ret = jsonNewObject(ctx->session->session_id);
528 osrfAppRespondComplete( ctx, ret );
531 if (!ctx->session->userData) {
532 ctx->session->userData = osrfNewHash();
533 osrfHashSetCallback((osrfHash*)ctx->session->userData, &sessionDataFree);
536 osrfHashSet( (osrfHash*)ctx->session->userData, strdup( ctx->session->session_id ), "xact_id" );
537 ctx->session->userDataFree = &userDataFree;
543 int setSavepoint ( osrfMethodContext* ctx ) {
544 if(osrfMethodVerifyContext( ctx )) {
545 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
552 jsonObject* user = verifyUserPCRUD( ctx );
553 if (!user) return -1;
554 jsonObjectFree(user);
557 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
558 osrfAppSessionStatus(
560 OSRF_STATUS_INTERNALSERVERERROR,
561 "osrfMethodException",
563 "No active transaction -- required for savepoints"
568 const char* spName = jsonObjectGetString(jsonObjectGetIndex(ctx->params, spNamePos));
570 dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
574 "%s: Error creating savepoint %s in transaction %s",
577 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
579 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
580 "osrfMethodException", ctx->request, "Error creating savepoint" );
583 jsonObject* ret = jsonNewObject(spName);
584 osrfAppRespondComplete( ctx, ret );
590 int releaseSavepoint ( osrfMethodContext* ctx ) {
591 if(osrfMethodVerifyContext( ctx )) {
592 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
599 jsonObject* user = verifyUserPCRUD( ctx );
600 if (!user) return -1;
601 jsonObjectFree(user);
604 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
605 osrfAppSessionStatus(
607 OSRF_STATUS_INTERNALSERVERERROR,
608 "osrfMethodException",
610 "No active transaction -- required for savepoints"
615 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
617 dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
621 "%s: Error releasing savepoint %s in transaction %s",
624 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
626 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
627 "osrfMethodException", ctx->request, "Error releasing savepoint" );
630 jsonObject* ret = jsonNewObject(spName);
631 osrfAppRespondComplete( ctx, ret );
637 int rollbackSavepoint ( osrfMethodContext* ctx ) {
638 if(osrfMethodVerifyContext( ctx )) {
639 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
646 jsonObject* user = verifyUserPCRUD( ctx );
647 if (!user) return -1;
648 jsonObjectFree(user);
651 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
652 osrfAppSessionStatus(
654 OSRF_STATUS_INTERNALSERVERERROR,
655 "osrfMethodException",
657 "No active transaction -- required for savepoints"
662 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
664 dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
668 "%s: Error rolling back savepoint %s in transaction %s",
671 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
673 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
674 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
677 jsonObject* ret = jsonNewObject(spName);
678 osrfAppRespondComplete( ctx, ret );
684 int commitTransaction ( osrfMethodContext* ctx ) {
685 if(osrfMethodVerifyContext( ctx )) {
686 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
691 jsonObject* user = verifyUserPCRUD( ctx );
692 if (!user) return -1;
693 jsonObjectFree(user);
696 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
697 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to commit" );
701 dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
703 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
704 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error committing transaction" );
707 osrfHashRemove(ctx->session->userData, "xact_id");
708 jsonObject* ret = jsonNewObject(ctx->session->session_id);
709 osrfAppRespondComplete( ctx, ret );
715 int rollbackTransaction ( osrfMethodContext* ctx ) {
716 if(osrfMethodVerifyContext( ctx )) {
717 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
722 jsonObject* user = verifyUserPCRUD( ctx );
723 if (!user) return -1;
724 jsonObjectFree(user);
727 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
728 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to roll back" );
732 dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
734 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
735 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error rolling back transaction" );
738 osrfHashRemove(ctx->session->userData, "xact_id");
739 jsonObject* ret = jsonNewObject(ctx->session->session_id);
740 osrfAppRespondComplete( ctx, ret );
746 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
747 if(osrfMethodVerifyContext( ctx )) {
748 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
752 osrfHash* meta = (osrfHash*) ctx->method->userData;
753 osrfHash* class_obj = osrfHashGet( meta, "class" );
757 const char* methodtype = osrfHashGet(meta, "methodtype");
758 jsonObject * obj = NULL;
760 if (!strcmp(methodtype, "create")) {
761 obj = doCreate(ctx, &err);
762 osrfAppRespondComplete( ctx, obj );
764 else if (!strcmp(methodtype, "retrieve")) {
765 obj = doRetrieve(ctx, &err);
766 osrfAppRespondComplete( ctx, obj );
768 else if (!strcmp(methodtype, "update")) {
769 obj = doUpdate(ctx, &err);
770 osrfAppRespondComplete( ctx, obj );
772 else if (!strcmp(methodtype, "delete")) {
773 obj = doDelete(ctx, &err);
774 osrfAppRespondComplete( ctx, obj );
776 else if (!strcmp(methodtype, "search")) {
778 jsonObject* where_clause;
779 jsonObject* rest_of_query;
782 where_clause = jsonObjectGetIndex( ctx->params, 1 );
783 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
785 where_clause = jsonObjectGetIndex( ctx->params, 0 );
786 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
789 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
794 jsonIterator* itr = jsonNewIterator( obj );
795 while ((cur = jsonIteratorNext( itr ))) {
797 if(!verifyObjectPCRUD(ctx, cur)) continue;
799 osrfAppRespond( ctx, cur );
801 jsonIteratorFree(itr);
802 osrfAppRespondComplete( ctx, NULL );
804 } else if (!strcmp(methodtype, "id_list")) {
806 jsonObject* where_clause;
807 jsonObject* rest_of_query;
809 // We use the where clause without change. But we need
810 // to massage the rest of the query, so we work with a copy
811 // of it instead of modifying the original.
813 where_clause = jsonObjectGetIndex( ctx->params, 1 );
814 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
816 where_clause = jsonObjectGetIndex( ctx->params, 0 );
817 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
820 if ( rest_of_query ) {
821 jsonObjectRemoveKey( rest_of_query, "select" );
822 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
823 jsonObjectRemoveKey( rest_of_query, "flesh" );
824 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
826 rest_of_query = jsonNewObjectType( JSON_HASH );
829 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
835 "{ \"%s\":[\"%s\"] }",
836 osrfHashGet( class_obj, "classname" ),
837 osrfHashGet( class_obj, "primarykey" )
841 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
843 jsonObjectFree( rest_of_query );
847 jsonIterator* itr = jsonNewIterator( obj );
848 while ((cur = jsonIteratorNext( itr ))) {
850 if(!verifyObjectPCRUD(ctx, cur)) continue;
854 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
857 jsonIteratorFree(itr);
858 osrfAppRespondComplete( ctx, NULL );
861 osrfAppRespondComplete( ctx, obj );
869 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
872 osrfHash* meta = (osrfHash*) ctx->method->userData;
873 osrfHash* class = osrfHashGet( meta, "class" );
875 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
877 growing_buffer* msg = buffer_init(128);
880 "%s: %s method for type %s was passed a %s",
882 osrfHashGet(meta, "methodtype"),
883 osrfHashGet(class, "classname"),
887 char* m = buffer_release(msg);
888 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException", ctx->request, m );
896 ret = verifyObjectPCRUD( ctx, param );
904 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
905 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
906 jsonObject* auth_object = jsonNewObject(auth);
907 jsonObject* user = oilsUtilsQuickReq("open-ils.auth","open-ils.auth.session.retrieve", auth_object);
908 jsonObjectFree(auth_object);
910 if (!user->classname || strcmp(user->classname, "au")) {
912 growing_buffer* msg = buffer_init(128);
915 "%s: permacrud received a bad auth token: %s",
920 char* m = buffer_release(msg);
921 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException", ctx->request, m );
924 jsonObjectFree(user);
932 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
934 dbhandle = writehandle;
936 osrfHash* meta = (osrfHash*) ctx->method->userData;
937 osrfHash* class = osrfHashGet( meta, "class" );
938 char* method_type = strdup( osrfHashGet(meta, "methodtype") );
941 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
943 method_type = strdup("retrieve"); // search and id_list are equivelant to retrieve for this
944 } else if ( *method_type == 'u' || *method_type == 'd' ) {
945 fetch = 1; // MUST go to the db for the object for update and delete
948 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
952 // No permacrud for this method type on this class
954 growing_buffer* msg = buffer_init(128);
957 "%s: %s on class %s has no permacrud IDL entry",
959 osrfHashGet(meta, "methodtype"),
960 osrfHashGet(class, "classname")
963 char* m = buffer_release(msg);
964 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN, "osrfMethodException", ctx->request, m );
971 jsonObject* user = verifyUserPCRUD( ctx );
974 int userid = atoi( oilsFMGetString( user, "id" ) );
975 jsonObjectFree(user);
977 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
978 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
979 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
981 osrfStringArray* context_org_array = osrfNewStringArray(1);
984 char* pkey_value = NULL;
985 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
986 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions required, fetching top of the org tree" );
988 // check for perm at top of org tree
989 jsonObject* _tmp_params = jsonParseString("{\"parent_ou\":null}");
990 jsonObject* _list = doFieldmapperSearch(ctx, osrfHashGet( oilsIDL(), "aou" ),
991 _tmp_params, NULL, &err);
993 jsonObject* _tree_top = jsonObjectGetIndex(_list, 0);
996 jsonObjectFree(_tmp_params);
997 jsonObjectFree(_list);
999 growing_buffer* msg = buffer_init(128);
1000 OSRF_BUFFER_ADD( msg, MODULENAME );
1001 OSRF_BUFFER_ADD( msg,
1002 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1004 char* m = buffer_release(msg);
1005 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1011 osrfStringArrayAdd( context_org_array, oilsFMGetString( _tree_top, "id" ) );
1012 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", osrfStringArrayGetString(context_org_array, 0) );
1014 jsonObjectFree(_tmp_params);
1015 jsonObjectFree(_list);
1018 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, fetching context org ids" );
1019 char* pkey = osrfHashGet(class, "primarykey");
1020 jsonObject *param = NULL;
1022 if (obj->classname) {
1023 pkey_value = oilsFMGetString( obj, pkey );
1024 if (!fetch) param = jsonObjectClone(obj);
1025 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s", pkey_value );
1027 pkey_value = jsonObjectToSimpleString( obj );
1029 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value of %s and retrieving from the database", pkey_value );
1033 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1034 jsonObject* _list = doFieldmapperSearch(
1035 ctx, class, _tmp_params, NULL, &err );
1037 param = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1039 jsonObjectFree(_tmp_params);
1040 jsonObjectFree(_list);
1044 osrfLogDebug( OSRF_LOG_MARK, "Object not found in the database with primary key %s of %s", pkey, pkey_value );
1046 growing_buffer* msg = buffer_init(128);
1049 "%s: no object found with primary key %s of %s",
1055 char* m = buffer_release(msg);
1056 osrfAppSessionStatus(
1058 OSRF_STATUS_INTERNALSERVERERROR,
1059 "osrfMethodException",
1065 if (pkey_value) free(pkey_value);
1070 if (local_context->size > 0) {
1071 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified", local_context->size);
1073 char* lcontext = NULL;
1074 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1075 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1078 "adding class-local field %s (value: %s) to the context org list",
1080 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1085 osrfStringArray* class_list;
1087 if (foreign_context) {
1088 class_list = osrfHashKeys( foreign_context );
1089 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_list->size);
1091 if (class_list->size > 0) {
1094 char* class_name = NULL;
1095 while ( (class_name = osrfStringArrayGetString(class_list, i++)) ) {
1096 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1100 "%d foreign context fields(s) specified for class %s",
1101 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1105 char* foreign_pkey = osrfHashGet(fcontext, "field");
1106 char* foreign_pkey_value = oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1108 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1110 jsonObject* _list = doFieldmapperSearch(
1111 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1113 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1114 jsonObjectFree(_tmp_params);
1115 jsonObjectFree(_list);
1117 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1119 if (_fparam && jump_list) {
1122 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1123 free(foreign_pkey_value);
1125 osrfHash* foreign_link_hash = oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1127 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1128 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1130 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1132 _list = doFieldmapperSearch(
1134 osrfHashGet( oilsIDL(), osrfHashGet( foreign_link_hash, "class" ) ),
1140 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1141 jsonObjectFree(_tmp_params);
1142 jsonObjectFree(_list);
1149 growing_buffer* msg = buffer_init(128);
1152 "%s: no object found with primary key %s of %s",
1158 char* m = buffer_release(msg);
1159 osrfAppSessionStatus(
1161 OSRF_STATUS_INTERNALSERVERERROR,
1162 "osrfMethodException",
1168 osrfStringArrayFree(class_list);
1169 free(foreign_pkey_value);
1170 jsonObjectFree(param);
1175 free(foreign_pkey_value);
1178 char* foreign_field = NULL;
1179 while ( (foreign_field = osrfStringArrayGetString(osrfHashGet(fcontext,"context"), j++)) ) {
1180 osrfStringArrayAdd( context_org_array, oilsFMGetString( _fparam, foreign_field ) );
1183 "adding foreign class %s field %s (value: %s) to the context org list",
1186 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1190 jsonObjectFree(_fparam);
1193 osrfStringArrayFree(class_list);
1197 jsonObjectFree(param);
1200 char* context_org = NULL;
1204 if (permission->size == 0) {
1205 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1210 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1212 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1218 "Checking object permission [%s] for user %d on object %s (class %s) at org %d",
1222 osrfHashGet(class, "classname"),
1226 result = dbi_conn_queryf(
1228 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1231 osrfHashGet(class, "classname"),
1239 "Recieved a result for object permission [%s] for user %d on object %s (class %s) at org %d",
1243 osrfHashGet(class, "classname"),
1247 if (dbi_result_first_row(result)) {
1248 jsonObject* return_val = oilsMakeJSONFromResult( result );
1249 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1253 "Status of object permission [%s] for user %d on object %s (class %s) at org %d is %s",
1257 osrfHashGet(class, "classname"),
1262 if ( *has_perm == 't' ) OK = 1;
1263 jsonObjectFree(return_val);
1266 dbi_result_free(result);
1271 osrfLogDebug( OSRF_LOG_MARK, "Checking non-object permission [%s] for user %d at org %d", perm, userid, atoi(context_org) );
1272 result = dbi_conn_queryf(
1274 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1281 osrfLogDebug( OSRF_LOG_MARK, "Received a result for permission [%s] for user %d at org %d",
1282 perm, userid, atoi(context_org) );
1283 if ( dbi_result_first_row(result) ) {
1284 jsonObject* return_val = oilsMakeJSONFromResult( result );
1285 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1286 osrfLogDebug( OSRF_LOG_MARK, "Status of permission [%s] for user %d at org %d is [%s]",
1287 perm, userid, atoi(context_org), has_perm );
1288 if ( *has_perm == 't' ) OK = 1;
1289 jsonObjectFree(return_val);
1292 dbi_result_free(result);
1300 if (pkey_value) free(pkey_value);
1301 osrfStringArrayFree(context_org_array);
1307 Utility function: create a JSON_HASH with a single key/value pair.
1308 This function is equivalent to:
1310 jsonParseStringFmt( "{\"%s\":\"%s\"}", key, value )
1312 ...but faster because it doesn't create and parse a JSON string.
1314 static jsonObject* single_hash( const char* key, const char* value ) {
1316 if( ! key ) key = "";
1317 if( ! value ) value = "";
1319 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1320 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1326 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1328 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1330 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1331 jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1333 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1334 jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1337 if (!verifyObjectClass(ctx, target)) {
1342 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1344 char* trans_id = NULL;
1345 if( ctx->session && ctx->session->userData )
1346 trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
1349 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1351 osrfAppSessionStatus(
1353 OSRF_STATUS_BADREQUEST,
1354 "osrfMethodException",
1356 "No active transaction -- required for CREATE"
1362 // The following test is harmless but redundant. If a class is
1363 // readonly, we don't register a create method for it.
1364 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1365 osrfAppSessionStatus(
1367 OSRF_STATUS_BADREQUEST,
1368 "osrfMethodException",
1370 "Cannot INSERT readonly class"
1376 // Set the last_xact_id
1377 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1379 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
1380 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1383 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1385 dbhandle = writehandle;
1387 osrfHash* fields = osrfHashGet(meta, "fields");
1388 char* pkey = osrfHashGet(meta, "primarykey");
1389 char* seq = osrfHashGet(meta, "sequence");
1391 growing_buffer* table_buf = buffer_init(128);
1392 growing_buffer* col_buf = buffer_init(128);
1393 growing_buffer* val_buf = buffer_init(128);
1395 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1396 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1397 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1398 buffer_add(val_buf,"VALUES (");
1404 osrfStringArray* field_list = osrfHashKeys( fields );
1405 while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
1407 osrfHash* field = osrfHashGet( fields, field_name );
1409 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1412 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1415 if (field_object && field_object->classname) {
1416 value = oilsFMGetString(
1418 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1421 value = jsonObjectToSimpleString( field_object );
1428 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1429 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1432 buffer_add(col_buf, field_name);
1434 if (!field_object || field_object->type == JSON_NULL) {
1435 buffer_add( val_buf, "DEFAULT" );
1437 } else if ( !strcmp(get_primitive( field ), "number") ) {
1438 const char* numtype = get_datatype( field );
1439 if ( !strcmp( numtype, "INT8") ) {
1440 buffer_fadd( val_buf, "%lld", atoll(value) );
1442 } else if ( !strcmp( numtype, "INT") ) {
1443 buffer_fadd( val_buf, "%d", atoi(value) );
1445 } else if ( !strcmp( numtype, "NUMERIC") ) {
1446 buffer_fadd( val_buf, "%f", atof(value) );
1449 if ( dbi_conn_quote_string(writehandle, &value) ) {
1450 OSRF_BUFFER_ADD( val_buf, value );
1453 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1454 osrfAppSessionStatus(
1456 OSRF_STATUS_INTERNALSERVERERROR,
1457 "osrfMethodException",
1459 "Error quoting string -- please see the error log for more details"
1462 buffer_free(table_buf);
1463 buffer_free(col_buf);
1464 buffer_free(val_buf);
1475 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1476 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1478 char* table_str = buffer_release(table_buf);
1479 char* col_str = buffer_release(col_buf);
1480 char* val_str = buffer_release(val_buf);
1481 growing_buffer* sql = buffer_init(128);
1482 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1487 char* query = buffer_release(sql);
1489 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1492 dbi_result result = dbi_conn_query(writehandle, query);
1494 jsonObject* obj = NULL;
1497 obj = jsonNewObject(NULL);
1500 "%s ERROR inserting %s object using query [%s]",
1502 osrfHashGet(meta, "fieldmapper"),
1505 osrfAppSessionStatus(
1507 OSRF_STATUS_INTERNALSERVERERROR,
1508 "osrfMethodException",
1510 "INSERT error -- please see the error log for more details"
1515 char* id = oilsFMGetString(target, pkey);
1517 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1518 growing_buffer* _id = buffer_init(10);
1519 buffer_fadd(_id, "%lld", new_id);
1520 id = buffer_release(_id);
1523 // Find quietness specification, if present
1524 const char* quiet_str = NULL;
1526 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1528 quiet_str = jsonObjectGetString( quiet_obj );
1531 if( str_is_true( quiet_str ) ) { // if quietness is specified
1532 obj = jsonNewObject(id);
1536 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1537 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1539 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1541 jsonObjectFree( where_clause );
1546 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1549 jsonObjectFree( list );
1562 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1572 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1574 const char* id = jsonObjectGetString(jsonObjectGetIndex(ctx->params, id_pos));
1575 jsonObject* order_hash = jsonObjectGetIndex(ctx->params, order_pos);
1579 "%s retrieving %s object with primary key value of %s",
1581 osrfHashGet(meta, "fieldmapper"),
1585 jsonObject* key_param = jsonNewObjectType( JSON_HASH );
1588 osrfHashGet( meta, "primarykey" ),
1589 jsonObjectClone( jsonObjectGetIndex(ctx->params, id_pos) )
1592 jsonObject* list = doFieldmapperSearch( ctx, meta, key_param, order_hash, err );
1595 jsonObjectFree( key_param );
1599 jsonObject* obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1601 jsonObjectFree( list );
1602 jsonObjectFree( key_param );
1605 if(!verifyObjectPCRUD(ctx, obj)) {
1606 jsonObjectFree(obj);
1609 growing_buffer* msg = buffer_init(128);
1610 OSRF_BUFFER_ADD( msg, MODULENAME );
1611 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1613 char* m = buffer_release(msg);
1614 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1625 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1626 growing_buffer* val_buf = buffer_init(32);
1627 const char* numtype = get_datatype( field );
1629 if ( !strncmp( numtype, "INT", 3 ) ) {
1630 if (value->type == JSON_NUMBER)
1631 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1632 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1634 //const char* val_str = jsonObjectGetString( value );
1635 //buffer_fadd( val_buf, "%ld", atol(val_str) );
1636 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1639 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
1640 if (value->type == JSON_NUMBER)
1641 //buffer_fadd( val_buf, "%f", jsonObjectGetNumber(value) );
1642 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1644 //const char* val_str = jsonObjectGetString( value );
1645 //buffer_fadd( val_buf, "%f", atof(val_str) );
1646 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1650 // Presumably this was really intended ot be a string, so quote it
1651 char* str = jsonObjectToSimpleString( value );
1652 if ( dbi_conn_quote_string(dbhandle, &str) ) {
1653 OSRF_BUFFER_ADD( val_buf, str );
1656 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
1658 buffer_free(val_buf);
1663 return buffer_release(val_buf);
1666 static char* searchINPredicate (const char* class, osrfHash* field,
1667 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1668 growing_buffer* sql_buf = buffer_init(32);
1674 osrfHashGet(field, "name")
1678 buffer_add(sql_buf, "IN (");
1679 } else if (!(strcasecmp(op,"not in"))) {
1680 buffer_add(sql_buf, "NOT IN (");
1682 buffer_add(sql_buf, "IN (");
1685 if (node->type == JSON_HASH) {
1686 // subquery predicate
1687 char* subpred = SELECT(
1689 jsonObjectGetKey( node, "select" ),
1690 jsonObjectGetKey( node, "from" ),
1691 jsonObjectGetKey( node, "where" ),
1692 jsonObjectGetKey( node, "having" ),
1693 jsonObjectGetKey( node, "order_by" ),
1694 jsonObjectGetKey( node, "limit" ),
1695 jsonObjectGetKey( node, "offset" ),
1700 buffer_add(sql_buf, subpred);
1703 buffer_free( sql_buf );
1707 } else if (node->type == JSON_ARRAY) {
1708 // literal value list
1709 int in_item_index = 0;
1710 int in_item_first = 1;
1711 const jsonObject* in_item;
1712 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1717 buffer_add(sql_buf, ", ");
1720 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1721 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1722 MODULENAME, json_type( in_item->type ) );
1723 buffer_free(sql_buf);
1727 // Append the literal value -- quoted if not a number
1728 if ( JSON_NUMBER == in_item->type ) {
1729 char* val = jsonNumberToDBString( field, in_item );
1730 OSRF_BUFFER_ADD( sql_buf, val );
1733 } else if ( !strcmp( get_primitive( field ), "number") ) {
1734 char* val = jsonNumberToDBString( field, in_item );
1735 OSRF_BUFFER_ADD( sql_buf, val );
1739 char* key_string = jsonObjectToSimpleString(in_item);
1740 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1741 OSRF_BUFFER_ADD( sql_buf, key_string );
1744 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1746 buffer_free(sql_buf);
1752 if( in_item_first ) {
1753 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1754 buffer_free( sql_buf );
1758 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1759 MODULENAME, json_type( node->type ) );
1760 buffer_free(sql_buf);
1764 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1766 return buffer_release(sql_buf);
1769 // Receive a JSON_ARRAY representing a function call. The first
1770 // entry in the array is the function name. The rest are parameters.
1771 static char* searchValueTransform( const jsonObject* array ) {
1773 if( array->size < 1 ) {
1774 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
1778 // Get the function name
1779 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
1780 if( func_item->type != JSON_STRING ) {
1781 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
1782 MODULENAME, json_type( func_item->type ) );
1786 growing_buffer* sql_buf = buffer_init(32);
1788 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1789 OSRF_BUFFER_ADD( sql_buf, "( " );
1791 // Get the parameters
1792 int func_item_index = 1; // We already grabbed the zeroth entry
1793 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1795 // Add a separator comma, if we need one
1796 if( func_item_index > 2 )
1797 buffer_add( sql_buf, ", " );
1799 // Add the current parameter
1800 if (func_item->type == JSON_NULL) {
1801 buffer_add( sql_buf, "NULL" );
1803 char* val = jsonObjectToSimpleString(func_item);
1804 if ( dbi_conn_quote_string(dbhandle, &val) ) {
1805 OSRF_BUFFER_ADD( sql_buf, val );
1808 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1809 buffer_free(sql_buf);
1816 buffer_add( sql_buf, " )" );
1818 return buffer_release(sql_buf);
1821 static char* searchFunctionPredicate (const char* class, osrfHash* field,
1822 const jsonObject* node, const char* op) {
1824 if( ! is_good_operator( op ) ) {
1825 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
1829 char* val = searchValueTransform(node);
1833 growing_buffer* sql_buf = buffer_init(32);
1838 osrfHashGet(field, "name"),
1845 return buffer_release(sql_buf);
1848 // class is a class name
1849 // field is a field definition as stored in the IDL
1850 // node comes from the method parameter, and may represent an entry in the SELECT list
1851 static char* searchFieldTransform (const char* class, osrfHash* field, const jsonObject* node) {
1852 growing_buffer* sql_buf = buffer_init(32);
1854 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
1855 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
1857 if(transform_subcolumn) {
1858 if( ! is_identifier( transform_subcolumn ) ) {
1859 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
1860 MODULENAME, transform_subcolumn );
1861 buffer_free( sql_buf );
1864 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
1867 if (field_transform) {
1869 if( ! is_identifier( field_transform ) ) {
1870 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
1871 MODULENAME, field_transform );
1872 buffer_free( sql_buf );
1876 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class, osrfHashGet(field, "name"));
1877 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
1880 if( array->type != JSON_ARRAY ) {
1881 osrfLogError( OSRF_LOG_MARK,
1882 "%s: Expected JSON_ARRAY for function params; found %s",
1883 MODULENAME, json_type( array->type ) );
1884 buffer_free( sql_buf );
1887 int func_item_index = 0;
1888 jsonObject* func_item;
1889 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1891 char* val = jsonObjectToSimpleString(func_item);
1894 buffer_add( sql_buf, ",NULL" );
1895 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
1896 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
1897 OSRF_BUFFER_ADD( sql_buf, val );
1899 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1901 buffer_free(sql_buf);
1908 buffer_add( sql_buf, " )" );
1911 buffer_fadd( sql_buf, "\"%s\".%s", class, osrfHashGet(field, "name"));
1914 if (transform_subcolumn)
1915 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
1917 return buffer_release(sql_buf);
1920 static char* searchFieldTransformPredicate (const char* class, osrfHash* field,
1921 const jsonObject* node, const char* op ) {
1923 if( ! is_good_operator( op ) ) {
1924 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
1928 char* field_transform = searchFieldTransform( class, field, node );
1929 if( ! field_transform )
1932 int extra_parens = 0; // boolean
1934 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
1935 if ( ! value_obj ) {
1936 value = searchWHERE( node, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1938 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
1939 free(field_transform);
1943 } else if ( value_obj->type == JSON_ARRAY ) {
1944 value = searchValueTransform( value_obj );
1946 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
1947 free( field_transform );
1950 } else if ( value_obj->type == JSON_HASH ) {
1951 value = searchWHERE( value_obj, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1953 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
1954 free(field_transform);
1958 } else if ( value_obj->type == JSON_NUMBER ) {
1959 value = jsonNumberToDBString( field, value_obj );
1960 } else if ( value_obj->type == JSON_NULL ) {
1961 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
1962 free(field_transform);
1964 } else if ( value_obj->type == JSON_BOOL ) {
1965 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
1966 free(field_transform);
1969 if ( !strcmp( get_primitive( field ), "number") ) {
1970 value = jsonNumberToDBString( field, value_obj );
1972 value = jsonObjectToSimpleString( value_obj );
1973 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
1974 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
1976 free(field_transform);
1982 const char* left_parens = "";
1983 const char* right_parens = "";
1985 if( extra_parens ) {
1990 growing_buffer* sql_buf = buffer_init(32);
1994 "%s%s %s %s %s %s%s",
2005 free(field_transform);
2007 return buffer_release(sql_buf);
2010 static char* searchSimplePredicate (const char* op, const char* class,
2011 osrfHash* field, const jsonObject* node) {
2013 if( ! is_good_operator( op ) ) {
2014 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2020 // Get the value to which we are comparing the specified column
2021 if (node->type != JSON_NULL) {
2022 if ( node->type == JSON_NUMBER ) {
2023 val = jsonNumberToDBString( field, node );
2024 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2025 val = jsonNumberToDBString( field, node );
2027 val = jsonObjectToSimpleString(node);
2032 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2033 // Value is not numeric; enclose it in quotes
2034 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2035 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2041 // Compare to a null value
2042 val = strdup( "NULL" );
2043 if (strcmp( op, "=" ))
2049 growing_buffer* sql_buf = buffer_init(32);
2050 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class, osrfHashGet(field, "name"), op, val );
2051 char* pred = buffer_release( sql_buf );
2058 static char* searchBETWEENPredicate (const char* class, osrfHash* field, const jsonObject* node) {
2060 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2061 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2063 if( NULL == y_node ) {
2064 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2067 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2068 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2075 if ( !strcmp( get_primitive( field ), "number") ) {
2076 x_string = jsonNumberToDBString(field, x_node);
2077 y_string = jsonNumberToDBString(field, y_node);
2080 x_string = jsonObjectToSimpleString(x_node);
2081 y_string = jsonObjectToSimpleString(y_node);
2082 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2083 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2084 MODULENAME, x_string, y_string);
2091 growing_buffer* sql_buf = buffer_init(32);
2092 buffer_fadd( sql_buf, "%s BETWEEN %s AND %s", osrfHashGet(field, "name"), x_string, y_string );
2096 return buffer_release(sql_buf);
2099 static char* searchPredicate ( const char* class, osrfHash* field,
2100 jsonObject* node, osrfMethodContext* ctx ) {
2103 if (node->type == JSON_ARRAY) { // equality IN search
2104 pred = searchINPredicate( class, field, node, NULL, ctx );
2105 } else if (node->type == JSON_HASH) { // other search
2106 jsonIterator* pred_itr = jsonNewIterator( node );
2107 if( !jsonIteratorHasNext( pred_itr ) ) {
2108 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2109 MODULENAME, osrfHashGet(field, "name") );
2111 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2113 // Verify that there are no additional predicates
2114 if( jsonIteratorHasNext( pred_itr ) ) {
2115 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2116 MODULENAME, osrfHashGet(field, "name") );
2117 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2118 pred = searchBETWEENPredicate( class, field, pred_node );
2119 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2120 pred = searchINPredicate( class, field, pred_node, pred_itr->key, ctx );
2121 else if ( pred_node->type == JSON_ARRAY )
2122 pred = searchFunctionPredicate( class, field, pred_node, pred_itr->key );
2123 else if ( pred_node->type == JSON_HASH )
2124 pred = searchFieldTransformPredicate( class, field, pred_node, pred_itr->key );
2126 pred = searchSimplePredicate( pred_itr->key, class, field, pred_node );
2128 jsonIteratorFree(pred_itr);
2130 } else if (node->type == JSON_NULL) { // IS NULL search
2131 growing_buffer* _p = buffer_init(64);
2134 "\"%s\".%s IS NULL",
2136 osrfHashGet(field, "name")
2138 pred = buffer_release(_p);
2139 } else { // equality search
2140 pred = searchSimplePredicate( "=", class, field, node );
2159 field : call_number,
2175 static char* searchJOIN ( const jsonObject* join_hash, osrfHash* leftmeta ) {
2177 const jsonObject* working_hash;
2178 jsonObject* freeable_hash = NULL;
2180 if (join_hash->type == JSON_STRING) {
2181 // create a wrapper around a copy of the original
2182 const char* _tmp = jsonObjectGetString( join_hash );
2183 freeable_hash = jsonNewObjectType(JSON_HASH);
2184 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2185 working_hash = freeable_hash;
2188 if( join_hash->type != JSON_HASH ) {
2191 "%s: JOIN failed; expected JSON object type not found",
2196 working_hash = join_hash;
2199 growing_buffer* join_buf = buffer_init(128);
2200 const char* leftclass = osrfHashGet(leftmeta, "classname");
2202 jsonObject* snode = NULL;
2203 jsonIterator* search_itr = jsonNewIterator( working_hash );
2205 while ( (snode = jsonIteratorNext( search_itr )) ) {
2206 const char* class = search_itr->key;
2207 osrfHash* idlClass = osrfHashGet( oilsIDL(), class );
2211 "%s: JOIN failed. No class \"%s\" defined in IDL",
2215 jsonIteratorFree( search_itr );
2216 buffer_free( join_buf );
2218 jsonObjectFree( freeable_hash );
2222 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2223 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2225 if (field && !fkey) {
2226 // Look up the corresponding join column in the IDL.
2227 // The link must be defined in the child table,
2228 // and point to the right parent table.
2229 osrfHash* idl_link = (osrfHash*) oilsIDLFindPath( "/%s/links/%s", class, field );
2230 const char* reltype = NULL;
2231 const char* other_class = NULL;
2232 reltype = osrfHashGet( idl_link, "reltype" );
2233 if( reltype && strcmp( reltype, "has_many" ) )
2234 other_class = osrfHashGet( idl_link, "class" );
2235 if( other_class && !strcmp( other_class, leftclass ) )
2236 fkey = osrfHashGet( idl_link, "key" );
2240 "%s: JOIN failed. No link defined from %s.%s to %s",
2246 buffer_free(join_buf);
2248 jsonObjectFree(freeable_hash);
2249 jsonIteratorFree(search_itr);
2253 } else if (!field && fkey) {
2254 // Look up the corresponding join column in the IDL.
2255 // The link must be defined in the child table,
2256 // and point to the right parent table.
2257 osrfHash* idl_link = (osrfHash*) oilsIDLFindPath( "/%s/links/%s", leftclass, fkey );
2258 const char* reltype = NULL;
2259 const char* other_class = NULL;
2260 reltype = osrfHashGet( idl_link, "reltype" );
2261 if( reltype && strcmp( reltype, "has_many" ) )
2262 other_class = osrfHashGet( idl_link, "class" );
2263 if( other_class && !strcmp( other_class, class ) )
2264 field = osrfHashGet( idl_link, "key" );
2268 "%s: JOIN failed. No link defined from %s.%s to %s",
2274 buffer_free(join_buf);
2276 jsonObjectFree(freeable_hash);
2277 jsonIteratorFree(search_itr);
2281 } else if (!field && !fkey) {
2282 osrfHash* _links = oilsIDL_links( leftclass );
2284 // For each link defined for the left class:
2285 // see if the link references the joined class
2286 osrfHashIterator* itr = osrfNewHashIterator( _links );
2287 osrfHash* curr_link = NULL;
2288 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2289 const char* other_class = osrfHashGet( curr_link, "class" );
2290 if( other_class && !strcmp( other_class, class ) ) {
2292 // In the IDL, the parent class doesn't know then names of the child
2293 // columns that are pointing to it, so don't use that end of the link
2294 const char* reltype = osrfHashGet( curr_link, "reltype" );
2295 if( reltype && strcmp( reltype, "has_many" ) ) {
2296 // Found a link between the classes
2297 fkey = osrfHashIteratorKey( itr );
2298 field = osrfHashGet( curr_link, "key" );
2303 osrfHashIteratorFree( itr );
2305 if (!field || !fkey) {
2306 // Do another such search, with the classes reversed
2307 _links = oilsIDL_links( class );
2309 // For each link defined for the joined class:
2310 // see if the link references the left class
2311 osrfHashIterator* itr = osrfNewHashIterator( _links );
2312 osrfHash* curr_link = NULL;
2313 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2314 const char* other_class = osrfHashGet( curr_link, "class" );
2315 if( other_class && !strcmp( other_class, leftclass ) ) {
2317 // In the IDL, the parent class doesn't know then names of the child
2318 // columns that are pointing to it, so don't use that end of the link
2319 const char* reltype = osrfHashGet( curr_link, "reltype" );
2320 if( reltype && strcmp( reltype, "has_many" ) ) {
2321 // Found a link between the classes
2322 field = osrfHashIteratorKey( itr );
2323 fkey = osrfHashGet( curr_link, "key" );
2328 osrfHashIteratorFree( itr );
2331 if (!field || !fkey) {
2334 "%s: JOIN failed. No link defined between %s and %s",
2339 buffer_free(join_buf);
2341 jsonObjectFree(freeable_hash);
2342 jsonIteratorFree(search_itr);
2348 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2350 if ( !strcasecmp(type,"left") ) {
2351 buffer_add(join_buf, " LEFT JOIN");
2352 } else if ( !strcasecmp(type,"right") ) {
2353 buffer_add(join_buf, " RIGHT JOIN");
2354 } else if ( !strcasecmp(type,"full") ) {
2355 buffer_add(join_buf, " FULL JOIN");
2357 buffer_add(join_buf, " INNER JOIN");
2360 buffer_add(join_buf, " INNER JOIN");
2363 char* table = getSourceDefinition(idlClass);
2365 jsonIteratorFree( search_itr );
2366 buffer_free( join_buf );
2368 jsonObjectFree( freeable_hash );
2372 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2373 table, class, class, field, leftclass, fkey);
2376 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2378 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2379 if ( filter_op && !strcasecmp("or",filter_op) ) {
2380 buffer_add( join_buf, " OR " );
2382 buffer_add( join_buf, " AND " );
2385 char* jpred = searchWHERE( filter, idlClass, AND_OP_JOIN, NULL );
2387 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2388 OSRF_BUFFER_ADD( join_buf, jpred );
2393 "%s: JOIN failed. Invalid conditional expression.",
2396 jsonIteratorFree( search_itr );
2397 buffer_free( join_buf );
2399 jsonObjectFree( freeable_hash );
2404 buffer_add(join_buf, " ) ");
2406 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2408 char* jpred = searchJOIN( join_filter, idlClass );
2410 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2411 OSRF_BUFFER_ADD( join_buf, jpred );
2414 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2415 jsonIteratorFree( search_itr );
2416 buffer_free( join_buf );
2418 jsonObjectFree( freeable_hash );
2425 jsonObjectFree(freeable_hash);
2426 jsonIteratorFree(search_itr);
2428 return buffer_release(join_buf);
2433 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2434 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2435 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2437 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2439 search_hash is the JSON expression of the conditions.
2440 meta is the class definition from the IDL, for the relevant table.
2441 opjoin_type indicates whether multiple conditions, if present, should be
2442 connected by AND or OR.
2443 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2444 to pass it to other functions -- and all they do with it is to use the session
2445 and request members to send error messages back to the client.
2449 static char* searchWHERE ( const jsonObject* search_hash, osrfHash* meta, int opjoin_type, osrfMethodContext* ctx ) {
2453 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2461 growing_buffer* sql_buf = buffer_init(128);
2463 jsonObject* node = NULL;
2466 if ( search_hash->type == JSON_ARRAY ) {
2467 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2468 jsonIterator* search_itr = jsonNewIterator( search_hash );
2469 if( !jsonIteratorHasNext( search_itr ) ) {
2472 "%s: Invalid predicate structure: empty JSON array",
2475 jsonIteratorFree( search_itr );
2476 buffer_free( sql_buf );
2480 while ( (node = jsonIteratorNext( search_itr )) ) {
2484 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2485 else buffer_add(sql_buf, " AND ");
2488 char* subpred = searchWHERE( node, meta, opjoin_type, ctx );
2490 buffer_fadd(sql_buf, "( %s )", subpred);
2493 jsonIteratorFree( search_itr );
2494 buffer_free( sql_buf );
2498 jsonIteratorFree(search_itr);
2500 } else if ( search_hash->type == JSON_HASH ) {
2501 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2502 jsonIterator* search_itr = jsonNewIterator( search_hash );
2503 if( !jsonIteratorHasNext( search_itr ) ) {
2506 "%s: Invalid predicate structure: empty JSON object",
2509 jsonIteratorFree( search_itr );
2510 buffer_free( sql_buf );
2514 while ( (node = jsonIteratorNext( search_itr )) ) {
2519 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2520 else buffer_add(sql_buf, " AND ");
2523 if ( '+' == search_itr->key[ 0 ] ) {
2524 if ( node->type == JSON_STRING ) {
2525 // Intended purpose; to allow reference to a Boolean column
2527 // Verify that the class alias is not empty
2528 if( '\0' == search_itr->key[ 1 ] ) {
2531 "%s: Table alias is empty",
2534 jsonIteratorFree( search_itr );
2535 buffer_free( sql_buf );
2539 // Verify that the string looks like an identifier.
2540 const char* subpred = jsonObjectGetString( node );
2541 if( ! is_identifier( subpred ) ) {
2544 "%s: Invalid boolean identifier in WHERE clause: \"%s\"",
2548 jsonIteratorFree( search_itr );
2549 buffer_free( sql_buf );
2553 buffer_fadd(sql_buf, " \"%s\".%s ", search_itr->key + 1, subpred);
2555 char* subpred = searchWHERE( node, osrfHashGet( oilsIDL(), search_itr->key + 1 ), AND_OP_JOIN, ctx );
2557 buffer_fadd(sql_buf, "( %s )", subpred);
2560 jsonIteratorFree( search_itr );
2561 buffer_free( sql_buf );
2565 } else if ( !strcasecmp("-or",search_itr->key) ) {
2566 char* subpred = searchWHERE( node, meta, OR_OP_JOIN, ctx );
2568 buffer_fadd(sql_buf, "( %s )", subpred);
2571 buffer_free( sql_buf );
2574 } else if ( !strcasecmp("-and",search_itr->key) ) {
2575 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2577 buffer_fadd(sql_buf, "( %s )", subpred);
2580 buffer_free( sql_buf );
2583 } else if ( !strcasecmp("-not",search_itr->key) ) {
2584 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2586 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2589 buffer_free( sql_buf );
2592 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2593 char* subpred = SELECT(
2595 jsonObjectGetKey( node, "select" ),
2596 jsonObjectGetKey( node, "from" ),
2597 jsonObjectGetKey( node, "where" ),
2598 jsonObjectGetKey( node, "having" ),
2599 jsonObjectGetKey( node, "order_by" ),
2600 jsonObjectGetKey( node, "limit" ),
2601 jsonObjectGetKey( node, "offset" ),
2606 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2609 buffer_free( sql_buf );
2612 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2613 char* subpred = SELECT(
2615 jsonObjectGetKey( node, "select" ),
2616 jsonObjectGetKey( node, "from" ),
2617 jsonObjectGetKey( node, "where" ),
2618 jsonObjectGetKey( node, "having" ),
2619 jsonObjectGetKey( node, "order_by" ),
2620 jsonObjectGetKey( node, "limit" ),
2621 jsonObjectGetKey( node, "offset" ),
2626 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2629 buffer_free( sql_buf );
2635 char* class = osrfHashGet(meta, "classname");
2636 osrfHash* fields = osrfHashGet(meta, "fields");
2637 osrfHash* field = osrfHashGet( fields, search_itr->key );
2641 char* table = getSourceDefinition(meta);
2643 table = strdup( "(?)" );
2646 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2652 buffer_free(sql_buf);
2654 jsonIteratorFree(search_itr);
2658 char* subpred = searchPredicate( class, field, node, ctx );
2660 buffer_add( sql_buf, subpred );
2663 buffer_free(sql_buf);
2664 jsonIteratorFree(search_itr);
2669 jsonIteratorFree(search_itr);
2672 // ERROR ... only hash and array allowed at this level
2673 char* predicate_string = jsonObjectToJSON( search_hash );
2676 "%s: Invalid predicate structure: %s",
2680 buffer_free(sql_buf);
2681 free(predicate_string);
2685 return buffer_release(sql_buf);
2688 // Return 1 if the class is in the FROM clause, or 0 if not
2689 static int is_joined( jsonObject* from_clause, const char* class ) {
2693 else if( from_clause->type == JSON_STRING ) {
2694 if( strcmp( class, jsonObjectGetString( from_clause ) ) )
2698 } else if( from_clause->type != JSON_HASH ) {
2700 } else { // Look at any subjoins
2701 jsonIterator* class_itr = jsonNewIterator( from_clause );
2702 jsonObject* curr_class;
2703 int rc = 0; // return code
2704 while ( ( curr_class = jsonIteratorNext( class_itr ) ) ) {
2705 if( ! strcmp( class_itr->key, class ) ) {
2709 jsonObject* subjoin = jsonObjectGetKey( curr_class, "join" );
2710 if( subjoin && is_joined( subjoin, class ) ) {
2716 jsonIteratorFree( class_itr );
2723 /* method context */ osrfMethodContext* ctx,
2725 /* SELECT */ jsonObject* selhash,
2726 /* FROM */ jsonObject* join_hash,
2727 /* WHERE */ jsonObject* search_hash,
2728 /* HAVING */ jsonObject* having_hash,
2729 /* ORDER BY */ jsonObject* order_hash,
2730 /* LIMIT */ jsonObject* limit,
2731 /* OFFSET */ jsonObject* offset,
2732 /* flags */ int flags
2734 const char* locale = osrf_message_get_last_locale();
2736 // in case we don't get a select list
2737 jsonObject* defaultselhash = NULL;
2739 // general tmp objects
2740 const jsonObject* tmp_const;
2741 jsonObject* selclass = NULL;
2742 jsonObject* selfield = NULL;
2743 jsonObject* snode = NULL;
2744 jsonObject* onode = NULL;
2746 char* string = NULL;
2747 int from_function = 0;
2752 // the core search class
2753 char* core_class = NULL;
2755 // metadata about the core search class
2756 osrfHash* core_meta = NULL;
2758 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale);
2760 // punt if there's no core class
2761 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2764 "%s: FROM clause is missing or empty",
2768 osrfAppSessionStatus(
2770 OSRF_STATUS_INTERNALSERVERERROR,
2771 "osrfMethodException",
2773 "FROM clause is missing or empty in JSON query"
2778 // get the core class -- the only key of the top level FROM clause, or a string
2779 if (join_hash->type == JSON_HASH) {
2780 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2781 snode = jsonIteratorNext( tmp_itr );
2783 core_class = strdup( tmp_itr->key );
2786 jsonObject* extra = jsonIteratorNext( tmp_itr );
2788 jsonIteratorFree( tmp_itr );
2791 // There shouldn't be more than one entry in join_hash
2795 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2799 osrfAppSessionStatus(
2801 OSRF_STATUS_INTERNALSERVERERROR,
2802 "osrfMethodException",
2804 "Malformed FROM clause in JSON query"
2807 return NULL; // Malformed join_hash; extra entry
2809 } else if (join_hash->type == JSON_ARRAY) {
2811 core_class = jsonObjectToSimpleString( jsonObjectGetIndex(join_hash, 0) );
2814 } else if (join_hash->type == JSON_STRING) {
2815 core_class = jsonObjectToSimpleString( join_hash );
2821 "%s: FROM clause is unexpected JSON type: %s",
2823 json_type( join_hash->type )
2826 osrfAppSessionStatus(
2828 OSRF_STATUS_INTERNALSERVERERROR,
2829 "osrfMethodException",
2831 "Ill-formed FROM clause in JSON query"
2837 if (!from_function) {
2838 // Get the IDL class definition for the core class
2839 core_meta = osrfHashGet( oilsIDL(), core_class );
2840 if( !core_meta ) { // Didn't find it?
2843 "%s: SELECT clause references undefined class: \"%s\"",
2848 osrfAppSessionStatus(
2850 OSRF_STATUS_INTERNALSERVERERROR,
2851 "osrfMethodException",
2853 "SELECT clause references undefined class in JSON query"
2859 // Make sure the class isn't virtual
2860 if( str_is_true( osrfHashGet( core_meta, "virtual" ) ) ) {
2863 "%s: Core class is virtual: \"%s\"",
2868 osrfAppSessionStatus(
2870 OSRF_STATUS_INTERNALSERVERERROR,
2871 "osrfMethodException",
2873 "FROM clause references virtual class in JSON query"
2880 // if the select list is empty, or the core class field list is '*',
2881 // build the default select list ...
2883 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
2884 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2885 } else if( selhash->type != JSON_HASH ) {
2888 "%s: Expected JSON_HASH for SELECT clause; found %s",
2890 json_type( selhash->type )
2894 osrfAppSessionStatus(
2896 OSRF_STATUS_INTERNALSERVERERROR,
2897 "osrfMethodException",
2899 "Malformed SELECT clause in JSON query"
2903 } else if ( (tmp_const = jsonObjectGetKeyConst( selhash, core_class )) && tmp_const->type == JSON_STRING ) {
2904 const char* _x = jsonObjectGetString( tmp_const );
2905 if (!strncmp( "*", _x, 1 )) {
2906 jsonObjectRemoveKey( selhash, core_class );
2907 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2912 growing_buffer* sql_buf = buffer_init(128);
2914 // temp buffers for the SELECT list and GROUP BY clause
2915 growing_buffer* select_buf = buffer_init(128);
2916 growing_buffer* group_buf = buffer_init(128);
2918 int aggregate_found = 0; // boolean
2920 // Build a select list
2921 if(from_function) // From a function we select everything
2922 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
2925 // If we need to build a default list, prepare to do so
2926 jsonObject* _tmp = jsonObjectGetKey( selhash, core_class );
2927 if ( _tmp && !_tmp->size ) {
2929 osrfHash* core_fields = osrfHashGet( core_meta, "fields" );
2931 osrfHashIterator* field_itr = osrfNewHashIterator( core_fields );
2932 osrfHash* field_def;
2933 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2934 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2935 // This field is not virtual, so add it to the list
2936 jsonObjectPush( _tmp, jsonNewObject( osrfHashIteratorKey( field_itr ) ) );
2939 osrfHashIteratorFree( field_itr );
2942 // Now build the actual select list
2946 jsonIterator* selclass_itr = jsonNewIterator( selhash );
2947 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
2949 const char* cname = selclass_itr->key;
2951 // Make sure the target relation is in the FROM clause.
2953 // At this point join_hash is a step down from the join_hash we
2954 // received as a parameter. If the original was a JSON_STRING,
2955 // then json_hash is now NULL. If the original was a JSON_HASH,
2956 // then json_hash is now the first (and only) entry in it,
2957 // denoting the core class. We've already excluded the
2958 // possibility that the original was a JSON_ARRAY, because in
2959 // that case from_function would be non-NULL, and we wouldn't
2962 // If the current class isn't the core class
2963 // and it isn't in the join tree, bail out
2964 if ( strcmp( core_class, cname ) && ! is_joined( join_hash, cname ) ) {
2967 "%s: SELECT clause references class not in FROM clause: \"%s\"",
2972 osrfAppSessionStatus(
2974 OSRF_STATUS_INTERNALSERVERERROR,
2975 "osrfMethodException",
2977 "Selected class not in FROM clause in JSON query"
2979 jsonIteratorFree( selclass_itr );
2980 buffer_free( sql_buf );
2981 buffer_free( select_buf );
2982 buffer_free( group_buf );
2983 if( defaultselhash ) jsonObjectFree( defaultselhash );
2988 // Look up some attributes of the current class, so that we
2989 // don't have to look them up again for each field
2990 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
2991 osrfHash* class_field_set = osrfHashGet( idlClass, "fields" );
2992 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
2993 const char* class_tname = osrfHashGet( idlClass, "tablename" );
2995 // stitch together the column list ...
2996 jsonIterator* select_itr = jsonNewIterator( selclass );
2997 while ( (selfield = jsonIteratorNext( select_itr )) ) { // for each SELECT column
2999 // If we need a separator comma, add one
3003 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3006 // ... if it's a string, just toss it on the pile
3007 if (selfield->type == JSON_STRING) {
3009 // Look up the field in the IDL
3010 const char* col_name = jsonObjectGetString( selfield );
3011 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3013 // No such field in current class
3016 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3022 osrfAppSessionStatus(
3024 OSRF_STATUS_INTERNALSERVERERROR,
3025 "osrfMethodException",
3027 "Selected column not defined in JSON query"
3029 jsonIteratorFree( select_itr );
3030 jsonIteratorFree( selclass_itr );
3031 buffer_free( sql_buf );
3032 buffer_free( select_buf );
3033 buffer_free( group_buf );
3034 if( defaultselhash ) jsonObjectFree( defaultselhash );
3037 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3038 // Virtual field not allowed
3041 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3047 osrfAppSessionStatus(
3049 OSRF_STATUS_INTERNALSERVERERROR,
3050 "osrfMethodException",
3052 "Selected column may not be virtual in JSON query"
3054 jsonIteratorFree( select_itr );
3055 jsonIteratorFree( selclass_itr );
3056 buffer_free( sql_buf );
3057 buffer_free( select_buf );
3058 buffer_free( group_buf );
3059 if( defaultselhash ) jsonObjectFree( defaultselhash );
3066 if (flags & DISABLE_I18N)
3069 i18n = osrfHashGet(field_def, "i18n");
3071 if( str_is_true( i18n ) ) {
3072 buffer_fadd( select_buf,
3073 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3074 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3076 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3079 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3082 // ... but it could be an object, in which case we check for a Field Transform
3083 } else if (selfield->type == JSON_HASH) {
3085 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3087 // Get the field definition from the IDL
3088 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3090 // No such field in current class
3093 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3099 osrfAppSessionStatus(
3101 OSRF_STATUS_INTERNALSERVERERROR,
3102 "osrfMethodException",
3104 "Selected column is not defined in JSON query"
3106 jsonIteratorFree( select_itr );
3107 jsonIteratorFree( selclass_itr );
3108 buffer_free( sql_buf );
3109 buffer_free( select_buf );
3110 buffer_free( group_buf );
3111 if( defaultselhash ) jsonObjectFree( defaultselhash );
3114 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3115 // No such field in current class
3118 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3124 osrfAppSessionStatus(
3126 OSRF_STATUS_INTERNALSERVERERROR,
3127 "osrfMethodException",
3129 "Selected column is virtual in JSON query"
3131 jsonIteratorFree( select_itr );
3132 jsonIteratorFree( selclass_itr );
3133 buffer_free( sql_buf );
3134 buffer_free( select_buf );
3135 buffer_free( group_buf );
3136 if( defaultselhash ) jsonObjectFree( defaultselhash );
3141 // Decide what to use as a column alias
3143 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3144 _alias = jsonObjectGetString( tmp_const );
3145 } else { // Use field name as the alias
3149 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3150 char* transform_str = searchFieldTransform(cname, field_def, selfield);
3151 if( transform_str ) {
3152 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3153 free(transform_str);
3156 osrfAppSessionStatus(
3158 OSRF_STATUS_INTERNALSERVERERROR,
3159 "osrfMethodException",
3161 "Unable to generate transform function in JSON query"
3163 jsonIteratorFree( select_itr );
3164 jsonIteratorFree( selclass_itr );
3165 buffer_free( sql_buf );
3166 buffer_free( select_buf );
3167 buffer_free( group_buf );
3168 if( defaultselhash ) jsonObjectFree( defaultselhash );
3176 if (flags & DISABLE_I18N)
3179 i18n = osrfHashGet(field_def, "i18n");
3181 if( str_is_true( i18n ) ) {
3182 buffer_fadd( select_buf,
3183 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3184 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3186 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3189 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3196 "%s: Selected item is unexpected JSON type: %s",
3198 json_type( selfield->type )
3201 osrfAppSessionStatus(
3203 OSRF_STATUS_INTERNALSERVERERROR,
3204 "osrfMethodException",
3206 "Ill-formed SELECT item in JSON query"
3208 jsonIteratorFree( select_itr );
3209 jsonIteratorFree( selclass_itr );
3210 buffer_free( sql_buf );
3211 buffer_free( select_buf );
3212 buffer_free( group_buf );
3213 if( defaultselhash ) jsonObjectFree( defaultselhash );
3218 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3219 if( obj_is_true( agg_obj ) )
3220 aggregate_found = 1;
3222 // Append a comma (except for the first one)
3223 // and add the column to a GROUP BY clause
3227 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3229 buffer_fadd(group_buf, " %d", sel_pos);
3233 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3235 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3236 if ( ! obj_is_true( aggregate_obj ) ) {
3240 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3243 buffer_fadd(group_buf, " %d", sel_pos);
3246 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3250 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3253 _column = searchFieldTransform(cname, field, selfield);
3254 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3255 OSRF_BUFFER_ADD(group_buf, _column);
3256 _column = searchFieldTransform(cname, field, selfield);
3263 } // end while -- iterating across SELECT columns
3265 jsonIteratorFree(select_itr);
3266 } // end while -- iterating across classes
3268 jsonIteratorFree(selclass_itr);
3272 char* col_list = buffer_release(select_buf);
3274 if (from_function) table = searchValueTransform(join_hash);
3275 else table = getSourceDefinition(core_meta);
3279 osrfAppSessionStatus(
3281 OSRF_STATUS_INTERNALSERVERERROR,
3282 "osrfMethodException",
3284 "Unable to identify table for core class"
3287 buffer_free( sql_buf );
3288 buffer_free( group_buf );
3289 if( defaultselhash ) jsonObjectFree( defaultselhash );
3294 // Put it all together
3295 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3299 char* order_by_list = NULL;
3300 char* having_buf = NULL;
3302 if (!from_function) {
3304 // Now, walk the join tree and add that clause
3306 char* join_clause = searchJOIN( join_hash, core_meta );
3308 buffer_add(sql_buf, join_clause);
3312 osrfAppSessionStatus(
3314 OSRF_STATUS_INTERNALSERVERERROR,
3315 "osrfMethodException",
3317 "Unable to construct JOIN clause(s)"
3319 buffer_free( sql_buf );
3320 buffer_free( group_buf );
3321 if( defaultselhash ) jsonObjectFree( defaultselhash );
3327 // Build a WHERE clause, if there is one
3328 if ( search_hash ) {
3329 buffer_add(sql_buf, " WHERE ");
3331 // and it's on the WHERE clause
3332 char* pred = searchWHERE( search_hash, core_meta, AND_OP_JOIN, ctx );
3335 buffer_add(sql_buf, pred);
3339 osrfAppSessionStatus(
3341 OSRF_STATUS_INTERNALSERVERERROR,
3342 "osrfMethodException",
3344 "Severe query error in WHERE predicate -- see error log for more details"
3348 buffer_free(group_buf);
3349 buffer_free(sql_buf);
3350 if (defaultselhash) jsonObjectFree(defaultselhash);
3355 // Build a HAVING clause, if there is one
3356 if ( having_hash ) {
3358 // and it's on the the WHERE clause
3359 having_buf = searchWHERE( having_hash, core_meta, AND_OP_JOIN, ctx );
3361 if( ! having_buf ) {
3363 osrfAppSessionStatus(
3365 OSRF_STATUS_INTERNALSERVERERROR,
3366 "osrfMethodException",
3368 "Severe query error in HAVING predicate -- see error log for more details"
3372 buffer_free(group_buf);
3373 buffer_free(sql_buf);
3374 if (defaultselhash) jsonObjectFree(defaultselhash);
3379 growing_buffer* order_buf = NULL; // to collect ORDER BY list
3381 // Build an ORDER BY clause, if there is one
3382 if( NULL == order_hash )
3383 ; // No ORDER BY? do nothing
3384 else if( JSON_ARRAY == order_hash->type ) {
3385 // Array of field specifications, each specification being a
3386 // hash to define the class, field, and other details
3388 jsonObject* order_spec;
3389 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
3391 if( JSON_HASH != order_spec->type ) {
3392 osrfLogError(OSRF_LOG_MARK,
3393 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
3394 MODULENAME, json_type( order_spec->type ) );
3396 osrfAppSessionStatus(
3398 OSRF_STATUS_INTERNALSERVERERROR,
3399 "osrfMethodException",
3401 "Malformed ORDER BY clause -- see error log for more details"
3403 buffer_free( order_buf );
3406 buffer_free(group_buf);
3407 buffer_free(sql_buf);
3408 if (defaultselhash) jsonObjectFree(defaultselhash);
3413 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
3415 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
3418 OSRF_BUFFER_ADD(order_buf, ", ");
3420 order_buf = buffer_init(128);
3422 if( !field || !class ) {
3423 osrfLogError(OSRF_LOG_MARK,
3424 "%s: Missing class or field name in field specification of ORDER BY clause",
3427 osrfAppSessionStatus(
3429 OSRF_STATUS_INTERNALSERVERERROR,
3430 "osrfMethodException",
3432 "Malformed ORDER BY clause -- see error log for more details"
3434 buffer_free( order_buf );
3437 buffer_free(group_buf);
3438 buffer_free(sql_buf);
3439 if (defaultselhash) jsonObjectFree(defaultselhash);
3443 if ( ! jsonObjectGetKeyConst( selhash, class )
3444 && strcmp( core_class, class )
3445 && ! is_joined( join_hash, class ) ) {
3446 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
3447 "not in FROM clause", MODULENAME, class );
3449 osrfAppSessionStatus(
3451 OSRF_STATUS_INTERNALSERVERERROR,
3452 "osrfMethodException",
3454 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3458 buffer_free(group_buf);
3459 buffer_free(sql_buf);
3460 if (defaultselhash) jsonObjectFree(defaultselhash);
3464 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s", class, field );
3466 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
3467 MODULENAME, class, field );
3469 osrfAppSessionStatus(
3471 OSRF_STATUS_INTERNALSERVERERROR,
3472 "osrfMethodException",
3474 "Invalid field referenced in ORDER BY clause -- see error log for more details"
3478 buffer_free(group_buf);
3479 buffer_free(sql_buf);
3480 if (defaultselhash) jsonObjectFree(defaultselhash);
3482 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3483 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3484 MODULENAME, field );
3486 osrfAppSessionStatus(
3488 OSRF_STATUS_INTERNALSERVERERROR,
3489 "osrfMethodException",
3491 "Virtual field in ORDER BY clause -- see error log for more details"
3493 buffer_free( order_buf );
3496 buffer_free(group_buf);
3497 buffer_free(sql_buf);
3498 if (defaultselhash) jsonObjectFree(defaultselhash);
3502 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
3503 char* transform_str = searchFieldTransform( class, field_def, order_spec );
3504 if( ! transform_str ) {
3506 osrfAppSessionStatus(
3508 OSRF_STATUS_INTERNALSERVERERROR,
3509 "osrfMethodException",
3511 "Severe query error in ORDER BY clause -- see error log for more details"
3513 buffer_free( order_buf );
3516 buffer_free(group_buf);
3517 buffer_free(sql_buf);
3518 if (defaultselhash) jsonObjectFree(defaultselhash);
3522 OSRF_BUFFER_ADD( order_buf, transform_str );
3523 free( transform_str );
3526 buffer_fadd( order_buf, "\"%s\".%s", class, field );
3528 const char* direction =
3529 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
3531 if( direction[ 0 ] || 'D' == direction[ 0 ] )
3532 OSRF_BUFFER_ADD( order_buf, " DESC" );
3534 OSRF_BUFFER_ADD( order_buf, " ASC" );
3537 } else if( JSON_HASH == order_hash->type ) {
3538 // This hash is keyed on class name. Each class has either
3539 // an array of field names or a hash keyed on field name.
3540 jsonIterator* class_itr = jsonNewIterator( order_hash );
3541 while ( (snode = jsonIteratorNext( class_itr )) ) {
3543 if ( ! jsonObjectGetKeyConst( selhash,class_itr->key )
3544 && strcmp( core_class, class_itr->key )
3545 && ! is_joined( join_hash, class_itr->key ) ) {
3546 osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
3547 MODULENAME, class_itr->key );
3549 osrfAppSessionStatus(
3551 OSRF_STATUS_INTERNALSERVERERROR,
3552 "osrfMethodException",
3554 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3556 jsonIteratorFree( class_itr );
3557 buffer_free( order_buf );
3560 buffer_free(group_buf);
3561 buffer_free(sql_buf);
3562 if (defaultselhash) jsonObjectFree(defaultselhash);
3566 osrfHash* field_list_def = oilsIDLFindPath( "/%s/fields", class_itr->key );
3568 if ( snode->type == JSON_HASH ) {
3570 // Hash is keyed on field names from the current class. For each field
3571 // there is another layer of hash to define the sorting details, if any,
3572 // or a string to indicate direction of sorting.
3573 jsonIterator* order_itr = jsonNewIterator( snode );
3574 while ( (onode = jsonIteratorNext( order_itr )) ) {
3576 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
3578 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3579 MODULENAME, order_itr->key );
3581 osrfAppSessionStatus(
3583 OSRF_STATUS_INTERNALSERVERERROR,
3584 "osrfMethodException",
3586 "Invalid field in ORDER BY clause -- see error log for more details"
3588 jsonIteratorFree( order_itr );
3589 jsonIteratorFree( class_itr );
3590 buffer_free( order_buf );
3593 buffer_free(group_buf);
3594 buffer_free(sql_buf);
3595 if (defaultselhash) jsonObjectFree(defaultselhash);
3597 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3598 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3599 MODULENAME, order_itr->key );
3601 osrfAppSessionStatus(
3603 OSRF_STATUS_INTERNALSERVERERROR,
3604 "osrfMethodException",
3606 "Virtual field in ORDER BY clause -- see error log for more details"
3608 jsonIteratorFree( order_itr );
3609 jsonIteratorFree( class_itr );
3610 buffer_free( order_buf );
3613 buffer_free(group_buf);
3614 buffer_free(sql_buf);
3615 if (defaultselhash) jsonObjectFree(defaultselhash);
3619 const char* direction = NULL;
3620 if ( onode->type == JSON_HASH ) {
3621 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3622 string = searchFieldTransform(
3624 osrfHashGet( field_list_def, order_itr->key ),
3628 if( ctx ) osrfAppSessionStatus(
3630 OSRF_STATUS_INTERNALSERVERERROR,
3631 "osrfMethodException",
3633 "Severe query error in ORDER BY clause -- see error log for more details"
3635 jsonIteratorFree( order_itr );
3636 jsonIteratorFree( class_itr );
3639 buffer_free(group_buf);
3640 buffer_free(order_buf);
3641 buffer_free(sql_buf);
3642 if (defaultselhash) jsonObjectFree(defaultselhash);
3646 growing_buffer* field_buf = buffer_init(16);
3647 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3648 string = buffer_release(field_buf);
3651 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3652 const char* dir = jsonObjectGetString(tmp_const);
3653 if (!strncasecmp(dir, "d", 1)) {
3654 direction = " DESC";
3660 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
3661 osrfLogError( OSRF_LOG_MARK,
3662 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
3663 MODULENAME, json_type( onode->type ) );
3665 osrfAppSessionStatus(
3667 OSRF_STATUS_INTERNALSERVERERROR,
3668 "osrfMethodException",
3670 "Malformed ORDER BY clause -- see error log for more details"
3672 jsonIteratorFree( order_itr );
3673 jsonIteratorFree( class_itr );
3676 buffer_free(group_buf);
3677 buffer_free(order_buf);
3678 buffer_free(sql_buf);
3679 if (defaultselhash) jsonObjectFree(defaultselhash);
3683 string = strdup(order_itr->key);
3684 const char* dir = jsonObjectGetString(onode);
3685 if (!strncasecmp(dir, "d", 1)) {
3686 direction = " DESC";
3693 OSRF_BUFFER_ADD(order_buf, ", ");
3695 order_buf = buffer_init(128);
3697 OSRF_BUFFER_ADD(order_buf, string);
3701 OSRF_BUFFER_ADD(order_buf, direction);
3705 jsonIteratorFree(order_itr);
3707 } else if ( snode->type == JSON_ARRAY ) {
3709 // Array is a list of fields from the current class
3710 unsigned long order_idx = 0;
3711 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
3713 const char* _f = jsonObjectGetString( onode );
3715 osrfHash* field_def = osrfHashGet( field_list_def, _f );
3717 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3720 osrfAppSessionStatus(
3722 OSRF_STATUS_INTERNALSERVERERROR,
3723 "osrfMethodException",
3725 "Invalid field in ORDER BY clause -- see error log for more details"
3727 jsonIteratorFree( class_itr );
3728 buffer_free( order_buf );
3731 buffer_free(group_buf);
3732 buffer_free(sql_buf);
3733 if (defaultselhash) jsonObjectFree(defaultselhash);
3735 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3736 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3739 osrfAppSessionStatus(
3741 OSRF_STATUS_INTERNALSERVERERROR,
3742 "osrfMethodException",
3744 "Virtual field in ORDER BY clause -- see error log for more details"
3746 jsonIteratorFree( class_itr );
3747 buffer_free( order_buf );
3750 buffer_free(group_buf);
3751 buffer_free(sql_buf);
3752 if (defaultselhash) jsonObjectFree(defaultselhash);
3757 OSRF_BUFFER_ADD(order_buf, ", ");
3759 order_buf = buffer_init(128);
3761 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
3765 // IT'S THE OOOOOOOOOOOLD STYLE!
3767 osrfLogError(OSRF_LOG_MARK,
3768 "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3770 osrfAppSessionStatus(
3772 OSRF_STATUS_INTERNALSERVERERROR,
3773 "osrfMethodException",
3775 "Severe query error -- see error log for more details"
3781 buffer_free(group_buf);
3782 buffer_free(order_buf);
3783 buffer_free(sql_buf);
3784 if (defaultselhash) jsonObjectFree(defaultselhash);
3785 jsonIteratorFree(class_itr);
3789 jsonIteratorFree( class_itr );
3791 osrfLogError(OSRF_LOG_MARK,
3792 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
3793 MODULENAME, json_type( order_hash->type ) );
3795 osrfAppSessionStatus(
3797 OSRF_STATUS_INTERNALSERVERERROR,
3798 "osrfMethodException",
3800 "Malformed ORDER BY clause -- see error log for more details"
3802 buffer_free( order_buf );
3805 buffer_free(group_buf);
3806 buffer_free(sql_buf);
3807 if (defaultselhash) jsonObjectFree(defaultselhash);
3812 order_by_list = buffer_release( order_buf );
3816 string = buffer_release(group_buf);
3818 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
3819 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3820 OSRF_BUFFER_ADD( sql_buf, string );
3825 if( having_buf && *having_buf ) {
3826 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3827 OSRF_BUFFER_ADD( sql_buf, having_buf );
3831 if( order_by_list ) {
3833 if ( *order_by_list ) {
3834 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3835 OSRF_BUFFER_ADD( sql_buf, order_by_list );
3838 free( order_by_list );
3842 const char* str = jsonObjectGetString(limit);
3843 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
3847 const char* str = jsonObjectGetString(offset);
3848 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
3851 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3854 if (defaultselhash) jsonObjectFree(defaultselhash);
3856 return buffer_release(sql_buf);
3860 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
3862 const char* locale = osrf_message_get_last_locale();
3864 osrfHash* fields = osrfHashGet(meta, "fields");
3865 char* core_class = osrfHashGet(meta, "classname");
3867 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
3869 jsonObject* node = NULL;
3870 jsonObject* snode = NULL;
3871 jsonObject* onode = NULL;
3872 const jsonObject* _tmp = NULL;
3873 jsonObject* selhash = NULL;
3874 jsonObject* defaultselhash = NULL;
3876 growing_buffer* sql_buf = buffer_init(128);
3877 growing_buffer* select_buf = buffer_init(128);
3879 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
3880 defaultselhash = jsonNewObjectType(JSON_HASH);
3881 selhash = defaultselhash;
3884 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
3885 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
3886 jsonObject* flist = jsonObjectGetKey( selhash, core_class );
3891 osrfStringArray* keys = osrfHashKeys( fields );
3892 while ( (field = osrfStringArrayGetString(keys, i++)) ) {
3893 if( ! str_is_true( osrfHashGet( osrfHashGet( fields, field ), "virtual" ) ) )
3894 jsonObjectPush( flist, jsonNewObject( field ) );
3896 osrfStringArrayFree(keys);
3900 jsonIterator* class_itr = jsonNewIterator( selhash );
3901 while ( (snode = jsonIteratorNext( class_itr )) ) {
3903 char* cname = class_itr->key;
3904 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
3905 if (!idlClass) continue;
3907 if (strcmp(core_class,class_itr->key)) {
3908 if (!join_hash) continue;
3910 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
3912 jsonObjectFree(found);
3916 jsonObjectFree(found);
3919 jsonIterator* select_itr = jsonNewIterator( snode );
3920 while ( (node = jsonIteratorNext( select_itr )) ) {
3921 const char* item_str = jsonObjectGetString( node );
3922 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
3923 char* fname = osrfHashGet(field, "name");
3925 if (!field) continue;
3930 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
3935 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
3936 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
3939 i18n = osrfHashGet(field, "i18n");
3941 if( str_is_true( i18n ) ) {
3942 char* pkey = osrfHashGet(idlClass, "primarykey");
3943 char* tname = osrfHashGet(idlClass, "tablename");
3945 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);
3947 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3950 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3954 jsonIteratorFree(select_itr);
3957 jsonIteratorFree(class_itr);
3959 char* col_list = buffer_release(select_buf);
3960 char* table = getSourceDefinition(meta);
3962 table = strdup( "(null)" );
3964 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
3969 char* join_clause = searchJOIN( join_hash, meta );
3970 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
3971 OSRF_BUFFER_ADD(sql_buf, join_clause);
3975 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
3976 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
3978 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
3980 char* pred = searchWHERE( search_hash, meta, AND_OP_JOIN, ctx );
3982 osrfAppSessionStatus(
3984 OSRF_STATUS_INTERNALSERVERERROR,
3985 "osrfMethodException",
3987 "Severe query error -- see error log for more details"
3989 buffer_free(sql_buf);
3990 if(defaultselhash) jsonObjectFree(defaultselhash);
3993 buffer_add(sql_buf, pred);
3998 char* string = NULL;
3999 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4001 growing_buffer* order_buf = buffer_init(128);
4004 jsonIterator* class_itr = jsonNewIterator( _tmp );
4005 while ( (snode = jsonIteratorNext( class_itr )) ) {
4007 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4010 if ( snode->type == JSON_HASH ) {
4012 jsonIterator* order_itr = jsonNewIterator( snode );
4013 while ( (onode = jsonIteratorNext( order_itr )) ) {
4015 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4016 class_itr->key, order_itr->key );
4020 char* direction = NULL;
4021 if ( onode->type == JSON_HASH ) {
4022 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4023 string = searchFieldTransform( class_itr->key, field_def, onode );
4025 osrfAppSessionStatus(
4027 OSRF_STATUS_INTERNALSERVERERROR,
4028 "osrfMethodException",
4030 "Severe query error in ORDER BY clause -- see error log for more details"
4032 jsonIteratorFree( order_itr );
4033 jsonIteratorFree( class_itr );
4034 buffer_free( order_buf );
4035 buffer_free( sql_buf );
4036 if( defaultselhash ) jsonObjectFree( defaultselhash );
4040 growing_buffer* field_buf = buffer_init(16);
4041 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4042 string = buffer_release(field_buf);
4045 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4046 const char* dir = jsonObjectGetString(_tmp);
4047 if (!strncasecmp(dir, "d", 1)) {
4048 direction = " DESC";
4055 string = strdup(order_itr->key);
4056 const char* dir = jsonObjectGetString(onode);
4057 if (!strncasecmp(dir, "d", 1)) {
4058 direction = " DESC";
4067 buffer_add(order_buf, ", ");
4070 buffer_add(order_buf, string);
4074 buffer_add(order_buf, direction);
4079 jsonIteratorFree(order_itr);
4082 const char* str = jsonObjectGetString(snode);
4083 buffer_add(order_buf, str);
4089 jsonIteratorFree(class_itr);
4091 string = buffer_release(order_buf);
4094 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4095 OSRF_BUFFER_ADD( sql_buf, string );
4101 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4102 const char* str = jsonObjectGetString(_tmp);
4110 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4112 const char* str = jsonObjectGetString(_tmp);
4121 if (defaultselhash) jsonObjectFree(defaultselhash);
4123 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4124 return buffer_release(sql_buf);
4127 int doJSONSearch ( osrfMethodContext* ctx ) {
4128 if(osrfMethodVerifyContext( ctx )) {
4129 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4133 osrfLogDebug(OSRF_LOG_MARK, "Recieved query request");
4138 dbhandle = writehandle;
4140 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4144 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4145 flags |= SELECT_DISTINCT;
4147 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4148 flags |= DISABLE_I18N;
4150 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4153 jsonObjectGetKey( hash, "select" ),
4154 jsonObjectGetKey( hash, "from" ),
4155 jsonObjectGetKey( hash, "where" ),
4156 jsonObjectGetKey( hash, "having" ),
4157 jsonObjectGetKey( hash, "order_by" ),
4158 jsonObjectGetKey( hash, "limit" ),
4159 jsonObjectGetKey( hash, "offset" ),
4168 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4169 dbi_result result = dbi_conn_query(dbhandle, sql);
4172 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4174 if (dbi_result_first_row(result)) {
4175 /* JSONify the result */
4176 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4179 jsonObject* return_val = oilsMakeJSONFromResult( result );
4180 osrfAppRespond( ctx, return_val );
4181 jsonObjectFree( return_val );
4182 } while (dbi_result_next_row(result));
4185 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4188 osrfAppRespondComplete( ctx, NULL );
4190 /* clean up the query */
4191 dbi_result_free(result);
4195 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4196 osrfAppSessionStatus(
4198 OSRF_STATUS_INTERNALSERVERERROR,
4199 "osrfMethodException",
4201 "Severe query error -- see error log for more details"
4209 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4210 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4213 dbhandle = writehandle;
4215 osrfHash* links = osrfHashGet(meta, "links");
4216 osrfHash* fields = osrfHashGet(meta, "fields");
4217 char* core_class = osrfHashGet(meta, "classname");
4218 char* pkey = osrfHashGet(meta, "primarykey");
4220 const jsonObject* _tmp;
4223 char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4225 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4230 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4232 dbi_result result = dbi_conn_query(dbhandle, sql);
4233 if( NULL == result ) {
4234 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4235 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4236 osrfAppSessionStatus(
4238 OSRF_STATUS_INTERNALSERVERERROR,
4239 "osrfMethodException",
4241 "Severe query error -- see error log for more details"
4248 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4251 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
4252 osrfHash* dedup = osrfNewHash();
4254 if (dbi_result_first_row(result)) {
4255 /* JSONify the result */
4256 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4258 obj = oilsMakeFieldmapperFromResult( result, meta );
4259 char* pkey_val = oilsFMGetString( obj, pkey );
4260 if ( osrfHashGet( dedup, pkey_val ) ) {
4261 jsonObjectFree(obj);
4264 osrfHashSet( dedup, pkey_val, pkey_val );
4265 jsonObjectPush(res_list, obj);
4267 } while (dbi_result_next_row(result));
4269 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
4273 osrfHashFree(dedup);
4274 /* clean up the query */
4275 dbi_result_free(result);
4278 if (res_list->size && query_hash) {
4279 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
4281 int x = (int)jsonObjectGetNumber(_tmp);
4282 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
4284 const jsonObject* temp_blob;
4285 if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
4287 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
4288 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
4290 osrfStringArray* link_fields = NULL;
4293 if (flesh_fields->size == 1) {
4294 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
4295 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
4300 link_fields = osrfNewStringArray(1);
4301 jsonIterator* _i = jsonNewIterator( flesh_fields );
4302 while ((_f = jsonIteratorNext( _i ))) {
4303 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4305 jsonIteratorFree(_i);
4310 jsonIterator* itr = jsonNewIterator( res_list );
4311 while ((cur = jsonIteratorNext( itr ))) {
4316 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4318 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4320 osrfHash* kid_link = osrfHashGet(links, link_field);
4321 if (!kid_link) continue;
4323 osrfHash* field = osrfHashGet(fields, link_field);
4324 if (!field) continue;
4326 osrfHash* value_field = field;
4328 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4329 if (!kid_idl) continue;
4331 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4332 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4335 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4336 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4339 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4341 if (link_map->size > 0) {
4342 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4345 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4350 osrfHashGet(kid_link, "class"),
4357 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4358 osrfHashGet(kid_link, "field"),
4359 osrfHashGet(kid_link, "class"),
4360 osrfHashGet(kid_link, "key"),
4361 osrfHashGet(kid_link, "reltype")
4364 const char* search_key = jsonObjectGetString(
4367 atoi( osrfHashGet(value_field, "array_position") )
4372 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4376 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
4378 // construct WHERE clause
4379 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
4382 osrfHashGet(kid_link, "key"),
4383 jsonNewObject( search_key )
4386 // construct the rest of the query
4387 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
4388 jsonObjectSetKey( rest_of_query, "flesh",
4389 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4393 jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
4395 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
4396 jsonObjectSetKey( rest_of_query, "order_by",
4397 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
4401 if (jsonObjectGetKeyConst(query_hash, "select")) {
4402 jsonObjectSetKey( rest_of_query, "select",
4403 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
4407 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
4408 where_clause, rest_of_query, err);
4410 jsonObjectFree( where_clause );
4411 jsonObjectFree( rest_of_query );
4414 osrfStringArrayFree(link_fields);
4415 jsonIteratorFree(itr);
4416 jsonObjectFree(res_list);
4417 jsonObjectFree(flesh_blob);
4421 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4423 jsonObject* X = NULL;
4424 if ( link_map->size > 0 && kids->size > 0 ) {
4426 kids = jsonNewObjectType(JSON_ARRAY);
4428 jsonObject* _k_node;
4429 jsonIterator* _k = jsonNewIterator( X );
4430 while ((_k_node = jsonIteratorNext( _k ))) {
4436 (unsigned long)atoi(
4442 osrfHashGet(kid_link, "class")
4446 osrfStringArrayGetString( link_map, 0 )
4455 jsonIteratorFree(_k);
4458 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4459 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4462 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4463 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4467 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4468 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4471 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4472 jsonObjectClone( kids )
4477 jsonObjectFree(kids);
4481 jsonObjectFree( kids );
4483 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4484 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4488 jsonObjectFree( flesh_blob );
4489 osrfStringArrayFree(link_fields);
4490 jsonIteratorFree(itr);
4499 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
4501 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4503 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
4505 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
4508 if (!verifyObjectClass(ctx, target)) {
4513 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4514 osrfAppSessionStatus(
4516 OSRF_STATUS_BADREQUEST,
4517 "osrfMethodException",
4519 "No active transaction -- required for UPDATE"
4525 // The following test is harmless but redundant. If a class is
4526 // readonly, we don't register an update method for it.
4527 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4528 osrfAppSessionStatus(
4530 OSRF_STATUS_BADREQUEST,
4531 "osrfMethodException",
4533 "Cannot UPDATE readonly class"
4539 dbhandle = writehandle;
4541 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4543 // Set the last_xact_id
4544 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4546 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
4547 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4550 char* pkey = osrfHashGet(meta, "primarykey");
4551 osrfHash* fields = osrfHashGet(meta, "fields");
4553 char* id = oilsFMGetString( target, pkey );
4557 "%s updating %s object with %s = %s",
4559 osrfHashGet(meta, "fieldmapper"),
4564 growing_buffer* sql = buffer_init(128);
4565 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4570 osrfStringArray* field_list = osrfHashKeys( fields );
4571 while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
4573 osrfHash* field = osrfHashGet( fields, field_name );
4575 if(!( strcmp( field_name, pkey ) )) continue;
4576 if( str_is_true( osrfHashGet(osrfHashGet(fields,field_name), "virtual") ) )
4579 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4581 int value_is_numeric = 0; // boolean
4583 if (field_object && field_object->classname) {
4584 value = oilsFMGetString(
4586 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4589 value = jsonObjectToSimpleString( field_object );
4590 if( field_object && JSON_NUMBER == field_object->type )
4591 value_is_numeric = 1;
4594 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s", osrfHashGet(meta, "fieldmapper"), field_name, value);
4596 if (!field_object || field_object->type == JSON_NULL) {
4597 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) ) && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
4598 if (first) first = 0;
4599 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4600 buffer_fadd( sql, " %s = NULL", field_name );
4603 } else if ( value_is_numeric || !strcmp( get_primitive( field ), "number") ) {
4604 if (first) first = 0;
4605 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4607 const char* numtype = get_datatype( field );
4608 if ( !strncmp( numtype, "INT", 3 ) ) {
4609 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
4610 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
4611 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
4613 // Must really be intended as a string, so quote it
4614 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4615 buffer_fadd( sql, " %s = %s", field_name, value );
4617 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4618 osrfAppSessionStatus(
4620 OSRF_STATUS_INTERNALSERVERERROR,
4621 "osrfMethodException",
4623 "Error quoting string -- please see the error log for more details"
4633 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
4636 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4637 if (first) first = 0;
4638 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4639 buffer_fadd( sql, " %s = %s", field_name, value );
4642 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4643 osrfAppSessionStatus(
4645 OSRF_STATUS_INTERNALSERVERERROR,
4646 "osrfMethodException",
4648 "Error quoting string -- please see the error log for more details"
4662 jsonObject* obj = jsonNewObject(id);
4664 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4665 dbi_conn_quote_string(dbhandle, &id);
4667 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
4669 char* query = buffer_release(sql);
4670 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
4672 dbi_result result = dbi_conn_query(dbhandle, query);
4676 jsonObjectFree(obj);
4677 obj = jsonNewObject(NULL);
4680 "%s ERROR updating %s object with %s = %s",
4682 osrfHashGet(meta, "fieldmapper"),
4693 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4695 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4697 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4698 osrfAppSessionStatus(
4700 OSRF_STATUS_BADREQUEST,
4701 "osrfMethodException",
4703 "No active transaction -- required for DELETE"
4709 // The following test is harmless but redundant. If a class is
4710 // readonly, we don't register a delete method for it.
4711 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4712 osrfAppSessionStatus(
4714 OSRF_STATUS_BADREQUEST,
4715 "osrfMethodException",
4717 "Cannot DELETE readonly class"
4723 dbhandle = writehandle;
4727 char* pkey = osrfHashGet(meta, "primarykey");
4735 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4736 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4741 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4744 if (!verifyObjectPCRUD( ctx, NULL )) {
4749 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4754 "%s deleting %s object with %s = %s",
4756 osrfHashGet(meta, "fieldmapper"),
4761 obj = jsonNewObject(id);
4763 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4764 dbi_conn_quote_string(writehandle, &id);
4766 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4769 jsonObjectFree(obj);
4770 obj = jsonNewObject(NULL);
4773 "%s ERROR deleting %s object with %s = %s",
4775 osrfHashGet(meta, "fieldmapper"),
4788 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4789 if(!(result && meta)) return jsonNULL;
4791 jsonObject* object = jsonNewObject(NULL);
4792 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4794 osrfHash* fields = osrfHashGet(meta, "fields");
4796 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4800 char dt_string[256];
4804 int columnIndex = 1;
4806 unsigned short type;
4807 const char* columnName;
4809 /* cycle through the column list */
4810 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4812 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4814 fmIndex = -1; // reset the position
4816 /* determine the field type and storage attributes */
4817 type = dbi_result_get_field_type(result, columnName);
4818 attr = dbi_result_get_field_attribs(result, columnName);
4820 /* fetch the fieldmapper index */
4821 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4823 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4826 const char* pos = (char*)osrfHashGet(_f, "array_position");
4827 if ( !pos ) continue;
4829 fmIndex = atoi( pos );
4830 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
4835 if (dbi_result_field_is_null(result, columnName)) {
4836 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
4841 case DBI_TYPE_INTEGER :
4843 if( attr & DBI_INTEGER_SIZE8 )
4844 jsonObjectSetIndex( object, fmIndex,
4845 jsonNewNumberObject(dbi_result_get_longlong(result, columnName)));
4847 jsonObjectSetIndex( object, fmIndex,
4848 jsonNewNumberObject(dbi_result_get_int(result, columnName)));
4852 case DBI_TYPE_DECIMAL :
4853 jsonObjectSetIndex( object, fmIndex,
4854 jsonNewNumberObject(dbi_result_get_double(result, columnName)));
4857 case DBI_TYPE_STRING :
4863 jsonNewObject( dbi_result_get_string(result, columnName) )
4868 case DBI_TYPE_DATETIME :
4870 memset(dt_string, '\0', sizeof(dt_string));
4871 memset(&gmdt, '\0', sizeof(gmdt));
4873 _tmp_dt = dbi_result_get_datetime(result, columnName);
4876 if (!(attr & DBI_DATETIME_DATE)) {
4877 gmtime_r( &_tmp_dt, &gmdt );
4878 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4879 } else if (!(attr & DBI_DATETIME_TIME)) {
4880 localtime_r( &_tmp_dt, &gmdt );
4881 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4883 localtime_r( &_tmp_dt, &gmdt );
4884 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4887 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
4891 case DBI_TYPE_BINARY :
4892 osrfLogError( OSRF_LOG_MARK,
4893 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4901 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
4902 if(!result) return jsonNULL;
4904 jsonObject* object = jsonNewObject(NULL);
4907 char dt_string[256];
4911 int columnIndex = 1;
4913 unsigned short type;
4914 const char* columnName;
4916 /* cycle through the column list */
4917 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4919 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4921 fmIndex = -1; // reset the position
4923 /* determine the field type and storage attributes */
4924 type = dbi_result_get_field_type(result, columnName);
4925 attr = dbi_result_get_field_attribs(result, columnName);
4927 if (dbi_result_field_is_null(result, columnName)) {
4928 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
4933 case DBI_TYPE_INTEGER :
4935 if( attr & DBI_INTEGER_SIZE8 )
4936 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_longlong(result, columnName)) );
4938 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_int(result, columnName)) );
4941 case DBI_TYPE_DECIMAL :
4942 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_double(result, columnName)) );
4945 case DBI_TYPE_STRING :
4946 jsonObjectSetKey( object, columnName, jsonNewObject(dbi_result_get_string(result, columnName)) );
4949 case DBI_TYPE_DATETIME :
4951 memset(dt_string, '\0', sizeof(dt_string));
4952 memset(&gmdt, '\0', sizeof(gmdt));
4954 _tmp_dt = dbi_result_get_datetime(result, columnName);
4957 if (!(attr & DBI_DATETIME_DATE)) {
4958 gmtime_r( &_tmp_dt, &gmdt );
4959 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4960 } else if (!(attr & DBI_DATETIME_TIME)) {
4961 localtime_r( &_tmp_dt, &gmdt );
4962 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4964 localtime_r( &_tmp_dt, &gmdt );
4965 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4968 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
4971 case DBI_TYPE_BINARY :
4972 osrfLogError( OSRF_LOG_MARK,
4973 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4981 // Interpret a string as true or false
4982 static int str_is_true( const char* str ) {
4983 if( NULL == str || strcasecmp( str, "true" ) )
4989 // Interpret a jsonObject as true or false
4990 static int obj_is_true( const jsonObject* obj ) {
4993 else switch( obj->type )
5001 if( strcasecmp( obj->value.s, "true" ) )
5005 case JSON_NUMBER : // Support 1/0 for perl's sake
5006 if( jsonObjectGetNumber( obj ) == 1.0 )
5015 // Translate a numeric code into a text string identifying a type of
5016 // jsonObject. To be used for building error messages.
5017 static const char* json_type( int code ) {
5023 return "JSON_ARRAY";
5025 return "JSON_STRING";
5027 return "JSON_NUMBER";
5033 return "(unrecognized)";
5037 // Extract the "primitive" attribute from an IDL field definition.
5038 // If we haven't initialized the app, then we must be running in
5039 // some kind of testbed. In that case, default to "string".
5040 static const char* get_primitive( osrfHash* field ) {
5041 const char* s = osrfHashGet( field, "primitive" );
5043 if( child_initialized )
5046 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5048 osrfHashGet( field, "name" )
5056 // Extract the "datatype" attribute from an IDL field definition.
5057 // If we haven't initialized the app, then we must be running in
5058 // some kind of testbed. In that case, default to to NUMERIC,
5059 // since we look at the datatype only for numbers.
5060 static const char* get_datatype( osrfHash* field ) {
5061 const char* s = osrfHashGet( field, "datatype" );
5063 if( child_initialized )
5066 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5068 osrfHashGet( field, "name" )
5077 If the input string is potentially a valid SQL identifier, return 1.
5080 Purpose: to prevent certain kinds of SQL injection. To that end we
5081 don't necessarily need to follow all the rules exactly, such as requiring
5082 that the first character not be a digit.
5084 We allow leading and trailing white space. In between, we do not allow
5085 punctuation (except for underscores and dollar signs), control
5086 characters, or embedded white space.
5088 More pedantically we should allow quoted identifiers containing arbitrary
5089 characters, but for the foreseeable future such quoted identifiers are not
5090 likely to be an issue.
5092 static int is_identifier( const char* s) {
5096 // Skip leading white space
5097 while( isspace( (unsigned char) *s ) )
5101 return 0; // Nothing but white space? Not okay.
5103 // Check each character until we reach white space or
5104 // end-of-string. Letters, digits, underscores, and
5105 // dollar signs are okay. With the exception of periods
5106 // (as in schema.identifier), control characters and other
5107 // punctuation characters are not okay. Anything else
5108 // is okay -- it could for example be part of a multibyte
5109 // UTF8 character such as a letter with diacritical marks,
5110 // and those are allowed.
5112 if( isalnum( (unsigned char) *s )
5116 ; // Fine; keep going
5117 else if( ispunct( (unsigned char) *s )
5118 || iscntrl( (unsigned char) *s ) )
5121 } while( *s && ! isspace( (unsigned char) *s ) );
5123 // If we found any white space in the above loop,
5124 // the rest had better be all white space.
5126 while( isspace( (unsigned char) *s ) )
5130 return 0; // White space was embedded within non-white space
5136 Determine whether to accept a character string as a comparison operator.
5137 Return 1 if it's good, or 0 if it's bad.
5139 We don't validate it for real. We just make sure that it doesn't contain
5140 any semicolons or white space (with a special exception for the
5141 "SIMILAR TO" operator). The idea is to block certain kinds of SQL
5142 injection. If it has no semicolons or white space but it's still not a
5143 valid operator, then the database will complain.
5145 Another approach would be to compare the string against a short list of
5146 approved operators. We don't do that because we want to allow custom
5147 operators like ">100*", which would be difficult or impossible to
5148 express otherwise in a JSON query.
5150 static int is_good_operator( const char* op ) {
5151 if( !op ) return 0; // Sanity check
5155 if( isspace( (unsigned char) *s ) ) {
5156 // Special exception for SIMILAR TO. Someday we might make
5157 // exceptions for IS DISTINCT FROM and IS NOT DISTINCT FROM.
5158 if( !strcasecmp( op, "similar to" ) )
5163 else if( ';' == *s )