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*, osrfHash*,
54 const jsonObject*, int* );
55 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
56 static jsonObject* oilsMakeJSONFromResult( dbi_result );
58 static char* searchSimplePredicate ( const char* op, const char* class,
59 osrfHash* field, const jsonObject* node );
60 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
61 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject*);
62 static char* searchFieldTransformPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
63 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
64 static char* searchINPredicate ( const char*, osrfHash*,
65 jsonObject*, const char*, osrfMethodContext* );
66 static char* searchPredicate ( const char*, osrfHash*, jsonObject*, osrfMethodContext* );
67 static char* searchJOIN ( const jsonObject*, osrfHash* );
68 static char* searchWHERE ( const jsonObject*, osrfHash*, int, osrfMethodContext* );
69 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
71 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
73 void userDataFree( void* );
74 static void sessionDataFree( char*, void* );
75 static char* getSourceDefinition( osrfHash* );
76 static int str_is_true( const char* str );
77 static int obj_is_true( const jsonObject* obj );
78 static const char* json_type( int code );
79 static const char* get_primitive( osrfHash* field );
80 static const char* get_datatype( osrfHash* field );
81 static int is_identifier( const char* s);
82 static int is_good_operator( const char* op );
85 static jsonObject* verifyUserPCRUD( osrfMethodContext* );
86 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
89 static int child_initialized = 0; /* boolean */
91 static dbi_conn writehandle; /* our MASTER db connection */
92 static dbi_conn dbhandle; /* our CURRENT db connection */
93 //static osrfHash * readHandles;
94 static jsonObject* jsonNULL = NULL; //
95 static int max_flesh_depth = 100;
97 /* called when this process is about to exit */
98 void osrfAppChildExit() {
99 osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
102 if (writehandle == dbhandle) same = 1;
104 dbi_conn_query(writehandle, "ROLLBACK;");
105 dbi_conn_close(writehandle);
108 if (dbhandle && !same)
109 dbi_conn_close(dbhandle);
111 // XXX add cleanup of readHandles whenever that gets used
116 int osrfAppInitialize() {
118 osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
119 osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
121 if (!oilsIDLInit( osrf_settings_host_value("/IDL") )) return 1; /* return non-zero to indicate error */
123 growing_buffer* method_name = buffer_init(64);
125 // Generic search thingy
126 buffer_add(method_name, MODULENAME);
127 buffer_add(method_name, ".json_query");
128 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
129 "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
132 // first we register all the transaction and savepoint methods
133 buffer_reset(method_name);
134 OSRF_BUFFER_ADD(method_name, MODULENAME);
135 OSRF_BUFFER_ADD(method_name, ".transaction.begin");
136 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
137 "beginTransaction", "", 0, 0 );
139 buffer_reset(method_name);
140 OSRF_BUFFER_ADD(method_name, MODULENAME);
141 OSRF_BUFFER_ADD(method_name, ".transaction.commit");
142 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
143 "commitTransaction", "", 0, 0 );
145 buffer_reset(method_name);
146 OSRF_BUFFER_ADD(method_name, MODULENAME);
147 OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
148 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
149 "rollbackTransaction", "", 0, 0 );
151 buffer_reset(method_name);
152 OSRF_BUFFER_ADD(method_name, MODULENAME);
153 OSRF_BUFFER_ADD(method_name, ".savepoint.set");
154 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
155 "setSavepoint", "", 1, 0 );
157 buffer_reset(method_name);
158 OSRF_BUFFER_ADD(method_name, MODULENAME);
159 OSRF_BUFFER_ADD(method_name, ".savepoint.release");
160 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
161 "releaseSavepoint", "", 1, 0 );
163 buffer_reset(method_name);
164 OSRF_BUFFER_ADD(method_name, MODULENAME);
165 OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
166 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
167 "rollbackSavepoint", "", 1, 0 );
169 buffer_free(method_name);
171 static const char* global_method[] = {
179 const int global_method_count
180 = sizeof( global_method ) / sizeof ( global_method[0] );
184 osrfStringArray* classes = osrfHashKeys( oilsIDL() );
185 osrfLogDebug(OSRF_LOG_MARK, "%d classes loaded", classes->size );
186 osrfLogDebug(OSRF_LOG_MARK,
187 "At least %d methods will be generated", classes->size * global_method_count);
189 while ( (classname = osrfStringArrayGetString(classes, c_index++)) ) {
190 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
192 osrfHash* idlClass = osrfHashGet(oilsIDL(), classname);
194 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), MODULENAME )) {
195 osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on", MODULENAME, classname);
199 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
200 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
204 // Look up some other attributes of the current class
205 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
206 const char* readonly = osrfHashGet(idlClass, "readonly");
208 osrfHash* idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
212 for( i = 0; i < global_method_count; ++i ) {
213 const char* method_type = global_method[ i ];
214 osrfLogDebug(OSRF_LOG_MARK,
215 "Using files to build %s class methods for %s", method_type, classname);
217 if (!idlClass_fieldmapper) continue;
220 if (!idlClass_permacrud) continue;
222 const char* tmp_method = method_type;
223 if ( *tmp_method == 'i' || *tmp_method == 's') {
224 tmp_method = "retrieve";
226 if (!osrfHashGet( idlClass_permacrud, tmp_method )) continue;
229 if ( str_is_true( readonly ) &&
230 ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd')
233 osrfHash* method_meta = osrfNewHash();
234 osrfHashSet(method_meta, idlClass, "class");
236 method_name = buffer_init(64);
238 buffer_fadd(method_name, "%s.%s.%s", MODULENAME, method_type, classname);
242 char* _fm = strdup( idlClass_fieldmapper );
243 part = strtok_r(_fm, ":", &st_tmp);
245 buffer_fadd(method_name, "%s.direct.%s", MODULENAME, part);
247 while ((part = strtok_r(NULL, ":", &st_tmp))) {
248 OSRF_BUFFER_ADD_CHAR(method_name, '.');
249 OSRF_BUFFER_ADD(method_name, part);
251 OSRF_BUFFER_ADD_CHAR(method_name, '.');
252 OSRF_BUFFER_ADD(method_name, method_type);
256 char* method = buffer_release(method_name);
258 osrfHashSet( method_meta, method, "methodname" );
259 osrfHashSet( method_meta, strdup(method_type), "methodtype" );
262 if (*method_type == 'i' || *method_type == 's') {
263 flags = flags | OSRF_METHOD_STREAMING;
266 osrfAppRegisterExtendedMethod(
269 "dispatchCRUDMethod",
283 static char* getSourceDefinition( osrfHash* class ) {
285 char* tabledef = osrfHashGet(class, "tablename");
288 tabledef = strdup(tabledef);
290 tabledef = osrfHashGet(class, "source_definition");
292 growing_buffer* tablebuf = buffer_init(128);
293 buffer_fadd( tablebuf, "(%s)", tabledef );
294 tabledef = buffer_release(tablebuf);
296 const char* classname = osrfHashGet( class, "classname" );
301 "%s ERROR No tablename or source_definition for class \"%s\"",
312 * Connects to the database
314 int osrfAppChildInit() {
316 osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
317 dbi_initialize(NULL);
318 osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
320 char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
321 char* user = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
322 char* host = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
323 char* port = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
324 char* db = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
325 char* pw = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
326 char* md = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion", MODULENAME);
328 osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
329 writehandle = dbi_conn_new(driver);
332 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
335 osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
337 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
338 "port=%s, user=%s, pw=%s, db=%s", MODULENAME, host, port, user, pw, db );
340 if(host) dbi_conn_set_option(writehandle, "host", host );
341 if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
342 if(user) dbi_conn_set_option(writehandle, "username", user);
343 if(pw) dbi_conn_set_option(writehandle, "password", pw );
344 if(db) dbi_conn_set_option(writehandle, "dbname", db );
346 if(md) max_flesh_depth = atoi(md);
347 if(max_flesh_depth < 0) max_flesh_depth = 1;
348 if(max_flesh_depth > 1000) max_flesh_depth = 1000;
357 if (dbi_conn_connect(writehandle) < 0) {
359 if (dbi_conn_connect(writehandle) < 0) {
360 dbi_conn_error(writehandle, &err);
361 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
366 osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
372 osrfStringArray* classes = osrfHashKeys( oilsIDL() );
374 while ( (classname = osrfStringArrayGetString(classes, i++)) ) {
375 osrfHash* class = osrfHashGet( oilsIDL(), classname );
376 osrfHash* fields = osrfHashGet( class, "fields" );
378 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
379 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
383 char* tabledef = getSourceDefinition(class);
385 tabledef = strdup( "(null)" );
387 growing_buffer* sql_buf = buffer_init(32);
388 buffer_fadd( sql_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
392 char* sql = buffer_release(sql_buf);
393 osrfLogDebug(OSRF_LOG_MARK, "%s Investigatory SQL = %s", MODULENAME, sql);
395 dbi_result result = dbi_conn_query(writehandle, sql);
401 const char* columnName;
403 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
405 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
407 /* fetch the fieldmapper index */
408 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
410 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", (char*)columnName);
412 /* determine the field type and storage attributes */
413 type = dbi_result_get_field_type(result, columnName);
414 attr = dbi_result_get_field_attribs(result, columnName);
418 case DBI_TYPE_INTEGER :
420 if ( !osrfHashGet(_f, "primitive") )
421 osrfHashSet(_f,"number", "primitive");
423 if( attr & DBI_INTEGER_SIZE8 )
424 osrfHashSet(_f,"INT8", "datatype");
426 osrfHashSet(_f,"INT", "datatype");
429 case DBI_TYPE_DECIMAL :
430 if ( !osrfHashGet(_f, "primitive") )
431 osrfHashSet(_f,"number", "primitive");
433 osrfHashSet(_f,"NUMERIC", "datatype");
436 case DBI_TYPE_STRING :
437 if ( !osrfHashGet(_f, "primitive") )
438 osrfHashSet(_f,"string", "primitive");
439 osrfHashSet(_f,"TEXT", "datatype");
442 case DBI_TYPE_DATETIME :
443 if ( !osrfHashGet(_f, "primitive") )
444 osrfHashSet(_f,"string", "primitive");
446 osrfHashSet(_f,"TIMESTAMP", "datatype");
449 case DBI_TYPE_BINARY :
450 if ( !osrfHashGet(_f, "primitive") )
451 osrfHashSet(_f,"string", "primitive");
453 osrfHashSet(_f,"BYTEA", "datatype");
458 "Setting [%s] to primitive [%s] and datatype [%s]...",
460 osrfHashGet(_f, "primitive"),
461 osrfHashGet(_f, "datatype")
465 dbi_result_free(result);
467 osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", (char*)classname);
471 osrfStringArrayFree(classes);
473 child_initialized = 1;
478 This function is a sleazy hack intended *only* for testing and
479 debugging. Any real server process should initialize the
480 database connection by calling osrfAppChildInit().
482 void set_cstore_dbi_conn( dbi_conn conn ) {
483 dbhandle = writehandle = conn;
486 void userDataFree( void* blob ) {
487 osrfHashFree( (osrfHash*)blob );
491 static void sessionDataFree( char* key, void* item ) {
492 if (!(strcmp(key,"xact_id"))) {
494 dbi_conn_query(writehandle, "ROLLBACK;");
501 int beginTransaction ( osrfMethodContext* ctx ) {
502 if(osrfMethodVerifyContext( ctx )) {
503 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
508 jsonObject* user = verifyUserPCRUD( ctx );
509 if (!user) return -1;
510 jsonObjectFree(user);
513 dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
515 osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
516 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error starting transaction" );
519 jsonObject* ret = jsonNewObject(ctx->session->session_id);
520 osrfAppRespondComplete( ctx, ret );
523 if (!ctx->session->userData) {
524 ctx->session->userData = osrfNewHash();
525 osrfHashSetCallback((osrfHash*)ctx->session->userData, &sessionDataFree);
528 osrfHashSet( (osrfHash*)ctx->session->userData, strdup( ctx->session->session_id ), "xact_id" );
529 ctx->session->userDataFree = &userDataFree;
535 int setSavepoint ( osrfMethodContext* ctx ) {
536 if(osrfMethodVerifyContext( ctx )) {
537 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
544 jsonObject* user = verifyUserPCRUD( ctx );
545 if (!user) return -1;
546 jsonObjectFree(user);
549 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
550 osrfAppSessionStatus(
552 OSRF_STATUS_INTERNALSERVERERROR,
553 "osrfMethodException",
555 "No active transaction -- required for savepoints"
560 const char* spName = jsonObjectGetString(jsonObjectGetIndex(ctx->params, spNamePos));
562 dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
566 "%s: Error creating savepoint %s in transaction %s",
569 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
571 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
572 "osrfMethodException", ctx->request, "Error creating savepoint" );
575 jsonObject* ret = jsonNewObject(spName);
576 osrfAppRespondComplete( ctx, ret );
582 int releaseSavepoint ( osrfMethodContext* ctx ) {
583 if(osrfMethodVerifyContext( ctx )) {
584 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
591 jsonObject* user = verifyUserPCRUD( ctx );
592 if (!user) return -1;
593 jsonObjectFree(user);
596 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
597 osrfAppSessionStatus(
599 OSRF_STATUS_INTERNALSERVERERROR,
600 "osrfMethodException",
602 "No active transaction -- required for savepoints"
607 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
609 dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
613 "%s: Error releasing savepoint %s in transaction %s",
616 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
618 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
619 "osrfMethodException", ctx->request, "Error releasing savepoint" );
622 jsonObject* ret = jsonNewObject(spName);
623 osrfAppRespondComplete( ctx, ret );
629 int rollbackSavepoint ( osrfMethodContext* ctx ) {
630 if(osrfMethodVerifyContext( ctx )) {
631 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
638 jsonObject* user = verifyUserPCRUD( ctx );
639 if (!user) return -1;
640 jsonObjectFree(user);
643 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
644 osrfAppSessionStatus(
646 OSRF_STATUS_INTERNALSERVERERROR,
647 "osrfMethodException",
649 "No active transaction -- required for savepoints"
654 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
656 dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
660 "%s: Error rolling back savepoint %s in transaction %s",
663 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
665 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
666 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
669 jsonObject* ret = jsonNewObject(spName);
670 osrfAppRespondComplete( ctx, ret );
676 int commitTransaction ( osrfMethodContext* ctx ) {
677 if(osrfMethodVerifyContext( ctx )) {
678 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
683 jsonObject* user = verifyUserPCRUD( ctx );
684 if (!user) return -1;
685 jsonObjectFree(user);
688 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
689 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to commit" );
693 dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
695 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
696 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error committing transaction" );
699 osrfHashRemove(ctx->session->userData, "xact_id");
700 jsonObject* ret = jsonNewObject(ctx->session->session_id);
701 osrfAppRespondComplete( ctx, ret );
707 int rollbackTransaction ( osrfMethodContext* ctx ) {
708 if(osrfMethodVerifyContext( ctx )) {
709 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
714 jsonObject* user = verifyUserPCRUD( ctx );
715 if (!user) return -1;
716 jsonObjectFree(user);
719 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
720 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to roll back" );
724 dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
726 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
727 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error rolling back transaction" );
730 osrfHashRemove(ctx->session->userData, "xact_id");
731 jsonObject* ret = jsonNewObject(ctx->session->session_id);
732 osrfAppRespondComplete( ctx, ret );
738 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
739 if(osrfMethodVerifyContext( ctx )) {
740 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
744 osrfHash* meta = (osrfHash*) ctx->method->userData;
745 osrfHash* class_obj = osrfHashGet( meta, "class" );
749 const char* methodtype = osrfHashGet(meta, "methodtype");
750 jsonObject * obj = NULL;
752 if (!strcmp(methodtype, "create")) {
753 obj = doCreate(ctx, &err);
754 osrfAppRespondComplete( ctx, obj );
756 else if (!strcmp(methodtype, "retrieve")) {
757 obj = doRetrieve(ctx, &err);
758 osrfAppRespondComplete( ctx, obj );
760 else if (!strcmp(methodtype, "update")) {
761 obj = doUpdate(ctx, &err);
762 osrfAppRespondComplete( ctx, obj );
764 else if (!strcmp(methodtype, "delete")) {
765 obj = doDelete(ctx, &err);
766 osrfAppRespondComplete( ctx, obj );
768 else if (!strcmp(methodtype, "search")) {
770 jsonObject* _p = jsonObjectClone( ctx->params );
773 _p = jsonParseString("[]");
774 jsonObjectPush(_p, jsonObjectClone(jsonObjectGetIndex(ctx->params, 1)));
775 jsonObjectPush(_p, jsonObjectClone(jsonObjectGetIndex(ctx->params, 2)));
778 obj = doFieldmapperSearch(ctx, class_obj, _p, &err);
784 jsonIterator* itr = jsonNewIterator( obj );
785 while ((cur = jsonIteratorNext( itr ))) {
787 if(!verifyObjectPCRUD(ctx, cur)) continue;
789 osrfAppRespond( ctx, cur );
791 jsonIteratorFree(itr);
792 osrfAppRespondComplete( ctx, NULL );
794 } else if (!strcmp(methodtype, "id_list")) {
796 jsonObject* _p = jsonObjectClone( ctx->params );
799 _p = jsonParseString("[]");
800 jsonObjectPush(_p, jsonObjectClone(jsonObjectGetIndex(ctx->params, 1)));
801 jsonObjectPush(_p, jsonObjectClone(jsonObjectGetIndex(ctx->params, 2)));
804 if (jsonObjectGetIndex( _p, 1 )) {
805 jsonObjectRemoveKey( jsonObjectGetIndex( _p, 1 ), "select" );
806 jsonObjectRemoveKey( jsonObjectGetIndex( _p, 1 ), "no_i18n" );
807 jsonObjectRemoveKey( jsonObjectGetIndex( _p, 1 ), "flesh" );
808 jsonObjectRemoveKey( jsonObjectGetIndex( _p, 1 ), "flesh_columns" );
810 jsonObjectSetIndex( _p, 1, jsonNewObjectType(JSON_HASH) );
813 jsonObjectSetKey( jsonObjectGetIndex( _p, 1 ), "no_i18n", jsonNewBoolObject( 1 ) );
816 jsonObjectGetIndex( _p, 1 ),
819 "{ \"%s\":[\"%s\"] }",
820 osrfHashGet( class_obj, "classname" ),
821 osrfHashGet( class_obj, "primarykey" )
825 obj = doFieldmapperSearch(ctx, class_obj, _p, &err);
831 jsonIterator* itr = jsonNewIterator( obj );
832 while ((cur = jsonIteratorNext( itr ))) {
834 if(!verifyObjectPCRUD(ctx, cur)) continue;
838 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
841 jsonIteratorFree(itr);
842 osrfAppRespondComplete( ctx, NULL );
845 osrfAppRespondComplete( ctx, obj );
853 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
856 osrfHash* meta = (osrfHash*) ctx->method->userData;
857 osrfHash* class = osrfHashGet( meta, "class" );
859 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
861 growing_buffer* msg = buffer_init(128);
864 "%s: %s method for type %s was passed a %s",
866 osrfHashGet(meta, "methodtype"),
867 osrfHashGet(class, "classname"),
871 char* m = buffer_release(msg);
872 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException", ctx->request, m );
880 ret = verifyObjectPCRUD( ctx, param );
888 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
889 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
890 jsonObject* auth_object = jsonNewObject(auth);
891 jsonObject* user = oilsUtilsQuickReq("open-ils.auth","open-ils.auth.session.retrieve", auth_object);
892 jsonObjectFree(auth_object);
894 if (!user->classname || strcmp(user->classname, "au")) {
896 growing_buffer* msg = buffer_init(128);
899 "%s: permacrud received a bad auth token: %s",
904 char* m = buffer_release(msg);
905 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException", ctx->request, m );
908 jsonObjectFree(user);
916 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
918 dbhandle = writehandle;
920 osrfHash* meta = (osrfHash*) ctx->method->userData;
921 osrfHash* class = osrfHashGet( meta, "class" );
922 char* method_type = strdup( osrfHashGet(meta, "methodtype") );
925 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
927 method_type = strdup("retrieve"); // search and id_list are equivelant to retrieve for this
928 } else if ( *method_type == 'u' || *method_type == 'd' ) {
929 fetch = 1; // MUST go to the db for the object for update and delete
932 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
936 // No permacrud for this method type on this class
938 growing_buffer* msg = buffer_init(128);
941 "%s: %s on class %s has no permacrud IDL entry",
943 osrfHashGet(meta, "methodtype"),
944 osrfHashGet(class, "classname")
947 char* m = buffer_release(msg);
948 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN, "osrfMethodException", ctx->request, m );
955 jsonObject* user = verifyUserPCRUD( ctx );
958 int userid = atoi( oilsFMGetString( user, "id" ) );
959 jsonObjectFree(user);
961 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
962 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
963 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
965 osrfStringArray* context_org_array = osrfNewStringArray(1);
968 char* pkey_value = NULL;
969 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
970 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions required, fetching top of the org tree" );
972 // check for perm at top of org tree
973 jsonObject* _tmp_params = jsonParseString("[{\"parent_ou\":null}]");
974 jsonObject* _list = doFieldmapperSearch(ctx, osrfHashGet( oilsIDL(), "aou" ), _tmp_params, &err);
976 jsonObject* _tree_top = jsonObjectGetIndex(_list, 0);
979 jsonObjectFree(_tmp_params);
980 jsonObjectFree(_list);
982 growing_buffer* msg = buffer_init(128);
983 OSRF_BUFFER_ADD( msg, MODULENAME );
984 OSRF_BUFFER_ADD( msg,
985 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
987 char* m = buffer_release(msg);
988 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
994 osrfStringArrayAdd( context_org_array, oilsFMGetString( _tree_top, "id" ) );
995 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", osrfStringArrayGetString(context_org_array, 0) );
997 jsonObjectFree(_tmp_params);
998 jsonObjectFree(_list);
1001 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, fetching context org ids" );
1002 char* pkey = osrfHashGet(class, "primarykey");
1003 jsonObject *param = NULL;
1005 if (obj->classname) {
1006 pkey_value = oilsFMGetString( obj, pkey );
1007 if (!fetch) param = jsonObjectClone(obj);
1008 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s", pkey_value );
1010 pkey_value = jsonObjectToSimpleString( obj );
1012 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value of %s and retrieving from the database", pkey_value );
1016 jsonObject* _tmp_params = jsonParseStringFmt("[{\"%s\":\"%s\"}]", pkey, pkey_value);
1017 jsonObject* _list = doFieldmapperSearch(
1024 param = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1026 jsonObjectFree(_tmp_params);
1027 jsonObjectFree(_list);
1031 osrfLogDebug( OSRF_LOG_MARK, "Object not found in the database with primary key %s of %s", pkey, pkey_value );
1033 growing_buffer* msg = buffer_init(128);
1036 "%s: no object found with primary key %s of %s",
1042 char* m = buffer_release(msg);
1043 osrfAppSessionStatus(
1045 OSRF_STATUS_INTERNALSERVERERROR,
1046 "osrfMethodException",
1052 if (pkey_value) free(pkey_value);
1057 if (local_context->size > 0) {
1058 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified", local_context->size);
1060 char* lcontext = NULL;
1061 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1062 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1065 "adding class-local field %s (value: %s) to the context org list",
1067 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1072 osrfStringArray* class_list;
1074 if (foreign_context) {
1075 class_list = osrfHashKeys( foreign_context );
1076 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_list->size);
1078 if (class_list->size > 0) {
1081 char* class_name = NULL;
1082 while ( (class_name = osrfStringArrayGetString(class_list, i++)) ) {
1083 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1087 "%d foreign context fields(s) specified for class %s",
1088 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1092 char* foreign_pkey = osrfHashGet(fcontext, "field");
1093 char* foreign_pkey_value = oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1095 jsonObject* _tmp_params = jsonParseStringFmt(
1096 "[{\"%s\":\"%s\"}]",
1101 jsonObject* _list = doFieldmapperSearch(
1103 osrfHashGet( oilsIDL(), class_name ),
1108 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1109 jsonObjectFree(_tmp_params);
1110 jsonObjectFree(_list);
1112 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1114 if (_fparam && jump_list) {
1117 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1118 free(foreign_pkey_value);
1120 osrfHash* foreign_link_hash = oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1122 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1123 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1125 _tmp_params = jsonParseStringFmt(
1126 "[{\"%s\":\"%s\"}]",
1131 _list = doFieldmapperSearch(
1133 osrfHashGet( oilsIDL(), osrfHashGet( foreign_link_hash, "class" ) ),
1138 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1139 jsonObjectFree(_tmp_params);
1140 jsonObjectFree(_list);
1147 growing_buffer* msg = buffer_init(128);
1150 "%s: no object found with primary key %s of %s",
1156 char* m = buffer_release(msg);
1157 osrfAppSessionStatus(
1159 OSRF_STATUS_INTERNALSERVERERROR,
1160 "osrfMethodException",
1166 osrfStringArrayFree(class_list);
1167 free(foreign_pkey_value);
1168 jsonObjectFree(param);
1173 free(foreign_pkey_value);
1176 char* foreign_field = NULL;
1177 while ( (foreign_field = osrfStringArrayGetString(osrfHashGet(fcontext,"context"), j++)) ) {
1178 osrfStringArrayAdd( context_org_array, oilsFMGetString( _fparam, foreign_field ) );
1181 "adding foreign class %s field %s (value: %s) to the context org list",
1184 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1188 jsonObjectFree(_fparam);
1191 osrfStringArrayFree(class_list);
1195 jsonObjectFree(param);
1198 char* context_org = NULL;
1202 if (permission->size == 0) {
1203 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1208 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1210 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1216 "Checking object permission [%s] for user %d on object %s (class %s) at org %d",
1220 osrfHashGet(class, "classname"),
1224 result = dbi_conn_queryf(
1226 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1229 osrfHashGet(class, "classname"),
1237 "Recieved a result for object permission [%s] for user %d on object %s (class %s) at org %d",
1241 osrfHashGet(class, "classname"),
1245 if (dbi_result_first_row(result)) {
1246 jsonObject* return_val = oilsMakeJSONFromResult( result );
1247 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1251 "Status of object permission [%s] for user %d on object %s (class %s) at org %d is %s",
1255 osrfHashGet(class, "classname"),
1260 if ( *has_perm == 't' ) OK = 1;
1261 jsonObjectFree(return_val);
1264 dbi_result_free(result);
1269 osrfLogDebug( OSRF_LOG_MARK, "Checking non-object permission [%s] for user %d at org %d", perm, userid, atoi(context_org) );
1270 result = dbi_conn_queryf(
1272 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1279 osrfLogDebug( OSRF_LOG_MARK, "Received a result for permission [%s] for user %d at org %d",
1280 perm, userid, atoi(context_org) );
1281 if ( dbi_result_first_row(result) ) {
1282 jsonObject* return_val = oilsMakeJSONFromResult( result );
1283 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1284 osrfLogDebug( OSRF_LOG_MARK, "Status of permission [%s] for user %d at org %d is [%s]",
1285 perm, userid, atoi(context_org), has_perm );
1286 if ( *has_perm == 't' ) OK = 1;
1287 jsonObjectFree(return_val);
1290 dbi_result_free(result);
1298 if (pkey_value) free(pkey_value);
1299 osrfStringArrayFree(context_org_array);
1306 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1308 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1310 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1311 jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1313 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1314 jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1317 if (!verifyObjectClass(ctx, target)) {
1322 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1324 char* trans_id = NULL;
1325 if( ctx->session && ctx->session->userData )
1326 trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
1329 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1331 osrfAppSessionStatus(
1333 OSRF_STATUS_BADREQUEST,
1334 "osrfMethodException",
1336 "No active transaction -- required for CREATE"
1342 // The following test is harmless but redundant. If a class is
1343 // readonly, we don't register a create method for it.
1344 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1345 osrfAppSessionStatus(
1347 OSRF_STATUS_BADREQUEST,
1348 "osrfMethodException",
1350 "Cannot INSERT readonly class"
1356 // Set the last_xact_id
1357 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1359 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
1360 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1363 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1365 dbhandle = writehandle;
1367 osrfHash* fields = osrfHashGet(meta, "fields");
1368 char* pkey = osrfHashGet(meta, "primarykey");
1369 char* seq = osrfHashGet(meta, "sequence");
1371 growing_buffer* table_buf = buffer_init(128);
1372 growing_buffer* col_buf = buffer_init(128);
1373 growing_buffer* val_buf = buffer_init(128);
1375 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1376 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1377 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1378 buffer_add(val_buf,"VALUES (");
1384 osrfStringArray* field_list = osrfHashKeys( fields );
1385 while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
1387 osrfHash* field = osrfHashGet( fields, field_name );
1389 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1392 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1395 if (field_object && field_object->classname) {
1396 value = oilsFMGetString(
1398 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1401 value = jsonObjectToSimpleString( field_object );
1408 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1409 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1412 buffer_add(col_buf, field_name);
1414 if (!field_object || field_object->type == JSON_NULL) {
1415 buffer_add( val_buf, "DEFAULT" );
1417 } else if ( !strcmp(get_primitive( field ), "number") ) {
1418 const char* numtype = get_datatype( field );
1419 if ( !strcmp( numtype, "INT8") ) {
1420 buffer_fadd( val_buf, "%lld", atoll(value) );
1422 } else if ( !strcmp( numtype, "INT") ) {
1423 buffer_fadd( val_buf, "%d", atoi(value) );
1425 } else if ( !strcmp( numtype, "NUMERIC") ) {
1426 buffer_fadd( val_buf, "%f", atof(value) );
1429 if ( dbi_conn_quote_string(writehandle, &value) ) {
1430 OSRF_BUFFER_ADD( val_buf, value );
1433 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1434 osrfAppSessionStatus(
1436 OSRF_STATUS_INTERNALSERVERERROR,
1437 "osrfMethodException",
1439 "Error quoting string -- please see the error log for more details"
1442 buffer_free(table_buf);
1443 buffer_free(col_buf);
1444 buffer_free(val_buf);
1455 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1456 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1458 char* table_str = buffer_release(table_buf);
1459 char* col_str = buffer_release(col_buf);
1460 char* val_str = buffer_release(val_buf);
1461 growing_buffer* sql = buffer_init(128);
1462 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1467 char* query = buffer_release(sql);
1469 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1472 dbi_result result = dbi_conn_query(writehandle, query);
1474 jsonObject* obj = NULL;
1477 obj = jsonNewObject(NULL);
1480 "%s ERROR inserting %s object using query [%s]",
1482 osrfHashGet(meta, "fieldmapper"),
1485 osrfAppSessionStatus(
1487 OSRF_STATUS_INTERNALSERVERERROR,
1488 "osrfMethodException",
1490 "INSERT error -- please see the error log for more details"
1495 char* id = oilsFMGetString(target, pkey);
1497 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1498 growing_buffer* _id = buffer_init(10);
1499 buffer_fadd(_id, "%lld", new_id);
1500 id = buffer_release(_id);
1503 // Find quietness specification, if present
1504 const char* quiet_str = NULL;
1506 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1508 quiet_str = jsonObjectGetString( quiet_obj );
1511 if( str_is_true( quiet_str ) ) { // if quietness is specified
1512 obj = jsonNewObject(id);
1516 jsonObject* fake_params = jsonNewObjectType(JSON_ARRAY);
1517 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH));
1520 jsonObjectGetIndex(fake_params, 0),
1525 jsonObject* list = doFieldmapperSearch( ctx,meta, fake_params, err);
1528 jsonObjectFree( fake_params );
1531 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1534 jsonObjectFree( list );
1535 jsonObjectFree( fake_params );
1548 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1558 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1560 const char* id = jsonObjectGetString(jsonObjectGetIndex(ctx->params, id_pos));
1561 jsonObject* order_hash = jsonObjectGetIndex(ctx->params, order_pos);
1565 "%s retrieving %s object with primary key value of %s",
1567 osrfHashGet(meta, "fieldmapper"),
1571 jsonObject* fake_params = jsonNewObjectType(JSON_ARRAY);
1572 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH));
1575 jsonObjectGetIndex(fake_params, 0),
1576 osrfHashGet(meta, "primarykey"),
1577 jsonObjectClone(jsonObjectGetIndex(ctx->params, id_pos))
1581 if (order_hash) jsonObjectPush(fake_params, jsonObjectClone(order_hash) );
1583 jsonObject* list = doFieldmapperSearch( ctx,meta, fake_params, err);
1586 jsonObjectFree( fake_params );
1590 jsonObject* obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1592 jsonObjectFree( list );
1593 jsonObjectFree( fake_params );
1596 if(!verifyObjectPCRUD(ctx, obj)) {
1597 jsonObjectFree(obj);
1600 growing_buffer* msg = buffer_init(128);
1601 OSRF_BUFFER_ADD( msg, MODULENAME );
1602 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1604 char* m = buffer_release(msg);
1605 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1616 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1617 growing_buffer* val_buf = buffer_init(32);
1618 const char* numtype = get_datatype( field );
1620 if ( !strncmp( numtype, "INT", 3 ) ) {
1621 if (value->type == JSON_NUMBER) buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1623 const char* val_str = jsonObjectGetString( value );
1624 buffer_fadd( val_buf, "%ld", atol(val_str) );
1627 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
1628 if (value->type == JSON_NUMBER) buffer_fadd( val_buf, "%f", jsonObjectGetNumber(value) );
1630 const char* val_str = jsonObjectGetString( value );
1631 buffer_fadd( val_buf, "%f", atof(val_str) );
1635 // Presumably this was really intended ot be a string, so quote it
1636 char* str = jsonObjectToSimpleString( value );
1637 if ( dbi_conn_quote_string(dbhandle, &str) ) {
1638 OSRF_BUFFER_ADD( val_buf, str );
1641 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
1643 buffer_free(val_buf);
1648 return buffer_release(val_buf);
1651 static char* searchINPredicate (const char* class, osrfHash* field,
1652 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1653 growing_buffer* sql_buf = buffer_init(32);
1659 osrfHashGet(field, "name")
1663 buffer_add(sql_buf, "IN (");
1664 } else if (!(strcasecmp(op,"not in"))) {
1665 buffer_add(sql_buf, "NOT IN (");
1667 buffer_add(sql_buf, "IN (");
1670 if (node->type == JSON_HASH) {
1671 // subquery predicate
1672 char* subpred = SELECT(
1674 jsonObjectGetKey( node, "select" ),
1675 jsonObjectGetKey( node, "from" ),
1676 jsonObjectGetKey( node, "where" ),
1677 jsonObjectGetKey( node, "having" ),
1678 jsonObjectGetKey( node, "order_by" ),
1679 jsonObjectGetKey( node, "limit" ),
1680 jsonObjectGetKey( node, "offset" ),
1684 buffer_add(sql_buf, subpred);
1687 } else if (node->type == JSON_ARRAY) {
1688 // literal value list
1689 int in_item_index = 0;
1690 int in_item_first = 1;
1691 const jsonObject* in_item;
1692 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1697 buffer_add(sql_buf, ", ");
1700 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1701 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1702 MODULENAME, json_type( in_item->type ) );
1703 buffer_free(sql_buf);
1707 // Append the literal value -- quoted if not a number
1708 if ( JSON_NUMBER == in_item->type ) {
1709 char* val = jsonNumberToDBString( field, in_item );
1710 OSRF_BUFFER_ADD( sql_buf, val );
1713 } else if ( !strcmp( get_primitive( field ), "number") ) {
1714 char* val = jsonNumberToDBString( field, in_item );
1715 OSRF_BUFFER_ADD( sql_buf, val );
1719 char* key_string = jsonObjectToSimpleString(in_item);
1720 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1721 OSRF_BUFFER_ADD( sql_buf, key_string );
1724 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1726 buffer_free(sql_buf);
1732 if( in_item_first ) {
1733 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1734 buffer_free( sql_buf );
1738 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1739 MODULENAME, json_type( node->type ) );
1740 buffer_free(sql_buf);
1744 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1746 return buffer_release(sql_buf);
1749 // Receive a JSON_ARRAY representing a function call. The first
1750 // entry in the array is the function name. The rest are parameters.
1751 static char* searchValueTransform( const jsonObject* array ) {
1753 if( array->size < 1 ) {
1754 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
1758 growing_buffer* sql_buf = buffer_init(32);
1760 // Get the function name
1761 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
1762 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1763 OSRF_BUFFER_ADD( sql_buf, "( " );
1765 // Get the parameters
1766 int func_item_index = 1; // We already grabbed the zeroth entry
1767 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1769 // Add a separator comma, if we need one
1770 if( func_item_index > 2 )
1771 buffer_add( sql_buf, ", " );
1773 // Add the current parameter
1774 if (func_item->type == JSON_NULL) {
1775 buffer_add( sql_buf, "NULL" );
1777 char* val = jsonObjectToSimpleString(func_item);
1778 if ( dbi_conn_quote_string(dbhandle, &val) ) {
1779 OSRF_BUFFER_ADD( sql_buf, val );
1782 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1783 buffer_free(sql_buf);
1790 buffer_add( sql_buf, " )" );
1792 return buffer_release(sql_buf);
1795 static char* searchFunctionPredicate (const char* class, osrfHash* field,
1796 const jsonObject* node, const char* op) {
1798 if( ! is_good_operator( op ) ) {
1799 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
1803 char* val = searchValueTransform(node);
1807 growing_buffer* sql_buf = buffer_init(32);
1812 osrfHashGet(field, "name"),
1819 return buffer_release(sql_buf);
1822 // class is a class name
1823 // field is a field definition as stored in the IDL
1824 // node comes from the method parameter, and represents an entry in the SELECT list
1825 static char* searchFieldTransform (const char* class, osrfHash* field, const jsonObject* node) {
1826 growing_buffer* sql_buf = buffer_init(32);
1828 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
1829 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
1831 if(transform_subcolumn)
1832 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
1834 if (field_transform) {
1835 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class, osrfHashGet(field, "name"));
1836 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
1839 if( array->type != JSON_ARRAY ) {
1840 osrfLogError( OSRF_LOG_MARK,
1841 "%s: Expected JSON_ARRAY for function params; found %s",
1842 MODULENAME, json_type( array->type ) );
1843 buffer_free( sql_buf );
1846 int func_item_index = 0;
1847 jsonObject* func_item;
1848 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1850 char* val = jsonObjectToSimpleString(func_item);
1853 buffer_add( sql_buf, ",NULL" );
1854 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
1855 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
1856 OSRF_BUFFER_ADD( sql_buf, val );
1858 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1860 buffer_free(sql_buf);
1867 buffer_add( sql_buf, " )" );
1870 buffer_fadd( sql_buf, "\"%s\".%s", class, osrfHashGet(field, "name"));
1873 if (transform_subcolumn)
1874 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
1876 return buffer_release(sql_buf);
1879 static char* searchFieldTransformPredicate (const char* class, osrfHash* field,
1880 const jsonObject* node, const char* node_key) {
1881 char* field_transform = searchFieldTransform( class, field, node );
1884 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
1885 if ( ! value_obj ) {
1886 value = searchWHERE( node, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1888 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME, value);
1889 free(field_transform);
1892 } else if ( value_obj->type == JSON_ARRAY ) {
1893 value = searchValueTransform( value_obj );
1895 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform",
1897 free( field_transform );
1900 } else if ( value_obj->type == JSON_HASH ) {
1901 value = searchWHERE( value_obj, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1903 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME, value);
1904 free(field_transform);
1907 } else if ( value_obj->type == JSON_NUMBER ) {
1908 value = jsonNumberToDBString( field, value_obj );
1909 } else if ( value_obj->type != JSON_NULL ) {
1910 if ( !strcmp( get_primitive( field ), "number") ) {
1911 value = jsonNumberToDBString( field, value_obj );
1913 value = jsonObjectToSimpleString( value_obj );
1914 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
1915 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
1917 free(field_transform);
1923 growing_buffer* sql_buf = buffer_init(32);
1934 free(field_transform);
1936 return buffer_release(sql_buf);
1939 static char* searchSimplePredicate (const char* op, const char* class,
1940 osrfHash* field, const jsonObject* node) {
1942 if( ! is_good_operator( op ) ) {
1943 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
1949 // Get the value to which we are comparing the specified column
1950 if (node->type != JSON_NULL) {
1951 if ( node->type == JSON_NUMBER ) {
1952 val = jsonNumberToDBString( field, node );
1953 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
1954 val = jsonNumberToDBString( field, node );
1956 val = jsonObjectToSimpleString(node);
1961 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
1962 // Value is not numeric; enclose it in quotes
1963 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
1964 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
1970 // Compare to a null value
1971 val = strdup( "NULL" );
1972 if (strcmp( op, "=" ))
1978 growing_buffer* sql_buf = buffer_init(32);
1979 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class, osrfHashGet(field, "name"), op, val );
1980 char* pred = buffer_release( sql_buf );
1987 static char* searchBETWEENPredicate (const char* class, osrfHash* field, const jsonObject* node) {
1989 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
1990 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
1992 if( NULL == y_node ) {
1993 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
1996 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
1997 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2004 if ( !strcmp( get_primitive( field ), "number") ) {
2005 x_string = jsonNumberToDBString(field, x_node);
2006 y_string = jsonNumberToDBString(field, y_node);
2009 x_string = jsonObjectToSimpleString(x_node);
2010 y_string = jsonObjectToSimpleString(y_node);
2011 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2012 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2013 MODULENAME, x_string, y_string);
2020 growing_buffer* sql_buf = buffer_init(32);
2021 buffer_fadd( sql_buf, "%s BETWEEN %s AND %s", osrfHashGet(field, "name"), x_string, y_string );
2025 return buffer_release(sql_buf);
2028 static char* searchPredicate ( const char* class, osrfHash* field,
2029 jsonObject* node, osrfMethodContext* ctx ) {
2032 if (node->type == JSON_ARRAY) { // equality IN search
2033 pred = searchINPredicate( class, field, node, NULL, ctx );
2034 } else if (node->type == JSON_HASH) { // non-equality search
2035 jsonIterator* pred_itr = jsonNewIterator( node );
2036 if( !jsonIteratorHasNext( pred_itr ) ) {
2037 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2038 MODULENAME, osrfHashGet(field, "name") );
2040 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2042 // Verify that there are no additional predicates
2043 if( jsonIteratorHasNext( pred_itr ) ) {
2044 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2045 MODULENAME, osrfHashGet(field, "name") );
2046 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2047 pred = searchBETWEENPredicate( class, field, pred_node );
2048 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2049 pred = searchINPredicate( class, field, pred_node, pred_itr->key, ctx );
2050 else if ( pred_node->type == JSON_ARRAY )
2051 pred = searchFunctionPredicate( class, field, pred_node, pred_itr->key );
2052 else if ( pred_node->type == JSON_HASH )
2053 pred = searchFieldTransformPredicate( class, field, pred_node, pred_itr->key );
2055 pred = searchSimplePredicate( pred_itr->key, class, field, pred_node );
2057 jsonIteratorFree(pred_itr);
2059 } else if (node->type == JSON_NULL) { // IS NULL search
2060 growing_buffer* _p = buffer_init(64);
2063 "\"%s\".%s IS NULL",
2065 osrfHashGet(field, "name")
2067 pred = buffer_release(_p);
2068 } else { // equality search
2069 pred = searchSimplePredicate( "=", class, field, node );
2088 field : call_number,
2104 static char* searchJOIN ( const jsonObject* join_hash, osrfHash* leftmeta ) {
2106 const jsonObject* working_hash;
2107 jsonObject* freeable_hash = NULL;
2109 if (join_hash->type == JSON_STRING) {
2110 // create a wrapper around a copy of the original
2111 const char* _tmp = jsonObjectGetString( join_hash );
2112 freeable_hash = jsonNewObjectType(JSON_HASH);
2113 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2114 working_hash = freeable_hash;
2117 if( join_hash->type != JSON_HASH ) {
2120 "%s: JOIN failed; expected JSON object type not found",
2125 working_hash = join_hash;
2128 growing_buffer* join_buf = buffer_init(128);
2129 const char* leftclass = osrfHashGet(leftmeta, "classname");
2131 jsonObject* snode = NULL;
2132 jsonIterator* search_itr = jsonNewIterator( working_hash );
2134 while ( (snode = jsonIteratorNext( search_itr )) ) {
2135 const char* class = search_itr->key;
2136 osrfHash* idlClass = osrfHashGet( oilsIDL(), class );
2140 "%s: JOIN failed. No class \"%s\" defined in IDL",
2144 jsonIteratorFree( search_itr );
2145 buffer_free( join_buf );
2147 jsonObjectFree( freeable_hash );
2151 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2152 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2154 if (field && !fkey) {
2155 fkey = (const char*)oilsIDLFindPath("/%s/links/%s/key", class, field);
2159 "%s: JOIN failed. No link defined from %s.%s to %s",
2165 buffer_free(join_buf);
2167 jsonObjectFree(freeable_hash);
2168 jsonIteratorFree(search_itr);
2172 } else if (!field && fkey) {
2173 field = (const char*)oilsIDLFindPath("/%s/links/%s/key", leftclass, fkey );
2177 "%s: JOIN failed. No link defined from %s.%s to %s",
2183 buffer_free(join_buf);
2185 jsonObjectFree(freeable_hash);
2186 jsonIteratorFree(search_itr);
2191 } else if (!field && !fkey) {
2192 osrfHash* _links = oilsIDL_links( leftclass );
2194 // For each link defined for the left class:
2195 // see if the link references the joined class
2196 osrfHashIterator* itr = osrfNewHashIterator( _links );
2197 osrfHash* curr_link = NULL;
2198 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2199 const char* other_class = osrfHashGet( curr_link, "class" );
2200 if( other_class && !strcmp( other_class, class ) ) {
2202 // Found a link between the classes
2203 fkey = osrfHashIteratorKey( itr );
2204 field = osrfHashGet( curr_link, "key" );
2208 osrfHashIteratorFree( itr );
2210 if (!field || !fkey) {
2211 // Do another such search, with the classes reversed
2212 _links = oilsIDL_links( class );
2214 // For each link defined for the joined class:
2215 // see if the link references the left class
2216 osrfHashIterator* itr = osrfNewHashIterator( _links );
2217 osrfHash* curr_link = NULL;
2218 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2219 const char* other_class = osrfHashGet( curr_link, "class" );
2220 if( other_class && !strcmp( other_class, leftclass ) ) {
2222 // Found a link between the classes
2223 fkey = osrfHashIteratorKey( itr );
2224 field = osrfHashGet( curr_link, "key" );
2228 osrfHashIteratorFree( itr );
2231 if (!field || !fkey) {
2234 "%s: JOIN failed. No link defined between %s and %s",
2239 buffer_free(join_buf);
2241 jsonObjectFree(freeable_hash);
2242 jsonIteratorFree(search_itr);
2248 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2250 if ( !strcasecmp(type,"left") ) {
2251 buffer_add(join_buf, " LEFT JOIN");
2252 } else if ( !strcasecmp(type,"right") ) {
2253 buffer_add(join_buf, " RIGHT JOIN");
2254 } else if ( !strcasecmp(type,"full") ) {
2255 buffer_add(join_buf, " FULL JOIN");
2257 buffer_add(join_buf, " INNER JOIN");
2260 buffer_add(join_buf, " INNER JOIN");
2263 char* table = getSourceDefinition(idlClass);
2265 jsonIteratorFree( search_itr );
2266 buffer_free( join_buf );
2268 jsonObjectFree( freeable_hash );
2272 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2273 table, class, class, field, leftclass, fkey);
2276 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2278 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2279 if ( filter_op && !strcasecmp("or",filter_op) ) {
2280 buffer_add( join_buf, " OR " );
2282 buffer_add( join_buf, " AND " );
2285 char* jpred = searchWHERE( filter, idlClass, AND_OP_JOIN, NULL );
2287 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2288 OSRF_BUFFER_ADD( join_buf, jpred );
2293 "%s: JOIN failed. Invalid conditional expression.",
2296 jsonIteratorFree( search_itr );
2297 buffer_free( join_buf );
2299 jsonObjectFree( freeable_hash );
2304 buffer_add(join_buf, " ) ");
2306 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2308 char* jpred = searchJOIN( join_filter, idlClass );
2309 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2310 OSRF_BUFFER_ADD( join_buf, jpred );
2316 jsonObjectFree(freeable_hash);
2317 jsonIteratorFree(search_itr);
2319 return buffer_release(join_buf);
2324 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2325 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2326 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2328 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2330 search_hash is the JSON expression of the conditions.
2331 meta is the class definition from the IDL, for the relevant table.
2332 opjoin_type indicates whether multiple conditions, if present, should be
2333 connected by AND or OR.
2334 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2335 to pass it to other functions -- and all they do with it is to use the session
2336 and request members to send error messages back to the client.
2340 static char* searchWHERE ( const jsonObject* search_hash, osrfHash* meta, int opjoin_type, osrfMethodContext* ctx ) {
2344 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2352 growing_buffer* sql_buf = buffer_init(128);
2354 jsonObject* node = NULL;
2357 if ( search_hash->type == JSON_ARRAY ) {
2358 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2359 jsonIterator* search_itr = jsonNewIterator( search_hash );
2360 if( !jsonIteratorHasNext( search_itr ) ) {
2363 "%s: Invalid predicate structure: empty JSON array",
2366 jsonIteratorFree( search_itr );
2367 buffer_free( sql_buf );
2371 while ( (node = jsonIteratorNext( search_itr )) ) {
2375 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2376 else buffer_add(sql_buf, " AND ");
2379 char* subpred = searchWHERE( node, meta, opjoin_type, ctx );
2381 buffer_fadd(sql_buf, "( %s )", subpred);
2384 jsonIteratorFree( search_itr );
2385 buffer_free( sql_buf );
2389 jsonIteratorFree(search_itr);
2391 } else if ( search_hash->type == JSON_HASH ) {
2392 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2393 jsonIterator* search_itr = jsonNewIterator( search_hash );
2394 if( !jsonIteratorHasNext( search_itr ) ) {
2397 "%s: Invalid predicate structure: empty JSON object",
2400 jsonIteratorFree( search_itr );
2401 buffer_free( sql_buf );
2405 while ( (node = jsonIteratorNext( search_itr )) ) {
2410 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2411 else buffer_add(sql_buf, " AND ");
2414 if ( '+' == search_itr->key[ 0 ] ) {
2415 if ( node->type == JSON_STRING ) {
2416 // Intended purpose; to allow reference to a Boolean column
2418 // Verify that the class alias is not empty
2419 if( '\0' == search_itr->key[ 1 ] ) {
2422 "%s: Table alias is empty",
2425 jsonIteratorFree( search_itr );
2426 buffer_free( sql_buf );
2430 // Verify that the string looks like an identifier.
2431 const char* subpred = jsonObjectGetString( node );
2432 if( ! is_identifier( subpred ) ) {
2435 "%s: Invalid boolean identifier in WHERE clause: \"%s\"",
2439 jsonIteratorFree( search_itr );
2440 buffer_free( sql_buf );
2444 buffer_fadd(sql_buf, " \"%s\".%s ", search_itr->key + 1, subpred);
2446 char* subpred = searchWHERE( node, osrfHashGet( oilsIDL(), search_itr->key + 1 ), AND_OP_JOIN, ctx );
2448 buffer_fadd(sql_buf, "( %s )", subpred);
2451 jsonIteratorFree( search_itr );
2452 buffer_free( sql_buf );
2456 } else if ( !strcasecmp("-or",search_itr->key) ) {
2457 char* subpred = searchWHERE( node, meta, OR_OP_JOIN, ctx );
2459 buffer_fadd(sql_buf, "( %s )", subpred);
2462 buffer_free( sql_buf );
2465 } else if ( !strcasecmp("-and",search_itr->key) ) {
2466 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2468 buffer_fadd(sql_buf, "( %s )", subpred);
2471 buffer_free( sql_buf );
2474 } else if ( !strcasecmp("-not",search_itr->key) ) {
2475 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2477 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2480 buffer_free( sql_buf );
2483 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2484 char* subpred = SELECT(
2486 jsonObjectGetKey( node, "select" ),
2487 jsonObjectGetKey( node, "from" ),
2488 jsonObjectGetKey( node, "where" ),
2489 jsonObjectGetKey( node, "having" ),
2490 jsonObjectGetKey( node, "order_by" ),
2491 jsonObjectGetKey( node, "limit" ),
2492 jsonObjectGetKey( node, "offset" ),
2496 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2498 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2499 char* subpred = SELECT(
2501 jsonObjectGetKey( node, "select" ),
2502 jsonObjectGetKey( node, "from" ),
2503 jsonObjectGetKey( node, "where" ),
2504 jsonObjectGetKey( node, "having" ),
2505 jsonObjectGetKey( node, "order_by" ),
2506 jsonObjectGetKey( node, "limit" ),
2507 jsonObjectGetKey( node, "offset" ),
2511 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2515 char* class = osrfHashGet(meta, "classname");
2516 osrfHash* fields = osrfHashGet(meta, "fields");
2517 osrfHash* field = osrfHashGet( fields, search_itr->key );
2521 char* table = getSourceDefinition(meta);
2523 table = strdup( "(?)" );
2526 "%s: Attempt to reference non-existent column %s on %s (%s)",
2532 buffer_free(sql_buf);
2534 jsonIteratorFree(search_itr);
2538 char* subpred = searchPredicate( class, field, node, ctx );
2540 buffer_add( sql_buf, subpred );
2543 buffer_free(sql_buf);
2544 jsonIteratorFree(search_itr);
2549 jsonIteratorFree(search_itr);
2552 // ERROR ... only hash and array allowed at this level
2553 char* predicate_string = jsonObjectToJSON( search_hash );
2556 "%s: Invalid predicate structure: %s",
2560 buffer_free(sql_buf);
2561 free(predicate_string);
2565 return buffer_release(sql_buf);
2569 /* method context */ osrfMethodContext* ctx,
2571 /* SELECT */ jsonObject* selhash,
2572 /* FROM */ jsonObject* join_hash,
2573 /* WHERE */ jsonObject* search_hash,
2574 /* HAVING */ jsonObject* having_hash,
2575 /* ORDER BY */ jsonObject* order_hash,
2576 /* LIMIT */ jsonObject* limit,
2577 /* OFFSET */ jsonObject* offset,
2578 /* flags */ int flags
2580 const char* locale = osrf_message_get_last_locale();
2582 // in case we don't get a select list
2583 jsonObject* defaultselhash = NULL;
2585 // general tmp objects
2586 const jsonObject* tmp_const;
2587 jsonObject* selclass = NULL;
2588 jsonObject* selfield = NULL;
2589 jsonObject* snode = NULL;
2590 jsonObject* onode = NULL;
2592 char* string = NULL;
2593 int from_function = 0;
2598 // the core search class
2599 char* core_class = NULL;
2601 // metadata about the core search class
2602 osrfHash* core_meta = NULL;
2604 // punt if there's no core class
2605 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2608 "%s: FROM clause is missing or empty",
2612 osrfAppSessionStatus(
2614 OSRF_STATUS_INTERNALSERVERERROR,
2615 "osrfMethodException",
2617 "FROM clause is missing or empty in JSON query"
2622 // get the core class -- the only key of the top level FROM clause, or a string
2623 if (join_hash->type == JSON_HASH) {
2624 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2625 snode = jsonIteratorNext( tmp_itr );
2627 core_class = strdup( tmp_itr->key );
2630 jsonObject* extra = jsonIteratorNext( tmp_itr );
2632 jsonIteratorFree( tmp_itr );
2635 // There shouldn't be more than one entry in join_hash
2639 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2643 osrfAppSessionStatus(
2645 OSRF_STATUS_INTERNALSERVERERROR,
2646 "osrfMethodException",
2648 "Malformed FROM clause in JSON query"
2651 return NULL; // Malformed join_hash; extra entry
2653 } else if (join_hash->type == JSON_ARRAY) {
2655 core_class = jsonObjectToSimpleString( jsonObjectGetIndex(join_hash, 0) );
2658 } else if (join_hash->type == JSON_STRING) {
2659 core_class = jsonObjectToSimpleString( join_hash );
2665 "%s: FROM clause is unexpected JSON type: %s",
2667 json_type( join_hash->type )
2670 osrfAppSessionStatus(
2672 OSRF_STATUS_INTERNALSERVERERROR,
2673 "osrfMethodException",
2675 "Ill-formed FROM clause in JSON query"
2681 if (!from_function) {
2682 // Get the IDL class definition for the core class
2683 core_meta = osrfHashGet( oilsIDL(), core_class );
2684 if( !core_meta ) { // Didn't find it?
2687 "%s: SELECT clause references undefined class: \"%s\"",
2692 osrfAppSessionStatus(
2694 OSRF_STATUS_INTERNALSERVERERROR,
2695 "osrfMethodException",
2697 "SELECT clause references undefined class in JSON query"
2703 // Make sure the class isn't virtual
2704 if( str_is_true( osrfHashGet( core_meta, "virtual" ) ) ) {
2707 "%s: Core class is virtual: \"%s\"",
2712 osrfAppSessionStatus(
2714 OSRF_STATUS_INTERNALSERVERERROR,
2715 "osrfMethodException",
2717 "FROM clause references virtual class in JSON query"
2724 // if the select list is empty, or the core class field list is '*',
2725 // build the default select list ...
2727 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
2728 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2729 } else if( selhash->type != JSON_HASH ) {
2732 "%s: Expected JSON_HASH for SELECT clause; found %s",
2734 json_type( selhash->type )
2738 osrfAppSessionStatus(
2740 OSRF_STATUS_INTERNALSERVERERROR,
2741 "osrfMethodException",
2743 "Malformed SELECT clause in JSON query"
2747 } else if ( (tmp_const = jsonObjectGetKeyConst( selhash, core_class )) && tmp_const->type == JSON_STRING ) {
2748 const char* _x = jsonObjectGetString( tmp_const );
2749 if (!strncmp( "*", _x, 1 )) {
2750 jsonObjectRemoveKey( selhash, core_class );
2751 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2756 growing_buffer* sql_buf = buffer_init(128);
2758 // temp buffer for the SELECT list
2759 growing_buffer* select_buf = buffer_init(128);
2760 growing_buffer* order_buf = buffer_init(128);
2761 growing_buffer* group_buf = buffer_init(128);
2762 growing_buffer* having_buf = buffer_init(128);
2764 int aggregate_found = 0; // boolean
2766 // Build a select list
2767 if(from_function) // From a function we select everything
2768 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
2771 // If we need to build a default list, prepare to do so
2772 jsonObject* _tmp = jsonObjectGetKey( selhash, core_class );
2773 if ( _tmp && !_tmp->size ) {
2775 osrfHash* core_fields = osrfHashGet( core_meta, "fields" );
2777 osrfHashIterator* field_itr = osrfNewHashIterator( core_fields );
2778 osrfHash* field_def;
2779 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2780 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2781 // This field is not virtual, so add it to the list
2782 jsonObjectPush( _tmp, jsonNewObject( osrfHashIteratorKey( field_itr ) ) );
2785 osrfHashIteratorFree( field_itr );
2788 // Now build the actual select list
2792 jsonIterator* selclass_itr = jsonNewIterator( selhash );
2793 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
2795 // Make sure the class is defined in the IDL
2796 const char* cname = selclass_itr->key;
2797 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
2801 "%s: Selected class \"%s\" not defined in IDL",
2807 osrfAppSessionStatus(
2809 OSRF_STATUS_INTERNALSERVERERROR,
2810 "osrfMethodException",
2812 "Selected class is not defined"
2814 jsonIteratorFree( selclass_itr );
2815 buffer_free( sql_buf );
2816 buffer_free( select_buf );
2817 buffer_free( order_buf );
2818 buffer_free( group_buf );
2819 buffer_free( having_buf );
2820 if( defaultselhash ) jsonObjectFree( defaultselhash );
2825 // Make sure the target relation is in the join tree.
2827 // At this point join_hash is a step down from the join_hash we
2828 // received as a parameter. If the original was a JSON_STRING,
2829 // then json_hash is now NULL. If the original was a JSON_HASH,
2830 // then json_hash is now the first (and only) entry in it,
2831 // denoting the core class. We've already excluded the
2832 // possibility that the original was a JSON_ARRAY, because in
2833 // that case from_function would be non-NULL, and we wouldn't
2836 int class_in_from_clause; // boolean
2838 if ( ! strcmp( core_class, cname ))
2839 // This is the core class -- no problem
2840 class_in_from_clause = 1;
2843 // There's only one class in the FROM clause, and this isn't it
2844 class_in_from_clause = 0;
2845 else if (join_hash->type == JSON_STRING) {
2846 // There's only one class in the FROM clause
2847 const char* str = jsonObjectGetString(join_hash);
2848 if ( strcmp( str, cname ) )
2849 class_in_from_clause = 0; // This isn't it
2851 class_in_from_clause = 1; // This is it
2853 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", cname);
2854 if ( 0 == found->size )
2855 class_in_from_clause = 0; // Nowhere in the join tree
2857 class_in_from_clause = 1; // Found it
2858 jsonObjectFree( found );
2862 // If the class isn't in the FROM clause, bail out
2863 if( ! class_in_from_clause ) {
2866 "%s: SELECT clause references class not in FROM clause: \"%s\"",
2871 osrfAppSessionStatus(
2873 OSRF_STATUS_INTERNALSERVERERROR,
2874 "osrfMethodException",
2876 "Selected class not in FROM clause in JSON query"
2878 jsonIteratorFree( selclass_itr );
2879 buffer_free( sql_buf );
2880 buffer_free( select_buf );
2881 buffer_free( order_buf );
2882 buffer_free( group_buf );
2883 buffer_free( having_buf );
2884 if( defaultselhash ) jsonObjectFree( defaultselhash );
2889 // Look up some attributes of the current class, so that we
2890 // don't have to look them up again for each field
2891 osrfHash* class_field_set = osrfHashGet( idlClass, "fields" );
2892 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
2893 const char* class_tname = osrfHashGet( idlClass, "tablename" );
2895 // stitch together the column list ...
2896 jsonIterator* select_itr = jsonNewIterator( selclass );
2897 while ( (selfield = jsonIteratorNext( select_itr )) ) { // for each SELECT column
2899 // If we need a separator comma, add one
2903 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
2906 // ... if it's a string, just toss it on the pile
2907 if (selfield->type == JSON_STRING) {
2909 // Look up the field in the IDL
2910 const char* col_name = jsonObjectGetString( selfield );
2911 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
2913 // No such field in current class
2916 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
2922 osrfAppSessionStatus(
2924 OSRF_STATUS_INTERNALSERVERERROR,
2925 "osrfMethodException",
2927 "Selected column not defined in JSON query"
2929 jsonIteratorFree( select_itr );
2930 jsonIteratorFree( selclass_itr );
2931 buffer_free( sql_buf );
2932 buffer_free( select_buf );
2933 buffer_free( order_buf );
2934 buffer_free( group_buf );
2935 buffer_free( having_buf );
2936 if( defaultselhash ) jsonObjectFree( defaultselhash );
2939 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2940 // Virtual field not allowed
2943 "%s: Selected column \"%s\" for class \"%s\" is virtual",
2949 osrfAppSessionStatus(
2951 OSRF_STATUS_INTERNALSERVERERROR,
2952 "osrfMethodException",
2954 "Selected column may not be virtual in JSON query"
2956 jsonIteratorFree( select_itr );
2957 jsonIteratorFree( selclass_itr );
2958 buffer_free( sql_buf );
2959 buffer_free( select_buf );
2960 buffer_free( order_buf );
2961 buffer_free( group_buf );
2962 buffer_free( having_buf );
2963 if( defaultselhash ) jsonObjectFree( defaultselhash );
2970 if (flags & DISABLE_I18N)
2973 i18n = osrfHashGet(field_def, "i18n");
2975 if( str_is_true( i18n ) ) {
2976 buffer_fadd( select_buf,
2977 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
2978 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
2980 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
2983 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
2986 // ... but it could be an object, in which case we check for a Field Transform
2987 } else if (selfield->type == JSON_HASH) {
2989 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
2991 // Get the field definition from the IDL
2992 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
2994 // No such field in current class
2997 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3003 osrfAppSessionStatus(
3005 OSRF_STATUS_INTERNALSERVERERROR,
3006 "osrfMethodException",
3008 "Selected column is not defined in JSON query"
3010 jsonIteratorFree( select_itr );
3011 jsonIteratorFree( selclass_itr );
3012 buffer_free( sql_buf );
3013 buffer_free( select_buf );
3014 buffer_free( order_buf );
3015 buffer_free( group_buf );
3016 buffer_free( having_buf );
3017 if( defaultselhash ) jsonObjectFree( defaultselhash );
3020 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3021 // No such field in current class
3024 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3030 osrfAppSessionStatus(
3032 OSRF_STATUS_INTERNALSERVERERROR,
3033 "osrfMethodException",
3035 "Selected column is virtual in JSON query"
3037 jsonIteratorFree( select_itr );
3038 jsonIteratorFree( selclass_itr );
3039 buffer_free( sql_buf );
3040 buffer_free( select_buf );
3041 buffer_free( order_buf );
3042 buffer_free( group_buf );
3043 buffer_free( having_buf );
3044 if( defaultselhash ) jsonObjectFree( defaultselhash );
3049 // Decide what to use as a column alias
3051 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3052 _alias = jsonObjectGetString( tmp_const );
3053 } else { // Use field name as the alias
3057 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3058 char* transform_str = searchFieldTransform(cname, field_def, selfield);
3059 if( transform_str ) {
3060 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3061 free(transform_str);
3064 osrfAppSessionStatus(
3066 OSRF_STATUS_INTERNALSERVERERROR,
3067 "osrfMethodException",
3069 "Unable to generate transform function in JSON query"
3071 jsonIteratorFree( select_itr );
3072 jsonIteratorFree( selclass_itr );
3073 buffer_free( sql_buf );
3074 buffer_free( select_buf );
3075 buffer_free( order_buf );
3076 buffer_free( group_buf );
3077 buffer_free( having_buf );
3078 if( defaultselhash ) jsonObjectFree( defaultselhash );
3086 if (flags & DISABLE_I18N)
3089 i18n = osrfHashGet(field_def, "i18n");
3091 if( str_is_true( i18n ) ) {
3092 buffer_fadd( select_buf,
3093 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3094 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3096 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3099 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3106 "%s: Selected item is unexpected JSON type: %s",
3108 json_type( selfield->type )
3111 osrfAppSessionStatus(
3113 OSRF_STATUS_INTERNALSERVERERROR,
3114 "osrfMethodException",
3116 "Ill-formed SELECT item in JSON query"
3118 jsonIteratorFree( select_itr );
3119 jsonIteratorFree( selclass_itr );
3120 buffer_free( sql_buf );
3121 buffer_free( select_buf );
3122 buffer_free( order_buf );
3123 buffer_free( group_buf );
3124 buffer_free( having_buf );
3125 if( defaultselhash ) jsonObjectFree( defaultselhash );
3130 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3131 if( obj_is_true( agg_obj ) )
3132 aggregate_found = 1;
3134 // Append a comma (except for the first one)
3135 // and add the column to a GROUP BY clause
3139 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3141 buffer_fadd(group_buf, " %d", sel_pos);
3145 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3147 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3148 if ( ! obj_is_true( aggregate_obj ) ) {
3152 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3155 buffer_fadd(group_buf, " %d", sel_pos);
3158 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3162 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3165 _column = searchFieldTransform(cname, field, selfield);
3166 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3167 OSRF_BUFFER_ADD(group_buf, _column);
3168 _column = searchFieldTransform(cname, field, selfield);
3175 } // end while -- iterating across SELECT columns
3177 jsonIteratorFree(select_itr);
3178 } // end while -- iterating across classes
3180 jsonIteratorFree(selclass_itr);
3184 char* col_list = buffer_release(select_buf);
3186 if (from_function) table = searchValueTransform(join_hash);
3187 else table = getSourceDefinition(core_meta);
3191 osrfAppSessionStatus(
3193 OSRF_STATUS_INTERNALSERVERERROR,
3194 "osrfMethodException",
3196 "Unable to identify table for core class"
3199 buffer_free( sql_buf );
3200 buffer_free( order_buf );
3201 buffer_free( group_buf );
3202 buffer_free( having_buf );
3203 if( defaultselhash ) jsonObjectFree( defaultselhash );
3208 // Put it all together
3209 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3213 if (!from_function) {
3214 // Now, walk the join tree and add that clause
3216 char* join_clause = searchJOIN( join_hash, core_meta );
3218 buffer_add(sql_buf, join_clause);
3222 osrfAppSessionStatus(
3224 OSRF_STATUS_INTERNALSERVERERROR,
3225 "osrfMethodException",
3227 "Unable to construct JOIN clause(s)"
3229 buffer_free( sql_buf );
3230 buffer_free( order_buf );
3231 buffer_free( group_buf );
3232 buffer_free( having_buf );
3233 if( defaultselhash ) jsonObjectFree( defaultselhash );
3239 // Build a WHERE clause, if there is one
3240 if ( search_hash ) {
3241 buffer_add(sql_buf, " WHERE ");
3243 // and it's on the WHERE clause
3244 char* pred = searchWHERE( search_hash, core_meta, AND_OP_JOIN, ctx );
3247 buffer_add(sql_buf, pred);
3251 osrfAppSessionStatus(
3253 OSRF_STATUS_INTERNALSERVERERROR,
3254 "osrfMethodException",
3256 "Severe query error in WHERE predicate -- see error log for more details"
3260 buffer_free(having_buf);
3261 buffer_free(group_buf);
3262 buffer_free(order_buf);
3263 buffer_free(sql_buf);
3264 if (defaultselhash) jsonObjectFree(defaultselhash);
3269 // Build a HAVING clause, if there is one
3270 if ( having_hash ) {
3271 buffer_add(sql_buf, " HAVING ");
3273 // and it's on the the WHERE clause
3274 char* pred = searchWHERE( having_hash, core_meta, AND_OP_JOIN, ctx );
3277 buffer_add(sql_buf, pred);
3281 osrfAppSessionStatus(
3283 OSRF_STATUS_INTERNALSERVERERROR,
3284 "osrfMethodException",
3286 "Severe query error in HAVING predicate -- see error log for more details"
3290 buffer_free(having_buf);
3291 buffer_free(group_buf);
3292 buffer_free(order_buf);
3293 buffer_free(sql_buf);
3294 if (defaultselhash) jsonObjectFree(defaultselhash);
3299 // Build an ORDER BY clause, if there is one
3301 jsonIterator* class_itr = jsonNewIterator( order_hash );
3302 while ( (snode = jsonIteratorNext( class_itr )) ) {
3304 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
3307 if ( snode->type == JSON_HASH ) {
3309 jsonIterator* order_itr = jsonNewIterator( snode );
3310 while ( (onode = jsonIteratorNext( order_itr )) ) {
3312 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ))
3315 const char* direction = NULL;
3316 if ( onode->type == JSON_HASH ) {
3317 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3318 string = searchFieldTransform(
3320 oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ),
3324 growing_buffer* field_buf = buffer_init(16);
3325 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3326 string = buffer_release(field_buf);
3329 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3330 const char* dir = jsonObjectGetString(tmp_const);
3331 if (!strncasecmp(dir, "d", 1)) {
3332 direction = " DESC";
3339 string = strdup(order_itr->key);
3340 const char* dir = jsonObjectGetString(onode);
3341 if (!strncasecmp(dir, "d", 1)) {
3342 direction = " DESC";
3351 buffer_add(order_buf, ", ");
3354 buffer_add(order_buf, string);
3358 buffer_add(order_buf, direction);
3362 // jsonIteratorFree(order_itr);
3364 } else if ( snode->type == JSON_ARRAY ) {
3366 jsonIterator* order_itr = jsonNewIterator( snode );
3367 while ( (onode = jsonIteratorNext( order_itr )) ) {
3369 const char* _f = jsonObjectGetString( onode );
3371 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, _f))
3377 buffer_add(order_buf, ", ");
3380 buffer_add(order_buf, _f);
3383 // jsonIteratorFree(order_itr);
3386 // IT'S THE OOOOOOOOOOOLD STYLE!
3388 osrfLogError(OSRF_LOG_MARK, "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3390 osrfAppSessionStatus(
3392 OSRF_STATUS_INTERNALSERVERERROR,
3393 "osrfMethodException",
3395 "Severe query error -- see error log for more details"
3400 buffer_free(having_buf);
3401 buffer_free(group_buf);
3402 buffer_free(order_buf);
3403 buffer_free(sql_buf);
3404 if (defaultselhash) jsonObjectFree(defaultselhash);
3405 jsonIteratorFree(class_itr);
3410 // jsonIteratorFree(class_itr);
3414 string = buffer_release(group_buf);
3416 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
3417 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3418 OSRF_BUFFER_ADD( sql_buf, string );
3423 string = buffer_release(having_buf);
3426 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3427 OSRF_BUFFER_ADD( sql_buf, string );
3432 string = buffer_release(order_buf);
3435 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3436 OSRF_BUFFER_ADD( sql_buf, string );
3442 const char* str = jsonObjectGetString(limit);
3443 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
3447 const char* str = jsonObjectGetString(offset);
3448 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
3451 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3454 if (defaultselhash) jsonObjectFree(defaultselhash);
3456 return buffer_release(sql_buf);
3460 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
3462 const char* locale = osrf_message_get_last_locale();
3464 osrfHash* fields = osrfHashGet(meta, "fields");
3465 char* core_class = osrfHashGet(meta, "classname");
3467 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
3469 jsonObject* node = NULL;
3470 jsonObject* snode = NULL;
3471 jsonObject* onode = NULL;
3472 const jsonObject* _tmp = NULL;
3473 jsonObject* selhash = NULL;
3474 jsonObject* defaultselhash = NULL;
3476 growing_buffer* sql_buf = buffer_init(128);
3477 growing_buffer* select_buf = buffer_init(128);
3479 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
3480 defaultselhash = jsonNewObjectType(JSON_HASH);
3481 selhash = defaultselhash;
3484 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
3485 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
3486 jsonObject* flist = jsonObjectGetKey( selhash, core_class );
3491 osrfStringArray* keys = osrfHashKeys( fields );
3492 while ( (field = osrfStringArrayGetString(keys, i++)) ) {
3493 if( ! str_is_true( osrfHashGet( osrfHashGet( fields, field ), "virtual" ) ) )
3494 jsonObjectPush( flist, jsonNewObject( field ) );
3496 osrfStringArrayFree(keys);
3500 jsonIterator* class_itr = jsonNewIterator( selhash );
3501 while ( (snode = jsonIteratorNext( class_itr )) ) {
3503 char* cname = class_itr->key;
3504 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
3505 if (!idlClass) continue;
3507 if (strcmp(core_class,class_itr->key)) {
3508 if (!join_hash) continue;
3510 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
3512 jsonObjectFree(found);
3516 jsonObjectFree(found);
3519 jsonIterator* select_itr = jsonNewIterator( snode );
3520 while ( (node = jsonIteratorNext( select_itr )) ) {
3521 const char* item_str = jsonObjectGetString( node );
3522 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
3523 char* fname = osrfHashGet(field, "name");
3525 if (!field) continue;
3530 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
3535 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
3536 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
3539 i18n = osrfHashGet(field, "i18n");
3541 if( str_is_true( i18n ) ) {
3542 char* pkey = osrfHashGet(idlClass, "primarykey");
3543 char* tname = osrfHashGet(idlClass, "tablename");
3545 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);
3547 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3550 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3554 jsonIteratorFree(select_itr);
3557 jsonIteratorFree(class_itr);
3559 char* col_list = buffer_release(select_buf);
3560 char* table = getSourceDefinition(meta);
3562 table = strdup( "(null)" );
3564 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
3569 char* join_clause = searchJOIN( join_hash, meta );
3570 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
3571 OSRF_BUFFER_ADD(sql_buf, join_clause);
3575 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
3576 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
3578 buffer_add(sql_buf, " WHERE ");
3580 char* pred = searchWHERE( search_hash, meta, AND_OP_JOIN, ctx );
3582 osrfAppSessionStatus(
3584 OSRF_STATUS_INTERNALSERVERERROR,
3585 "osrfMethodException",
3587 "Severe query error -- see error log for more details"
3589 buffer_free(sql_buf);
3590 if(defaultselhash) jsonObjectFree(defaultselhash);
3593 buffer_add(sql_buf, pred);
3598 char* string = NULL;
3599 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
3601 growing_buffer* order_buf = buffer_init(128);
3604 jsonIterator* class_itr = jsonNewIterator( _tmp );
3605 while ( (snode = jsonIteratorNext( class_itr )) ) {
3607 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
3610 if ( snode->type == JSON_HASH ) {
3612 jsonIterator* order_itr = jsonNewIterator( snode );
3613 while ( (onode = jsonIteratorNext( order_itr )) ) {
3615 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ))
3618 char* direction = NULL;
3619 if ( onode->type == JSON_HASH ) {
3620 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3621 string = searchFieldTransform(
3623 oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ),
3627 growing_buffer* field_buf = buffer_init(16);
3628 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3629 string = buffer_release(field_buf);
3632 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
3633 const char* dir = jsonObjectGetString(_tmp);
3634 if (!strncasecmp(dir, "d", 1)) {
3635 direction = " DESC";
3642 string = strdup(order_itr->key);
3643 const char* dir = jsonObjectGetString(onode);
3644 if (!strncasecmp(dir, "d", 1)) {
3645 direction = " DESC";
3654 buffer_add(order_buf, ", ");
3657 buffer_add(order_buf, string);
3661 buffer_add(order_buf, direction);
3666 jsonIteratorFree(order_itr);
3669 const char* str = jsonObjectGetString(snode);
3670 buffer_add(order_buf, str);
3676 jsonIteratorFree(class_itr);
3678 string = buffer_release(order_buf);
3681 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3682 OSRF_BUFFER_ADD( sql_buf, string );
3688 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
3689 const char* str = jsonObjectGetString(_tmp);
3697 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
3699 const char* str = jsonObjectGetString(_tmp);
3708 if (defaultselhash) jsonObjectFree(defaultselhash);
3710 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3711 return buffer_release(sql_buf);
3714 int doJSONSearch ( osrfMethodContext* ctx ) {
3715 if(osrfMethodVerifyContext( ctx )) {
3716 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
3720 osrfLogDebug(OSRF_LOG_MARK, "Recieved query request");
3725 dbhandle = writehandle;
3727 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
3731 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
3732 flags |= SELECT_DISTINCT;
3734 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
3735 flags |= DISABLE_I18N;
3737 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
3740 jsonObjectGetKey( hash, "select" ),
3741 jsonObjectGetKey( hash, "from" ),
3742 jsonObjectGetKey( hash, "where" ),
3743 jsonObjectGetKey( hash, "having" ),
3744 jsonObjectGetKey( hash, "order_by" ),
3745 jsonObjectGetKey( hash, "limit" ),
3746 jsonObjectGetKey( hash, "offset" ),
3755 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
3756 dbi_result result = dbi_conn_query(dbhandle, sql);
3759 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
3761 if (dbi_result_first_row(result)) {
3762 /* JSONify the result */
3763 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
3766 jsonObject* return_val = oilsMakeJSONFromResult( result );
3767 osrfAppRespond( ctx, return_val );
3768 jsonObjectFree( return_val );
3769 } while (dbi_result_next_row(result));
3772 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
3775 osrfAppRespondComplete( ctx, NULL );
3777 /* clean up the query */
3778 dbi_result_free(result);
3782 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
3783 osrfAppSessionStatus(
3785 OSRF_STATUS_INTERNALSERVERERROR,
3786 "osrfMethodException",
3788 "Severe query error -- see error log for more details"
3796 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
3797 const jsonObject* params, int* err ) {
3800 dbhandle = writehandle;
3802 osrfHash* links = osrfHashGet(meta, "links");
3803 osrfHash* fields = osrfHashGet(meta, "fields");
3804 char* core_class = osrfHashGet(meta, "classname");
3805 char* pkey = osrfHashGet(meta, "primarykey");
3807 const jsonObject* _tmp;
3809 jsonObject* search_hash = jsonObjectGetIndex(params, 0);
3810 jsonObject* order_hash = jsonObjectGetIndex(params, 1);
3812 char* sql = buildSELECT( search_hash, order_hash, meta, ctx );
3814 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
3819 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
3821 dbi_result result = dbi_conn_query(dbhandle, sql);
3822 if( NULL == result ) {
3823 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
3824 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
3825 osrfAppSessionStatus(
3827 OSRF_STATUS_INTERNALSERVERERROR,
3828 "osrfMethodException",
3830 "Severe query error -- see error log for more details"
3837 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
3840 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
3841 osrfHash* dedup = osrfNewHash();
3843 if (dbi_result_first_row(result)) {
3844 /* JSONify the result */
3845 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
3847 obj = oilsMakeFieldmapperFromResult( result, meta );
3848 char* pkey_val = oilsFMGetString( obj, pkey );
3849 if ( osrfHashGet( dedup, pkey_val ) ) {
3850 jsonObjectFree(obj);
3853 osrfHashSet( dedup, pkey_val, pkey_val );
3854 jsonObjectPush(res_list, obj);
3856 } while (dbi_result_next_row(result));
3858 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
3862 osrfHashFree(dedup);
3863 /* clean up the query */
3864 dbi_result_free(result);
3867 if (res_list->size && order_hash) {
3868 _tmp = jsonObjectGetKeyConst( order_hash, "flesh" );
3870 int x = (int)jsonObjectGetNumber(_tmp);
3871 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
3873 const jsonObject* temp_blob;
3874 if ((temp_blob = jsonObjectGetKeyConst( order_hash, "flesh_fields" )) && x > 0) {
3876 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
3877 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
3879 osrfStringArray* link_fields = NULL;
3882 if (flesh_fields->size == 1) {
3883 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
3884 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
3889 link_fields = osrfNewStringArray(1);
3890 jsonIterator* _i = jsonNewIterator( flesh_fields );
3891 while ((_f = jsonIteratorNext( _i ))) {
3892 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
3894 jsonIteratorFree(_i);
3899 jsonIterator* itr = jsonNewIterator( res_list );
3900 while ((cur = jsonIteratorNext( itr ))) {
3905 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
3907 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
3909 osrfHash* kid_link = osrfHashGet(links, link_field);
3910 if (!kid_link) continue;
3912 osrfHash* field = osrfHashGet(fields, link_field);
3913 if (!field) continue;
3915 osrfHash* value_field = field;
3917 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
3918 if (!kid_idl) continue;
3920 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
3921 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
3924 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
3925 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
3928 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
3930 if (link_map->size > 0) {
3931 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
3934 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
3939 osrfHashGet(kid_link, "class"),
3946 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
3947 osrfHashGet(kid_link, "field"),
3948 osrfHashGet(kid_link, "class"),
3949 osrfHashGet(kid_link, "key"),
3950 osrfHashGet(kid_link, "reltype")
3953 jsonObject* fake_params = jsonNewObjectType(JSON_ARRAY);
3954 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH)); // search hash
3955 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH)); // order/flesh hash
3957 osrfLogDebug(OSRF_LOG_MARK, "Creating dummy params object...");
3959 const char* search_key = jsonObjectGetString(
3962 atoi( osrfHashGet(value_field, "array_position") )
3967 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
3972 jsonObjectGetIndex(fake_params, 0),
3973 osrfHashGet(kid_link, "key"),
3974 jsonNewObject( search_key )
3978 jsonObjectGetIndex(fake_params, 1),
3980 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
3984 jsonObjectSetKey( jsonObjectGetIndex(fake_params, 1), "flesh_fields", jsonObjectClone(flesh_blob) );
3986 if (jsonObjectGetKeyConst(order_hash, "order_by")) {
3988 jsonObjectGetIndex(fake_params, 1),
3990 jsonObjectClone(jsonObjectGetKeyConst(order_hash, "order_by"))
3994 if (jsonObjectGetKeyConst(order_hash, "select")) {
3996 jsonObjectGetIndex(fake_params, 1),
3998 jsonObjectClone(jsonObjectGetKeyConst(order_hash, "select"))
4002 jsonObject* kids = doFieldmapperSearch(ctx, kid_idl, fake_params, err);
4005 jsonObjectFree( fake_params );
4006 osrfStringArrayFree(link_fields);
4007 jsonIteratorFree(itr);
4008 jsonObjectFree(res_list);
4009 jsonObjectFree(flesh_blob);
4013 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4015 jsonObject* X = NULL;
4016 if ( link_map->size > 0 && kids->size > 0 ) {
4018 kids = jsonNewObjectType(JSON_ARRAY);
4020 jsonObject* _k_node;
4021 jsonIterator* _k = jsonNewIterator( X );
4022 while ((_k_node = jsonIteratorNext( _k ))) {
4028 (unsigned long)atoi(
4034 osrfHashGet(kid_link, "class")
4038 osrfStringArrayGetString( link_map, 0 )
4047 jsonIteratorFree(_k);
4050 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4051 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4054 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4055 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4059 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4060 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4063 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4064 jsonObjectClone( kids )
4069 jsonObjectFree(kids);
4073 jsonObjectFree( kids );
4074 jsonObjectFree( fake_params );
4076 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4077 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4081 jsonObjectFree( flesh_blob );
4082 osrfStringArrayFree(link_fields);
4083 jsonIteratorFree(itr);
4092 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
4094 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4096 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
4098 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
4101 if (!verifyObjectClass(ctx, target)) {
4106 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4107 osrfAppSessionStatus(
4109 OSRF_STATUS_BADREQUEST,
4110 "osrfMethodException",
4112 "No active transaction -- required for UPDATE"
4118 // The following test is harmless but redundant. If a class is
4119 // readonly, we don't register an update method for it.
4120 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4121 osrfAppSessionStatus(
4123 OSRF_STATUS_BADREQUEST,
4124 "osrfMethodException",
4126 "Cannot UPDATE readonly class"
4132 dbhandle = writehandle;
4134 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4136 // Set the last_xact_id
4137 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4139 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
4140 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4143 char* pkey = osrfHashGet(meta, "primarykey");
4144 osrfHash* fields = osrfHashGet(meta, "fields");
4146 char* id = oilsFMGetString( target, pkey );
4150 "%s updating %s object with %s = %s",
4152 osrfHashGet(meta, "fieldmapper"),
4157 growing_buffer* sql = buffer_init(128);
4158 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4163 osrfStringArray* field_list = osrfHashKeys( fields );
4164 while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
4166 osrfHash* field = osrfHashGet( fields, field_name );
4168 if(!( strcmp( field_name, pkey ) )) continue;
4169 if( str_is_true( osrfHashGet(osrfHashGet(fields,field_name), "virtual") ) )
4172 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4174 int value_is_numeric = 0; // boolean
4176 if (field_object && field_object->classname) {
4177 value = oilsFMGetString(
4179 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4182 value = jsonObjectToSimpleString( field_object );
4183 if( field_object && JSON_NUMBER == field_object->type )
4184 value_is_numeric = 1;
4187 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s", osrfHashGet(meta, "fieldmapper"), field_name, value);
4189 if (!field_object || field_object->type == JSON_NULL) {
4190 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) ) && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
4191 if (first) first = 0;
4192 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4193 buffer_fadd( sql, " %s = NULL", field_name );
4196 } else if ( value_is_numeric || !strcmp( get_primitive( field ), "number") ) {
4197 if (first) first = 0;
4198 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4200 const char* numtype = get_datatype( field );
4201 if ( !strncmp( numtype, "INT", 3 ) ) {
4202 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
4203 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
4204 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
4206 // Must really be intended as a string, so quote it
4207 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4208 buffer_fadd( sql, " %s = %s", field_name, value );
4210 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4211 osrfAppSessionStatus(
4213 OSRF_STATUS_INTERNALSERVERERROR,
4214 "osrfMethodException",
4216 "Error quoting string -- please see the error log for more details"
4226 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
4229 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4230 if (first) first = 0;
4231 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4232 buffer_fadd( sql, " %s = %s", field_name, value );
4235 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4236 osrfAppSessionStatus(
4238 OSRF_STATUS_INTERNALSERVERERROR,
4239 "osrfMethodException",
4241 "Error quoting string -- please see the error log for more details"
4255 jsonObject* obj = jsonNewObject(id);
4257 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4258 dbi_conn_quote_string(dbhandle, &id);
4260 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
4262 char* query = buffer_release(sql);
4263 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
4265 dbi_result result = dbi_conn_query(dbhandle, query);
4269 jsonObjectFree(obj);
4270 obj = jsonNewObject(NULL);
4273 "%s ERROR updating %s object with %s = %s",
4275 osrfHashGet(meta, "fieldmapper"),
4286 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4288 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4290 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4291 osrfAppSessionStatus(
4293 OSRF_STATUS_BADREQUEST,
4294 "osrfMethodException",
4296 "No active transaction -- required for DELETE"
4302 // The following test is harmless but redundant. If a class is
4303 // readonly, we don't register a delete method for it.
4304 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4305 osrfAppSessionStatus(
4307 OSRF_STATUS_BADREQUEST,
4308 "osrfMethodException",
4310 "Cannot DELETE readonly class"
4316 dbhandle = writehandle;
4320 char* pkey = osrfHashGet(meta, "primarykey");
4328 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4329 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4334 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4337 if (!verifyObjectPCRUD( ctx, NULL )) {
4342 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4347 "%s deleting %s object with %s = %s",
4349 osrfHashGet(meta, "fieldmapper"),
4354 obj = jsonNewObject(id);
4356 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4357 dbi_conn_quote_string(writehandle, &id);
4359 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4362 jsonObjectFree(obj);
4363 obj = jsonNewObject(NULL);
4366 "%s ERROR deleting %s object with %s = %s",
4368 osrfHashGet(meta, "fieldmapper"),
4381 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4382 if(!(result && meta)) return jsonNULL;
4384 jsonObject* object = jsonNewObject(NULL);
4385 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4387 osrfHash* fields = osrfHashGet(meta, "fields");
4389 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4393 char dt_string[256];
4397 int columnIndex = 1;
4399 unsigned short type;
4400 const char* columnName;
4402 /* cycle through the column list */
4403 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4405 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4407 fmIndex = -1; // reset the position
4409 /* determine the field type and storage attributes */
4410 type = dbi_result_get_field_type(result, columnName);
4411 attr = dbi_result_get_field_attribs(result, columnName);
4413 /* fetch the fieldmapper index */
4414 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4416 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4419 const char* pos = (char*)osrfHashGet(_f, "array_position");
4420 if ( !pos ) continue;
4422 fmIndex = atoi( pos );
4423 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
4428 if (dbi_result_field_is_null(result, columnName)) {
4429 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
4434 case DBI_TYPE_INTEGER :
4436 if( attr & DBI_INTEGER_SIZE8 )
4437 jsonObjectSetIndex( object, fmIndex,
4438 jsonNewNumberObject(dbi_result_get_longlong(result, columnName)));
4440 jsonObjectSetIndex( object, fmIndex,
4441 jsonNewNumberObject(dbi_result_get_int(result, columnName)));
4445 case DBI_TYPE_DECIMAL :
4446 jsonObjectSetIndex( object, fmIndex,
4447 jsonNewNumberObject(dbi_result_get_double(result, columnName)));
4450 case DBI_TYPE_STRING :
4456 jsonNewObject( dbi_result_get_string(result, columnName) )
4461 case DBI_TYPE_DATETIME :
4463 memset(dt_string, '\0', sizeof(dt_string));
4464 memset(&gmdt, '\0', sizeof(gmdt));
4466 _tmp_dt = dbi_result_get_datetime(result, columnName);
4469 if (!(attr & DBI_DATETIME_DATE)) {
4470 gmtime_r( &_tmp_dt, &gmdt );
4471 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4472 } else if (!(attr & DBI_DATETIME_TIME)) {
4473 localtime_r( &_tmp_dt, &gmdt );
4474 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4476 localtime_r( &_tmp_dt, &gmdt );
4477 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4480 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
4484 case DBI_TYPE_BINARY :
4485 osrfLogError( OSRF_LOG_MARK,
4486 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4494 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
4495 if(!result) return jsonNULL;
4497 jsonObject* object = jsonNewObject(NULL);
4500 char dt_string[256];
4504 int columnIndex = 1;
4506 unsigned short type;
4507 const char* columnName;
4509 /* cycle through the column list */
4510 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4512 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4514 fmIndex = -1; // reset the position
4516 /* determine the field type and storage attributes */
4517 type = dbi_result_get_field_type(result, columnName);
4518 attr = dbi_result_get_field_attribs(result, columnName);
4520 if (dbi_result_field_is_null(result, columnName)) {
4521 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
4526 case DBI_TYPE_INTEGER :
4528 if( attr & DBI_INTEGER_SIZE8 )
4529 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_longlong(result, columnName)) );
4531 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_int(result, columnName)) );
4534 case DBI_TYPE_DECIMAL :
4535 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_double(result, columnName)) );
4538 case DBI_TYPE_STRING :
4539 jsonObjectSetKey( object, columnName, jsonNewObject(dbi_result_get_string(result, columnName)) );
4542 case DBI_TYPE_DATETIME :
4544 memset(dt_string, '\0', sizeof(dt_string));
4545 memset(&gmdt, '\0', sizeof(gmdt));
4547 _tmp_dt = dbi_result_get_datetime(result, columnName);
4550 if (!(attr & DBI_DATETIME_DATE)) {
4551 gmtime_r( &_tmp_dt, &gmdt );
4552 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4553 } else if (!(attr & DBI_DATETIME_TIME)) {
4554 localtime_r( &_tmp_dt, &gmdt );
4555 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4557 localtime_r( &_tmp_dt, &gmdt );
4558 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4561 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
4564 case DBI_TYPE_BINARY :
4565 osrfLogError( OSRF_LOG_MARK,
4566 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4574 // Interpret a string as true or false
4575 static int str_is_true( const char* str ) {
4576 if( NULL == str || strcasecmp( str, "true" ) )
4582 // Interpret a jsonObject as true or false
4583 static int obj_is_true( const jsonObject* obj ) {
4586 else switch( obj->type )
4594 if( strcasecmp( obj->value.s, "true" ) )
4598 case JSON_NUMBER : // Support 1/0 for perl's sake
4599 if( jsonObjectGetNumber( obj ) == 1.0 )
4608 // Translate a numeric code into a text string identifying a type of
4609 // jsonObject. To be used for building error messages.
4610 static const char* json_type( int code ) {
4616 return "JSON_ARRAY";
4618 return "JSON_STRING";
4620 return "JSON_NUMBER";
4626 return "(unrecognized)";
4630 // Extract the "primitive" attribute from an IDL field definition.
4631 // If we haven't initialized the app, then we must be running in
4632 // some kind of testbed. In that case, default to "string".
4633 static const char* get_primitive( osrfHash* field ) {
4634 const char* s = osrfHashGet( field, "primitive" );
4636 if( child_initialized )
4639 "%s ERROR No \"datatype\" attribute for field \"%s\"",
4641 osrfHashGet( field, "name" )
4649 // Extract the "datatype" attribute from an IDL field definition.
4650 // If we haven't initialized the app, then we must be running in
4651 // some kind of testbed. In that case, default to to NUMERIC,
4652 // since we look at the datatype only for numbers.
4653 static const char* get_datatype( osrfHash* field ) {
4654 const char* s = osrfHashGet( field, "datatype" );
4656 if( child_initialized )
4659 "%s ERROR No \"datatype\" attribute for field \"%s\"",
4661 osrfHashGet( field, "name" )
4670 If the input string is potentially a valid SQL identifier, return 1.
4673 Purpose: to prevent certain kinds of SQL injection. To that end we
4674 don't necessarily need to follow all the rules exactly, such as requiring
4675 that the first character not be a digit.
4677 We allow leading and trailing white space. In between, we do not allow
4678 punctuation (except for underscores and dollar signs), control
4679 characters, or embedded white space.
4681 More pedantically we should allow quoted identifiers containing arbitrary
4682 characters, but for the foreseeable future such quoted identifiers are not
4683 likely to be an issue.
4685 static int is_identifier( const char* s) {
4689 // Skip leading white space
4690 while( isspace( (unsigned char) *s ) )
4694 return 0; // Nothing but white space? Not okay.
4696 // Check each character until we reach white space or
4697 // end-of-string. Letters, digits, underscores, and
4698 // dollar signs are okay. Control characters and other
4699 // punctuation characters are not okay. Anything else
4700 // is okay -- it could for example be part of a multibyte
4701 // UTF8 character such as a letter with diacritical marks,
4702 // and those are allowed.
4704 if( isalnum( (unsigned char) *s )
4707 ; // Fine; keep going
4708 else if( ispunct( (unsigned char) *s )
4709 || iscntrl( (unsigned char) *s ) )
4712 } while( *s && ! isspace( (unsigned char) *s ) );
4714 // If we found any white space in the above loop,
4715 // the rest had better be all white space.
4717 while( isspace( (unsigned char) *s ) )
4721 return 0; // White space was embedded within non-white space
4727 Determine whether to accept a character string as a comparison operator.
4728 Return 1 if it's good, or 0 if it's bad.
4730 We don't validate it for real. We just make sure that it doesn't contain
4731 any semicolons or white space (with a special exception for the
4732 "SIMILAR TO" operator). The idea is to block certain kinds of SQL
4733 injection. If it has no semicolons or white space but it's still not a
4734 valid operator, then the database will complain.
4736 Another approach would be to compare the string against a short list of
4737 approved operators. We don't do that because we want to allow custom
4738 operators like ">100*", which would be difficult or impossible to
4739 express otherwise in a JSON query.
4741 static int is_good_operator( const char* op ) {
4742 if( !op ) return 0; // Sanity check
4746 if( isspace( (unsigned char) *s ) ) {
4747 // Special exception for SIMILAR TO. Someday we might make
4748 // exceptions for IS DISTINCT FROM and IS NOT DISTINCT FROM.
4749 if( !strcasecmp( op, "similar to" ) )
4754 else if( ';' == *s )