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 ) {
1752 growing_buffer* sql_buf = buffer_init(32);
1754 jsonObject* func_item;
1756 // Get the function name
1757 if( array->size > 0 ) {
1758 func_item = jsonObjectGetIndex( array, 0 );
1759 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1760 OSRF_BUFFER_ADD( sql_buf, "( " );
1763 // Get the parameters
1764 int func_item_index = 1; // We already grabbed the zeroth entry
1765 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1767 // Add a separator comma, if we need one
1768 if( func_item_index > 2 )
1769 buffer_add( sql_buf, ", " );
1771 // Add the current parameter
1772 if (func_item->type == JSON_NULL) {
1773 buffer_add( sql_buf, "NULL" );
1775 char* val = jsonObjectToSimpleString(func_item);
1776 if ( dbi_conn_quote_string(dbhandle, &val) ) {
1777 OSRF_BUFFER_ADD( sql_buf, val );
1780 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1781 buffer_free(sql_buf);
1788 buffer_add( sql_buf, " )" );
1790 return buffer_release(sql_buf);
1793 static char* searchFunctionPredicate (const char* class, osrfHash* field,
1794 const jsonObject* node, const char* node_key) {
1796 char* val = searchValueTransform(node);
1800 growing_buffer* sql_buf = buffer_init(32);
1805 osrfHashGet(field, "name"),
1812 return buffer_release(sql_buf);
1815 // class is a class name
1816 // field is a field definition as stored in the IDL
1817 // node comes from the method parameter, and represents an entry in the SELECT list
1818 static char* searchFieldTransform (const char* class, osrfHash* field, const jsonObject* node) {
1819 growing_buffer* sql_buf = buffer_init(32);
1821 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
1822 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
1824 if(transform_subcolumn)
1825 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
1827 if (field_transform) {
1828 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class, osrfHashGet(field, "name"));
1829 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
1832 if( array->type != JSON_ARRAY ) {
1833 osrfLogError( OSRF_LOG_MARK,
1834 "%s: Expected JSON_ARRAY for function params; found %s",
1835 MODULENAME, json_type( array->type ) );
1836 buffer_free( sql_buf );
1839 int func_item_index = 0;
1840 jsonObject* func_item;
1841 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1843 char* val = jsonObjectToSimpleString(func_item);
1846 buffer_add( sql_buf, ",NULL" );
1847 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
1848 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
1849 OSRF_BUFFER_ADD( sql_buf, val );
1851 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1853 buffer_free(sql_buf);
1860 buffer_add( sql_buf, " )" );
1863 buffer_fadd( sql_buf, "\"%s\".%s", class, osrfHashGet(field, "name"));
1866 if (transform_subcolumn)
1867 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
1869 return buffer_release(sql_buf);
1872 static char* searchFieldTransformPredicate (const char* class, osrfHash* field,
1873 const jsonObject* node, const char* node_key) {
1874 char* field_transform = searchFieldTransform( class, field, node );
1877 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
1878 if ( ! value_obj ) {
1879 value = searchWHERE( node, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1881 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME, value);
1882 free(field_transform);
1885 } else if ( value_obj->type == JSON_ARRAY ) {
1886 value = searchValueTransform( value_obj );
1888 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform",
1890 free( field_transform );
1893 } else if ( value_obj->type == JSON_HASH ) {
1894 value = searchWHERE( value_obj, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1896 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME, value);
1897 free(field_transform);
1900 } else if ( value_obj->type == JSON_NUMBER ) {
1901 value = jsonNumberToDBString( field, value_obj );
1902 } else if ( value_obj->type != JSON_NULL ) {
1903 if ( !strcmp( get_primitive( field ), "number") ) {
1904 value = jsonNumberToDBString( field, value_obj );
1906 value = jsonObjectToSimpleString( value_obj );
1907 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
1908 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
1910 free(field_transform);
1916 growing_buffer* sql_buf = buffer_init(32);
1927 free(field_transform);
1929 return buffer_release(sql_buf);
1932 static char* searchSimplePredicate (const char* op, const char* class,
1933 osrfHash* field, const jsonObject* node) {
1935 if( ! is_good_operator( op ) ) {
1936 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
1942 // Get the value to which we are comparing the specified column
1943 if (node->type != JSON_NULL) {
1944 if ( node->type == JSON_NUMBER ) {
1945 val = jsonNumberToDBString( field, node );
1946 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
1947 val = jsonNumberToDBString( field, node );
1949 val = jsonObjectToSimpleString(node);
1954 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
1955 // Value is not numeric; enclose it in quotes
1956 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
1957 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
1963 // Compare to a null value
1964 val = strdup( "NULL" );
1965 if (strcmp( op, "=" ))
1971 growing_buffer* sql_buf = buffer_init(32);
1972 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class, osrfHashGet(field, "name"), op, val );
1973 char* pred = buffer_release( sql_buf );
1980 static char* searchBETWEENPredicate (const char* class, osrfHash* field, const jsonObject* node) {
1982 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
1983 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
1985 if( NULL == y_node ) {
1986 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
1989 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
1990 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
1997 if ( !strcmp( get_primitive( field ), "number") ) {
1998 x_string = jsonNumberToDBString(field, x_node);
1999 y_string = jsonNumberToDBString(field, y_node);
2002 x_string = jsonObjectToSimpleString(x_node);
2003 y_string = jsonObjectToSimpleString(y_node);
2004 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2005 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2006 MODULENAME, x_string, y_string);
2013 growing_buffer* sql_buf = buffer_init(32);
2014 buffer_fadd( sql_buf, "%s BETWEEN %s AND %s", osrfHashGet(field, "name"), x_string, y_string );
2018 return buffer_release(sql_buf);
2021 static char* searchPredicate ( const char* class, osrfHash* field,
2022 jsonObject* node, osrfMethodContext* ctx ) {
2025 if (node->type == JSON_ARRAY) { // equality IN search
2026 pred = searchINPredicate( class, field, node, NULL, ctx );
2027 } else if (node->type == JSON_HASH) { // non-equality search
2028 jsonIterator* pred_itr = jsonNewIterator( node );
2029 if( !jsonIteratorHasNext( pred_itr ) ) {
2030 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2031 MODULENAME, osrfHashGet(field, "name") );
2033 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2035 // Verify that there are no additional predicates
2036 if( jsonIteratorHasNext( pred_itr ) ) {
2037 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2038 MODULENAME, osrfHashGet(field, "name") );
2039 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2040 pred = searchBETWEENPredicate( class, field, pred_node );
2041 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2042 pred = searchINPredicate( class, field, pred_node, pred_itr->key, ctx );
2043 else if ( pred_node->type == JSON_ARRAY )
2044 pred = searchFunctionPredicate( class, field, pred_node, pred_itr->key );
2045 else if ( pred_node->type == JSON_HASH )
2046 pred = searchFieldTransformPredicate( class, field, pred_node, pred_itr->key );
2048 pred = searchSimplePredicate( pred_itr->key, class, field, pred_node );
2050 jsonIteratorFree(pred_itr);
2052 } else if (node->type == JSON_NULL) { // IS NULL search
2053 growing_buffer* _p = buffer_init(64);
2056 "\"%s\".%s IS NULL",
2058 osrfHashGet(field, "name")
2060 pred = buffer_release(_p);
2061 } else { // equality search
2062 pred = searchSimplePredicate( "=", class, field, node );
2081 field : call_number,
2097 static char* searchJOIN ( const jsonObject* join_hash, osrfHash* leftmeta ) {
2099 const jsonObject* working_hash;
2100 jsonObject* freeable_hash = NULL;
2102 if (join_hash->type == JSON_STRING) {
2103 // create a wrapper around a copy of the original
2104 const char* _tmp = jsonObjectGetString( join_hash );
2105 freeable_hash = jsonNewObjectType(JSON_HASH);
2106 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2107 working_hash = freeable_hash;
2110 if( join_hash->type != JSON_HASH ) {
2113 "%s: JOIN failed; expected JSON object type not found",
2118 working_hash = join_hash;
2121 growing_buffer* join_buf = buffer_init(128);
2122 const char* leftclass = osrfHashGet(leftmeta, "classname");
2124 jsonObject* snode = NULL;
2125 jsonIterator* search_itr = jsonNewIterator( working_hash );
2127 while ( (snode = jsonIteratorNext( search_itr )) ) {
2128 const char* class = search_itr->key;
2129 osrfHash* idlClass = osrfHashGet( oilsIDL(), class );
2133 "%s: JOIN failed. No class \"%s\" defined in IDL",
2137 jsonIteratorFree( search_itr );
2138 buffer_free( join_buf );
2140 jsonObjectFree( freeable_hash );
2144 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2145 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2147 if (field && !fkey) {
2148 fkey = (const char*)oilsIDLFindPath("/%s/links/%s/key", class, field);
2152 "%s: JOIN failed. No link defined from %s.%s to %s",
2158 buffer_free(join_buf);
2160 jsonObjectFree(freeable_hash);
2161 jsonIteratorFree(search_itr);
2165 } else if (!field && fkey) {
2166 field = (const char*)oilsIDLFindPath("/%s/links/%s/key", leftclass, fkey );
2170 "%s: JOIN failed. No link defined from %s.%s to %s",
2176 buffer_free(join_buf);
2178 jsonObjectFree(freeable_hash);
2179 jsonIteratorFree(search_itr);
2184 } else if (!field && !fkey) {
2185 osrfHash* _links = oilsIDL_links( leftclass );
2187 // For each link defined for the left class:
2188 // see if the link references the joined class
2189 osrfHashIterator* itr = osrfNewHashIterator( _links );
2190 osrfHash* curr_link = NULL;
2191 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2192 const char* other_class = osrfHashGet( curr_link, "class" );
2193 if( other_class && !strcmp( other_class, class ) ) {
2195 // Found a link between the classes
2196 fkey = osrfHashIteratorKey( itr );
2197 field = osrfHashGet( curr_link, "key" );
2201 osrfHashIteratorFree( itr );
2203 if (!field || !fkey) {
2204 // Do another such search, with the classes reversed
2205 _links = oilsIDL_links( class );
2207 // For each link defined for the joined class:
2208 // see if the link references the left class
2209 osrfHashIterator* itr = osrfNewHashIterator( _links );
2210 osrfHash* curr_link = NULL;
2211 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2212 const char* other_class = osrfHashGet( curr_link, "class" );
2213 if( other_class && !strcmp( other_class, leftclass ) ) {
2215 // Found a link between the classes
2216 fkey = osrfHashIteratorKey( itr );
2217 field = osrfHashGet( curr_link, "key" );
2221 osrfHashIteratorFree( itr );
2224 if (!field || !fkey) {
2227 "%s: JOIN failed. No link defined between %s and %s",
2232 buffer_free(join_buf);
2234 jsonObjectFree(freeable_hash);
2235 jsonIteratorFree(search_itr);
2241 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2243 if ( !strcasecmp(type,"left") ) {
2244 buffer_add(join_buf, " LEFT JOIN");
2245 } else if ( !strcasecmp(type,"right") ) {
2246 buffer_add(join_buf, " RIGHT JOIN");
2247 } else if ( !strcasecmp(type,"full") ) {
2248 buffer_add(join_buf, " FULL JOIN");
2250 buffer_add(join_buf, " INNER JOIN");
2253 buffer_add(join_buf, " INNER JOIN");
2256 char* table = getSourceDefinition(idlClass);
2258 jsonIteratorFree( search_itr );
2259 buffer_free( join_buf );
2261 jsonObjectFree( freeable_hash );
2265 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2266 table, class, class, field, leftclass, fkey);
2269 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2271 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2272 if ( filter_op && !strcasecmp("or",filter_op) ) {
2273 buffer_add( join_buf, " OR " );
2275 buffer_add( join_buf, " AND " );
2278 char* jpred = searchWHERE( filter, idlClass, AND_OP_JOIN, NULL );
2280 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2281 OSRF_BUFFER_ADD( join_buf, jpred );
2286 "%s: JOIN failed. Invalid conditional expression.",
2289 jsonIteratorFree( search_itr );
2290 buffer_free( join_buf );
2292 jsonObjectFree( freeable_hash );
2297 buffer_add(join_buf, " ) ");
2299 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2301 char* jpred = searchJOIN( join_filter, idlClass );
2302 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2303 OSRF_BUFFER_ADD( join_buf, jpred );
2309 jsonObjectFree(freeable_hash);
2310 jsonIteratorFree(search_itr);
2312 return buffer_release(join_buf);
2317 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2318 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2319 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2321 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2323 search_hash is the JSON expression of the conditions.
2324 meta is the class definition from the IDL, for the relevant table.
2325 opjoin_type indicates whether multiple conditions, if present, should be
2326 connected by AND or OR.
2327 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2328 to pass it to other functions -- and all they do with it is to use the session
2329 and request members to send error messages back to the client.
2333 static char* searchWHERE ( const jsonObject* search_hash, osrfHash* meta, int opjoin_type, osrfMethodContext* ctx ) {
2337 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2345 growing_buffer* sql_buf = buffer_init(128);
2347 jsonObject* node = NULL;
2350 if ( search_hash->type == JSON_ARRAY ) {
2351 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2352 jsonIterator* search_itr = jsonNewIterator( search_hash );
2353 if( !jsonIteratorHasNext( search_itr ) ) {
2356 "%s: Invalid predicate structure: empty JSON array",
2359 jsonIteratorFree( search_itr );
2360 buffer_free( sql_buf );
2364 while ( (node = jsonIteratorNext( search_itr )) ) {
2368 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2369 else buffer_add(sql_buf, " AND ");
2372 char* subpred = searchWHERE( node, meta, opjoin_type, ctx );
2374 buffer_fadd(sql_buf, "( %s )", subpred);
2377 jsonIteratorFree( search_itr );
2378 buffer_free( sql_buf );
2382 jsonIteratorFree(search_itr);
2384 } else if ( search_hash->type == JSON_HASH ) {
2385 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2386 jsonIterator* search_itr = jsonNewIterator( search_hash );
2387 if( !jsonIteratorHasNext( search_itr ) ) {
2390 "%s: Invalid predicate structure: empty JSON object",
2393 jsonIteratorFree( search_itr );
2394 buffer_free( sql_buf );
2398 while ( (node = jsonIteratorNext( search_itr )) ) {
2403 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2404 else buffer_add(sql_buf, " AND ");
2407 if ( '+' == search_itr->key[ 0 ] ) {
2408 if ( node->type == JSON_STRING ) {
2409 // Intended purpose; to allow reference to a Boolean column
2411 // Verify that the class alias is not empty
2412 if( '\0' == search_itr->key[ 1 ] ) {
2415 "%s: Table alias is empty",
2418 jsonIteratorFree( search_itr );
2419 buffer_free( sql_buf );
2423 // Verify that the string looks like an identifier.
2424 const char* subpred = jsonObjectGetString( node );
2425 if( ! is_identifier( subpred ) ) {
2428 "%s: Invalid boolean identifier in WHERE clause: \"%s\"",
2432 jsonIteratorFree( search_itr );
2433 buffer_free( sql_buf );
2437 buffer_fadd(sql_buf, " \"%s\".%s ", search_itr->key + 1, subpred);
2439 char* subpred = searchWHERE( node, osrfHashGet( oilsIDL(), search_itr->key + 1 ), AND_OP_JOIN, ctx );
2441 buffer_fadd(sql_buf, "( %s )", subpred);
2444 jsonIteratorFree( search_itr );
2445 buffer_free( sql_buf );
2449 } else if ( !strcasecmp("-or",search_itr->key) ) {
2450 char* subpred = searchWHERE( node, meta, OR_OP_JOIN, ctx );
2452 buffer_fadd(sql_buf, "( %s )", subpred);
2455 buffer_free( sql_buf );
2458 } else if ( !strcasecmp("-and",search_itr->key) ) {
2459 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2461 buffer_fadd(sql_buf, "( %s )", subpred);
2464 buffer_free( sql_buf );
2467 } else if ( !strcasecmp("-not",search_itr->key) ) {
2468 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2470 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2473 buffer_free( sql_buf );
2476 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2477 char* subpred = SELECT(
2479 jsonObjectGetKey( node, "select" ),
2480 jsonObjectGetKey( node, "from" ),
2481 jsonObjectGetKey( node, "where" ),
2482 jsonObjectGetKey( node, "having" ),
2483 jsonObjectGetKey( node, "order_by" ),
2484 jsonObjectGetKey( node, "limit" ),
2485 jsonObjectGetKey( node, "offset" ),
2489 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2491 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2492 char* subpred = SELECT(
2494 jsonObjectGetKey( node, "select" ),
2495 jsonObjectGetKey( node, "from" ),
2496 jsonObjectGetKey( node, "where" ),
2497 jsonObjectGetKey( node, "having" ),
2498 jsonObjectGetKey( node, "order_by" ),
2499 jsonObjectGetKey( node, "limit" ),
2500 jsonObjectGetKey( node, "offset" ),
2504 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2508 char* class = osrfHashGet(meta, "classname");
2509 osrfHash* fields = osrfHashGet(meta, "fields");
2510 osrfHash* field = osrfHashGet( fields, search_itr->key );
2514 char* table = getSourceDefinition(meta);
2516 table = strdup( "(?)" );
2519 "%s: Attempt to reference non-existent column %s on %s (%s)",
2525 buffer_free(sql_buf);
2527 jsonIteratorFree(search_itr);
2531 char* subpred = searchPredicate( class, field, node, ctx );
2533 buffer_add( sql_buf, subpred );
2536 buffer_free(sql_buf);
2537 jsonIteratorFree(search_itr);
2542 jsonIteratorFree(search_itr);
2545 // ERROR ... only hash and array allowed at this level
2546 char* predicate_string = jsonObjectToJSON( search_hash );
2549 "%s: Invalid predicate structure: %s",
2553 buffer_free(sql_buf);
2554 free(predicate_string);
2558 return buffer_release(sql_buf);
2562 /* method context */ osrfMethodContext* ctx,
2564 /* SELECT */ jsonObject* selhash,
2565 /* FROM */ jsonObject* join_hash,
2566 /* WHERE */ jsonObject* search_hash,
2567 /* HAVING */ jsonObject* having_hash,
2568 /* ORDER BY */ jsonObject* order_hash,
2569 /* LIMIT */ jsonObject* limit,
2570 /* OFFSET */ jsonObject* offset,
2571 /* flags */ int flags
2573 const char* locale = osrf_message_get_last_locale();
2575 // in case we don't get a select list
2576 jsonObject* defaultselhash = NULL;
2578 // general tmp objects
2579 const jsonObject* tmp_const;
2580 jsonObject* selclass = NULL;
2581 jsonObject* selfield = NULL;
2582 jsonObject* snode = NULL;
2583 jsonObject* onode = NULL;
2585 char* string = NULL;
2586 int from_function = 0;
2591 // the core search class
2592 char* core_class = NULL;
2594 // metadata about the core search class
2595 osrfHash* core_meta = NULL;
2597 // punt if there's no core class
2598 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2601 "%s: FROM clause is missing or empty",
2605 osrfAppSessionStatus(
2607 OSRF_STATUS_INTERNALSERVERERROR,
2608 "osrfMethodException",
2610 "FROM clause is missing or empty in JSON query"
2615 // get the core class -- the only key of the top level FROM clause, or a string
2616 if (join_hash->type == JSON_HASH) {
2617 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2618 snode = jsonIteratorNext( tmp_itr );
2620 core_class = strdup( tmp_itr->key );
2623 jsonObject* extra = jsonIteratorNext( tmp_itr );
2625 jsonIteratorFree( tmp_itr );
2628 // There shouldn't be more than one entry in join_hash
2632 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2636 osrfAppSessionStatus(
2638 OSRF_STATUS_INTERNALSERVERERROR,
2639 "osrfMethodException",
2641 "Malformed FROM clause in JSON query"
2644 return NULL; // Malformed join_hash; extra entry
2646 } else if (join_hash->type == JSON_ARRAY) {
2648 core_class = jsonObjectToSimpleString( jsonObjectGetIndex(join_hash, 0) );
2651 } else if (join_hash->type == JSON_STRING) {
2652 core_class = jsonObjectToSimpleString( join_hash );
2658 "%s: FROM clause is unexpected JSON type: %s",
2660 json_type( join_hash->type )
2663 osrfAppSessionStatus(
2665 OSRF_STATUS_INTERNALSERVERERROR,
2666 "osrfMethodException",
2668 "Ill-formed FROM clause in JSON query"
2674 if (!from_function) {
2675 // Get the IDL class definition for the core class
2676 core_meta = osrfHashGet( oilsIDL(), core_class );
2677 if( !core_meta ) { // Didn't find it?
2680 "%s: SELECT clause references undefined class: \"%s\"",
2685 osrfAppSessionStatus(
2687 OSRF_STATUS_INTERNALSERVERERROR,
2688 "osrfMethodException",
2690 "SELECT clause references undefined class in JSON query"
2696 // Make sure the class isn't virtual
2697 if( str_is_true( osrfHashGet( core_meta, "virtual" ) ) ) {
2700 "%s: Core class is virtual: \"%s\"",
2705 osrfAppSessionStatus(
2707 OSRF_STATUS_INTERNALSERVERERROR,
2708 "osrfMethodException",
2710 "FROM clause references virtual class in JSON query"
2717 // if the select list is empty, or the core class field list is '*',
2718 // build the default select list ...
2720 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
2721 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2722 } else if( selhash->type != JSON_HASH ) {
2725 "%s: Expected JSON_HASH for SELECT clause; found %s",
2727 json_type( selhash->type )
2731 osrfAppSessionStatus(
2733 OSRF_STATUS_INTERNALSERVERERROR,
2734 "osrfMethodException",
2736 "Malformed SELECT clause in JSON query"
2740 } else if ( (tmp_const = jsonObjectGetKeyConst( selhash, core_class )) && tmp_const->type == JSON_STRING ) {
2741 const char* _x = jsonObjectGetString( tmp_const );
2742 if (!strncmp( "*", _x, 1 )) {
2743 jsonObjectRemoveKey( selhash, core_class );
2744 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2749 growing_buffer* sql_buf = buffer_init(128);
2751 // temp buffer for the SELECT list
2752 growing_buffer* select_buf = buffer_init(128);
2753 growing_buffer* order_buf = buffer_init(128);
2754 growing_buffer* group_buf = buffer_init(128);
2755 growing_buffer* having_buf = buffer_init(128);
2757 int aggregate_found = 0; // boolean
2759 // Build a select list
2760 if(from_function) // From a function we select everything
2761 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
2764 // If we need to build a default list, prepare to do so
2765 jsonObject* _tmp = jsonObjectGetKey( selhash, core_class );
2766 if ( _tmp && !_tmp->size ) {
2768 osrfHash* core_fields = osrfHashGet( core_meta, "fields" );
2770 osrfHashIterator* field_itr = osrfNewHashIterator( core_fields );
2771 osrfHash* field_def;
2772 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2773 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2774 // This field is not virtual, so add it to the list
2775 jsonObjectPush( _tmp, jsonNewObject( osrfHashIteratorKey( field_itr ) ) );
2778 osrfHashIteratorFree( field_itr );
2781 // Now build the actual select list
2785 jsonIterator* selclass_itr = jsonNewIterator( selhash );
2786 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
2788 // Make sure the class is defined in the IDL
2789 const char* cname = selclass_itr->key;
2790 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
2794 "%s: Selected class \"%s\" not defined in IDL",
2800 osrfAppSessionStatus(
2802 OSRF_STATUS_INTERNALSERVERERROR,
2803 "osrfMethodException",
2805 "Selected class is not defined"
2807 jsonIteratorFree( selclass_itr );
2808 buffer_free( sql_buf );
2809 buffer_free( select_buf );
2810 buffer_free( order_buf );
2811 buffer_free( group_buf );
2812 buffer_free( having_buf );
2813 if( defaultselhash ) jsonObjectFree( defaultselhash );
2818 // Make sure the target relation is in the join tree.
2820 // At this point join_hash is a step down from the join_hash we
2821 // received as a parameter. If the original was a JSON_STRING,
2822 // then json_hash is now NULL. If the original was a JSON_HASH,
2823 // then json_hash is now the first (and only) entry in it,
2824 // denoting the core class. We've already excluded the
2825 // possibility that the original was a JSON_ARRAY, because in
2826 // that case from_function would be non-NULL, and we wouldn't
2829 int class_in_from_clause; // boolean
2831 if ( ! strcmp( core_class, cname ))
2832 // This is the core class -- no problem
2833 class_in_from_clause = 1;
2836 // There's only one class in the FROM clause, and this isn't it
2837 class_in_from_clause = 0;
2838 else if (join_hash->type == JSON_STRING) {
2839 // There's only one class in the FROM clause
2840 const char* str = jsonObjectGetString(join_hash);
2841 if ( strcmp( str, cname ) )
2842 class_in_from_clause = 0; // This isn't it
2844 class_in_from_clause = 1; // This is it
2846 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", cname);
2847 if ( 0 == found->size )
2848 class_in_from_clause = 0; // Nowhere in the join tree
2850 class_in_from_clause = 1; // Found it
2851 jsonObjectFree( found );
2855 // If the class isn't in the FROM clause, bail out
2856 if( ! class_in_from_clause ) {
2859 "%s: SELECT clause references class not in FROM clause: \"%s\"",
2864 osrfAppSessionStatus(
2866 OSRF_STATUS_INTERNALSERVERERROR,
2867 "osrfMethodException",
2869 "Selected class not in FROM clause in JSON query"
2871 jsonIteratorFree( selclass_itr );
2872 buffer_free( sql_buf );
2873 buffer_free( select_buf );
2874 buffer_free( order_buf );
2875 buffer_free( group_buf );
2876 buffer_free( having_buf );
2877 if( defaultselhash ) jsonObjectFree( defaultselhash );
2882 // Look up some attributes of the current class, so that we
2883 // don't have to look them up again for each field
2884 osrfHash* class_field_set = osrfHashGet( idlClass, "fields" );
2885 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
2886 const char* class_tname = osrfHashGet( idlClass, "tablename" );
2888 // stitch together the column list ...
2889 jsonIterator* select_itr = jsonNewIterator( selclass );
2890 while ( (selfield = jsonIteratorNext( select_itr )) ) { // for each SELECT column
2892 // If we need a separator comma, add one
2896 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
2899 // ... if it's a string, just toss it on the pile
2900 if (selfield->type == JSON_STRING) {
2902 // Look up the field in the IDL
2903 const char* col_name = jsonObjectGetString( selfield );
2904 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
2906 // No such field in current class
2909 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
2915 osrfAppSessionStatus(
2917 OSRF_STATUS_INTERNALSERVERERROR,
2918 "osrfMethodException",
2920 "Selected column not defined in JSON query"
2922 jsonIteratorFree( select_itr );
2923 jsonIteratorFree( selclass_itr );
2924 buffer_free( sql_buf );
2925 buffer_free( select_buf );
2926 buffer_free( order_buf );
2927 buffer_free( group_buf );
2928 buffer_free( having_buf );
2929 if( defaultselhash ) jsonObjectFree( defaultselhash );
2932 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2933 // Virtual field not allowed
2936 "%s: Selected column \"%s\" for class \"%s\" is virtual",
2942 osrfAppSessionStatus(
2944 OSRF_STATUS_INTERNALSERVERERROR,
2945 "osrfMethodException",
2947 "Selected column may not be virtual in JSON query"
2949 jsonIteratorFree( select_itr );
2950 jsonIteratorFree( selclass_itr );
2951 buffer_free( sql_buf );
2952 buffer_free( select_buf );
2953 buffer_free( order_buf );
2954 buffer_free( group_buf );
2955 buffer_free( having_buf );
2956 if( defaultselhash ) jsonObjectFree( defaultselhash );
2963 if (flags & DISABLE_I18N)
2966 i18n = osrfHashGet(field_def, "i18n");
2968 if( str_is_true( i18n ) ) {
2969 buffer_fadd( select_buf,
2970 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
2971 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
2973 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
2976 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
2979 // ... but it could be an object, in which case we check for a Field Transform
2980 } else if (selfield->type == JSON_HASH) {
2982 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
2984 // Get the field definition from the IDL
2985 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
2987 // No such field in current class
2990 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
2996 osrfAppSessionStatus(
2998 OSRF_STATUS_INTERNALSERVERERROR,
2999 "osrfMethodException",
3001 "Selected column is not defined in JSON query"
3003 jsonIteratorFree( select_itr );
3004 jsonIteratorFree( selclass_itr );
3005 buffer_free( sql_buf );
3006 buffer_free( select_buf );
3007 buffer_free( order_buf );
3008 buffer_free( group_buf );
3009 buffer_free( having_buf );
3010 if( defaultselhash ) jsonObjectFree( defaultselhash );
3013 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3014 // No such field in current class
3017 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3023 osrfAppSessionStatus(
3025 OSRF_STATUS_INTERNALSERVERERROR,
3026 "osrfMethodException",
3028 "Selected column is virtual in JSON query"
3030 jsonIteratorFree( select_itr );
3031 jsonIteratorFree( selclass_itr );
3032 buffer_free( sql_buf );
3033 buffer_free( select_buf );
3034 buffer_free( order_buf );
3035 buffer_free( group_buf );
3036 buffer_free( having_buf );
3037 if( defaultselhash ) jsonObjectFree( defaultselhash );
3042 // Decide what to use as a column alias
3044 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3045 _alias = jsonObjectGetString( tmp_const );
3046 } else { // Use field name as the alias
3050 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3051 char* transform_str = searchFieldTransform(cname, field_def, selfield);
3052 if( transform_str ) {
3053 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3054 free(transform_str);
3057 osrfAppSessionStatus(
3059 OSRF_STATUS_INTERNALSERVERERROR,
3060 "osrfMethodException",
3062 "Unable to generate transform function in JSON query"
3064 jsonIteratorFree( select_itr );
3065 jsonIteratorFree( selclass_itr );
3066 buffer_free( sql_buf );
3067 buffer_free( select_buf );
3068 buffer_free( order_buf );
3069 buffer_free( group_buf );
3070 buffer_free( having_buf );
3071 if( defaultselhash ) jsonObjectFree( defaultselhash );
3079 if (flags & DISABLE_I18N)
3082 i18n = osrfHashGet(field_def, "i18n");
3084 if( str_is_true( i18n ) ) {
3085 buffer_fadd( select_buf,
3086 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3087 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3089 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3092 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3099 "%s: Selected item is unexpected JSON type: %s",
3101 json_type( selfield->type )
3104 osrfAppSessionStatus(
3106 OSRF_STATUS_INTERNALSERVERERROR,
3107 "osrfMethodException",
3109 "Ill-formed SELECT item in JSON query"
3111 jsonIteratorFree( select_itr );
3112 jsonIteratorFree( selclass_itr );
3113 buffer_free( sql_buf );
3114 buffer_free( select_buf );
3115 buffer_free( order_buf );
3116 buffer_free( group_buf );
3117 buffer_free( having_buf );
3118 if( defaultselhash ) jsonObjectFree( defaultselhash );
3123 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3124 if( obj_is_true( agg_obj ) )
3125 aggregate_found = 1;
3127 // Append a comma (except for the first one)
3128 // and add the column to a GROUP BY clause
3132 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3134 buffer_fadd(group_buf, " %d", sel_pos);
3138 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3140 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3141 if ( ! obj_is_true( aggregate_obj ) ) {
3145 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3148 buffer_fadd(group_buf, " %d", sel_pos);
3151 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3155 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3158 _column = searchFieldTransform(cname, field, selfield);
3159 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3160 OSRF_BUFFER_ADD(group_buf, _column);
3161 _column = searchFieldTransform(cname, field, selfield);
3168 } // end while -- iterating across SELECT columns
3170 jsonIteratorFree(select_itr);
3171 } // end while -- iterating across classes
3173 jsonIteratorFree(selclass_itr);
3177 char* col_list = buffer_release(select_buf);
3179 if (from_function) table = searchValueTransform(join_hash);
3180 else table = getSourceDefinition(core_meta);
3184 osrfAppSessionStatus(
3186 OSRF_STATUS_INTERNALSERVERERROR,
3187 "osrfMethodException",
3189 "Unable to identify table for core class"
3192 buffer_free( sql_buf );
3193 buffer_free( order_buf );
3194 buffer_free( group_buf );
3195 buffer_free( having_buf );
3196 if( defaultselhash ) jsonObjectFree( defaultselhash );
3201 // Put it all together
3202 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3206 if (!from_function) {
3207 // Now, walk the join tree and add that clause
3209 char* join_clause = searchJOIN( join_hash, core_meta );
3211 buffer_add(sql_buf, join_clause);
3215 osrfAppSessionStatus(
3217 OSRF_STATUS_INTERNALSERVERERROR,
3218 "osrfMethodException",
3220 "Unable to construct JOIN clause(s)"
3222 buffer_free( sql_buf );
3223 buffer_free( order_buf );
3224 buffer_free( group_buf );
3225 buffer_free( having_buf );
3226 if( defaultselhash ) jsonObjectFree( defaultselhash );
3232 // Build a WHERE clause, if there is one
3233 if ( search_hash ) {
3234 buffer_add(sql_buf, " WHERE ");
3236 // and it's on the WHERE clause
3237 char* pred = searchWHERE( search_hash, core_meta, AND_OP_JOIN, ctx );
3240 buffer_add(sql_buf, pred);
3244 osrfAppSessionStatus(
3246 OSRF_STATUS_INTERNALSERVERERROR,
3247 "osrfMethodException",
3249 "Severe query error in WHERE predicate -- see error log for more details"
3253 buffer_free(having_buf);
3254 buffer_free(group_buf);
3255 buffer_free(order_buf);
3256 buffer_free(sql_buf);
3257 if (defaultselhash) jsonObjectFree(defaultselhash);
3262 // Build a HAVING clause, if there is one
3263 if ( having_hash ) {
3264 buffer_add(sql_buf, " HAVING ");
3266 // and it's on the the WHERE clause
3267 char* pred = searchWHERE( having_hash, core_meta, AND_OP_JOIN, ctx );
3270 buffer_add(sql_buf, pred);
3274 osrfAppSessionStatus(
3276 OSRF_STATUS_INTERNALSERVERERROR,
3277 "osrfMethodException",
3279 "Severe query error in HAVING predicate -- see error log for more details"
3283 buffer_free(having_buf);
3284 buffer_free(group_buf);
3285 buffer_free(order_buf);
3286 buffer_free(sql_buf);
3287 if (defaultselhash) jsonObjectFree(defaultselhash);
3292 // Build an ORDER BY clause, if there is one
3294 jsonIterator* class_itr = jsonNewIterator( order_hash );
3295 while ( (snode = jsonIteratorNext( class_itr )) ) {
3297 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
3300 if ( snode->type == JSON_HASH ) {
3302 jsonIterator* order_itr = jsonNewIterator( snode );
3303 while ( (onode = jsonIteratorNext( order_itr )) ) {
3305 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ))
3308 const char* direction = NULL;
3309 if ( onode->type == JSON_HASH ) {
3310 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3311 string = searchFieldTransform(
3313 oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ),
3317 growing_buffer* field_buf = buffer_init(16);
3318 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3319 string = buffer_release(field_buf);
3322 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3323 const char* dir = jsonObjectGetString(tmp_const);
3324 if (!strncasecmp(dir, "d", 1)) {
3325 direction = " DESC";
3332 string = strdup(order_itr->key);
3333 const char* dir = jsonObjectGetString(onode);
3334 if (!strncasecmp(dir, "d", 1)) {
3335 direction = " DESC";
3344 buffer_add(order_buf, ", ");
3347 buffer_add(order_buf, string);
3351 buffer_add(order_buf, direction);
3355 // jsonIteratorFree(order_itr);
3357 } else if ( snode->type == JSON_ARRAY ) {
3359 jsonIterator* order_itr = jsonNewIterator( snode );
3360 while ( (onode = jsonIteratorNext( order_itr )) ) {
3362 const char* _f = jsonObjectGetString( onode );
3364 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, _f))
3370 buffer_add(order_buf, ", ");
3373 buffer_add(order_buf, _f);
3376 // jsonIteratorFree(order_itr);
3379 // IT'S THE OOOOOOOOOOOLD STYLE!
3381 osrfLogError(OSRF_LOG_MARK, "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3383 osrfAppSessionStatus(
3385 OSRF_STATUS_INTERNALSERVERERROR,
3386 "osrfMethodException",
3388 "Severe query error -- see error log for more details"
3393 buffer_free(having_buf);
3394 buffer_free(group_buf);
3395 buffer_free(order_buf);
3396 buffer_free(sql_buf);
3397 if (defaultselhash) jsonObjectFree(defaultselhash);
3398 jsonIteratorFree(class_itr);
3403 // jsonIteratorFree(class_itr);
3407 string = buffer_release(group_buf);
3409 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
3410 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3411 OSRF_BUFFER_ADD( sql_buf, string );
3416 string = buffer_release(having_buf);
3419 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3420 OSRF_BUFFER_ADD( sql_buf, string );
3425 string = buffer_release(order_buf);
3428 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3429 OSRF_BUFFER_ADD( sql_buf, string );
3435 const char* str = jsonObjectGetString(limit);
3436 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
3440 const char* str = jsonObjectGetString(offset);
3441 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
3444 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3447 if (defaultselhash) jsonObjectFree(defaultselhash);
3449 return buffer_release(sql_buf);
3453 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
3455 const char* locale = osrf_message_get_last_locale();
3457 osrfHash* fields = osrfHashGet(meta, "fields");
3458 char* core_class = osrfHashGet(meta, "classname");
3460 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
3462 jsonObject* node = NULL;
3463 jsonObject* snode = NULL;
3464 jsonObject* onode = NULL;
3465 const jsonObject* _tmp = NULL;
3466 jsonObject* selhash = NULL;
3467 jsonObject* defaultselhash = NULL;
3469 growing_buffer* sql_buf = buffer_init(128);
3470 growing_buffer* select_buf = buffer_init(128);
3472 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
3473 defaultselhash = jsonNewObjectType(JSON_HASH);
3474 selhash = defaultselhash;
3477 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
3478 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
3479 jsonObject* flist = jsonObjectGetKey( selhash, core_class );
3484 osrfStringArray* keys = osrfHashKeys( fields );
3485 while ( (field = osrfStringArrayGetString(keys, i++)) ) {
3486 if( ! str_is_true( osrfHashGet( osrfHashGet( fields, field ), "virtual" ) ) )
3487 jsonObjectPush( flist, jsonNewObject( field ) );
3489 osrfStringArrayFree(keys);
3493 jsonIterator* class_itr = jsonNewIterator( selhash );
3494 while ( (snode = jsonIteratorNext( class_itr )) ) {
3496 char* cname = class_itr->key;
3497 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
3498 if (!idlClass) continue;
3500 if (strcmp(core_class,class_itr->key)) {
3501 if (!join_hash) continue;
3503 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
3505 jsonObjectFree(found);
3509 jsonObjectFree(found);
3512 jsonIterator* select_itr = jsonNewIterator( snode );
3513 while ( (node = jsonIteratorNext( select_itr )) ) {
3514 const char* item_str = jsonObjectGetString( node );
3515 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
3516 char* fname = osrfHashGet(field, "name");
3518 if (!field) continue;
3523 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
3528 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
3529 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
3532 i18n = osrfHashGet(field, "i18n");
3534 if( str_is_true( i18n ) ) {
3535 char* pkey = osrfHashGet(idlClass, "primarykey");
3536 char* tname = osrfHashGet(idlClass, "tablename");
3538 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);
3540 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3543 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3547 jsonIteratorFree(select_itr);
3550 jsonIteratorFree(class_itr);
3552 char* col_list = buffer_release(select_buf);
3553 char* table = getSourceDefinition(meta);
3555 table = strdup( "(null)" );
3557 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
3562 char* join_clause = searchJOIN( join_hash, meta );
3563 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
3564 OSRF_BUFFER_ADD(sql_buf, join_clause);
3568 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
3569 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
3571 buffer_add(sql_buf, " WHERE ");
3573 char* pred = searchWHERE( search_hash, meta, AND_OP_JOIN, ctx );
3575 osrfAppSessionStatus(
3577 OSRF_STATUS_INTERNALSERVERERROR,
3578 "osrfMethodException",
3580 "Severe query error -- see error log for more details"
3582 buffer_free(sql_buf);
3583 if(defaultselhash) jsonObjectFree(defaultselhash);
3586 buffer_add(sql_buf, pred);
3591 char* string = NULL;
3592 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
3594 growing_buffer* order_buf = buffer_init(128);
3597 jsonIterator* class_itr = jsonNewIterator( _tmp );
3598 while ( (snode = jsonIteratorNext( class_itr )) ) {
3600 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
3603 if ( snode->type == JSON_HASH ) {
3605 jsonIterator* order_itr = jsonNewIterator( snode );
3606 while ( (onode = jsonIteratorNext( order_itr )) ) {
3608 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ))
3611 char* direction = NULL;
3612 if ( onode->type == JSON_HASH ) {
3613 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3614 string = searchFieldTransform(
3616 oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ),
3620 growing_buffer* field_buf = buffer_init(16);
3621 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3622 string = buffer_release(field_buf);
3625 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
3626 const char* dir = jsonObjectGetString(_tmp);
3627 if (!strncasecmp(dir, "d", 1)) {
3628 direction = " DESC";
3635 string = strdup(order_itr->key);
3636 const char* dir = jsonObjectGetString(onode);
3637 if (!strncasecmp(dir, "d", 1)) {
3638 direction = " DESC";
3647 buffer_add(order_buf, ", ");
3650 buffer_add(order_buf, string);
3654 buffer_add(order_buf, direction);
3659 jsonIteratorFree(order_itr);
3662 const char* str = jsonObjectGetString(snode);
3663 buffer_add(order_buf, str);
3669 jsonIteratorFree(class_itr);
3671 string = buffer_release(order_buf);
3674 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3675 OSRF_BUFFER_ADD( sql_buf, string );
3681 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
3682 const char* str = jsonObjectGetString(_tmp);
3690 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
3692 const char* str = jsonObjectGetString(_tmp);
3701 if (defaultselhash) jsonObjectFree(defaultselhash);
3703 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3704 return buffer_release(sql_buf);
3707 int doJSONSearch ( osrfMethodContext* ctx ) {
3708 if(osrfMethodVerifyContext( ctx )) {
3709 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
3713 osrfLogDebug(OSRF_LOG_MARK, "Recieved query request");
3718 dbhandle = writehandle;
3720 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
3724 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
3725 flags |= SELECT_DISTINCT;
3727 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
3728 flags |= DISABLE_I18N;
3730 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
3733 jsonObjectGetKey( hash, "select" ),
3734 jsonObjectGetKey( hash, "from" ),
3735 jsonObjectGetKey( hash, "where" ),
3736 jsonObjectGetKey( hash, "having" ),
3737 jsonObjectGetKey( hash, "order_by" ),
3738 jsonObjectGetKey( hash, "limit" ),
3739 jsonObjectGetKey( hash, "offset" ),
3748 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
3749 dbi_result result = dbi_conn_query(dbhandle, sql);
3752 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
3754 if (dbi_result_first_row(result)) {
3755 /* JSONify the result */
3756 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
3759 jsonObject* return_val = oilsMakeJSONFromResult( result );
3760 osrfAppRespond( ctx, return_val );
3761 jsonObjectFree( return_val );
3762 } while (dbi_result_next_row(result));
3765 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
3768 osrfAppRespondComplete( ctx, NULL );
3770 /* clean up the query */
3771 dbi_result_free(result);
3775 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
3776 osrfAppSessionStatus(
3778 OSRF_STATUS_INTERNALSERVERERROR,
3779 "osrfMethodException",
3781 "Severe query error -- see error log for more details"
3789 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
3790 const jsonObject* params, int* err ) {
3793 dbhandle = writehandle;
3795 osrfHash* links = osrfHashGet(meta, "links");
3796 osrfHash* fields = osrfHashGet(meta, "fields");
3797 char* core_class = osrfHashGet(meta, "classname");
3798 char* pkey = osrfHashGet(meta, "primarykey");
3800 const jsonObject* _tmp;
3802 jsonObject* search_hash = jsonObjectGetIndex(params, 0);
3803 jsonObject* order_hash = jsonObjectGetIndex(params, 1);
3805 char* sql = buildSELECT( search_hash, order_hash, meta, ctx );
3807 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
3812 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
3814 dbi_result result = dbi_conn_query(dbhandle, sql);
3815 if( NULL == result ) {
3816 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
3817 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
3818 osrfAppSessionStatus(
3820 OSRF_STATUS_INTERNALSERVERERROR,
3821 "osrfMethodException",
3823 "Severe query error -- see error log for more details"
3830 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
3833 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
3834 osrfHash* dedup = osrfNewHash();
3836 if (dbi_result_first_row(result)) {
3837 /* JSONify the result */
3838 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
3840 obj = oilsMakeFieldmapperFromResult( result, meta );
3841 char* pkey_val = oilsFMGetString( obj, pkey );
3842 if ( osrfHashGet( dedup, pkey_val ) ) {
3843 jsonObjectFree(obj);
3846 osrfHashSet( dedup, pkey_val, pkey_val );
3847 jsonObjectPush(res_list, obj);
3849 } while (dbi_result_next_row(result));
3851 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
3855 osrfHashFree(dedup);
3856 /* clean up the query */
3857 dbi_result_free(result);
3860 if (res_list->size && order_hash) {
3861 _tmp = jsonObjectGetKeyConst( order_hash, "flesh" );
3863 int x = (int)jsonObjectGetNumber(_tmp);
3864 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
3866 const jsonObject* temp_blob;
3867 if ((temp_blob = jsonObjectGetKeyConst( order_hash, "flesh_fields" )) && x > 0) {
3869 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
3870 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
3872 osrfStringArray* link_fields = NULL;
3875 if (flesh_fields->size == 1) {
3876 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
3877 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
3882 link_fields = osrfNewStringArray(1);
3883 jsonIterator* _i = jsonNewIterator( flesh_fields );
3884 while ((_f = jsonIteratorNext( _i ))) {
3885 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
3887 jsonIteratorFree(_i);
3892 jsonIterator* itr = jsonNewIterator( res_list );
3893 while ((cur = jsonIteratorNext( itr ))) {
3898 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
3900 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
3902 osrfHash* kid_link = osrfHashGet(links, link_field);
3903 if (!kid_link) continue;
3905 osrfHash* field = osrfHashGet(fields, link_field);
3906 if (!field) continue;
3908 osrfHash* value_field = field;
3910 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
3911 if (!kid_idl) continue;
3913 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
3914 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
3917 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
3918 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
3921 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
3923 if (link_map->size > 0) {
3924 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
3927 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
3932 osrfHashGet(kid_link, "class"),
3939 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
3940 osrfHashGet(kid_link, "field"),
3941 osrfHashGet(kid_link, "class"),
3942 osrfHashGet(kid_link, "key"),
3943 osrfHashGet(kid_link, "reltype")
3946 jsonObject* fake_params = jsonNewObjectType(JSON_ARRAY);
3947 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH)); // search hash
3948 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH)); // order/flesh hash
3950 osrfLogDebug(OSRF_LOG_MARK, "Creating dummy params object...");
3952 const char* search_key = jsonObjectGetString(
3955 atoi( osrfHashGet(value_field, "array_position") )
3960 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
3965 jsonObjectGetIndex(fake_params, 0),
3966 osrfHashGet(kid_link, "key"),
3967 jsonNewObject( search_key )
3971 jsonObjectGetIndex(fake_params, 1),
3973 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
3977 jsonObjectSetKey( jsonObjectGetIndex(fake_params, 1), "flesh_fields", jsonObjectClone(flesh_blob) );
3979 if (jsonObjectGetKeyConst(order_hash, "order_by")) {
3981 jsonObjectGetIndex(fake_params, 1),
3983 jsonObjectClone(jsonObjectGetKeyConst(order_hash, "order_by"))
3987 if (jsonObjectGetKeyConst(order_hash, "select")) {
3989 jsonObjectGetIndex(fake_params, 1),
3991 jsonObjectClone(jsonObjectGetKeyConst(order_hash, "select"))
3995 jsonObject* kids = doFieldmapperSearch(ctx, kid_idl, fake_params, err);
3998 jsonObjectFree( fake_params );
3999 osrfStringArrayFree(link_fields);
4000 jsonIteratorFree(itr);
4001 jsonObjectFree(res_list);
4002 jsonObjectFree(flesh_blob);
4006 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4008 jsonObject* X = NULL;
4009 if ( link_map->size > 0 && kids->size > 0 ) {
4011 kids = jsonNewObjectType(JSON_ARRAY);
4013 jsonObject* _k_node;
4014 jsonIterator* _k = jsonNewIterator( X );
4015 while ((_k_node = jsonIteratorNext( _k ))) {
4021 (unsigned long)atoi(
4027 osrfHashGet(kid_link, "class")
4031 osrfStringArrayGetString( link_map, 0 )
4040 jsonIteratorFree(_k);
4043 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4044 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4047 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4048 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4052 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4053 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4056 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4057 jsonObjectClone( kids )
4062 jsonObjectFree(kids);
4066 jsonObjectFree( kids );
4067 jsonObjectFree( fake_params );
4069 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4070 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4074 jsonObjectFree( flesh_blob );
4075 osrfStringArrayFree(link_fields);
4076 jsonIteratorFree(itr);
4085 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
4087 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4089 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
4091 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
4094 if (!verifyObjectClass(ctx, target)) {
4099 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4100 osrfAppSessionStatus(
4102 OSRF_STATUS_BADREQUEST,
4103 "osrfMethodException",
4105 "No active transaction -- required for UPDATE"
4111 // The following test is harmless but redundant. If a class is
4112 // readonly, we don't register an update method for it.
4113 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4114 osrfAppSessionStatus(
4116 OSRF_STATUS_BADREQUEST,
4117 "osrfMethodException",
4119 "Cannot UPDATE readonly class"
4125 dbhandle = writehandle;
4127 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4129 // Set the last_xact_id
4130 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4132 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
4133 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4136 char* pkey = osrfHashGet(meta, "primarykey");
4137 osrfHash* fields = osrfHashGet(meta, "fields");
4139 char* id = oilsFMGetString( target, pkey );
4143 "%s updating %s object with %s = %s",
4145 osrfHashGet(meta, "fieldmapper"),
4150 growing_buffer* sql = buffer_init(128);
4151 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4156 osrfStringArray* field_list = osrfHashKeys( fields );
4157 while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
4159 osrfHash* field = osrfHashGet( fields, field_name );
4161 if(!( strcmp( field_name, pkey ) )) continue;
4162 if( str_is_true( osrfHashGet(osrfHashGet(fields,field_name), "virtual") ) )
4165 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4167 int value_is_numeric = 0; // boolean
4169 if (field_object && field_object->classname) {
4170 value = oilsFMGetString(
4172 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4175 value = jsonObjectToSimpleString( field_object );
4176 if( field_object && JSON_NUMBER == field_object->type )
4177 value_is_numeric = 1;
4180 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s", osrfHashGet(meta, "fieldmapper"), field_name, value);
4182 if (!field_object || field_object->type == JSON_NULL) {
4183 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) ) && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
4184 if (first) first = 0;
4185 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4186 buffer_fadd( sql, " %s = NULL", field_name );
4189 } else if ( value_is_numeric || !strcmp( get_primitive( field ), "number") ) {
4190 if (first) first = 0;
4191 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4193 const char* numtype = get_datatype( field );
4194 if ( !strncmp( numtype, "INT", 3 ) ) {
4195 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
4196 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
4197 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
4199 // Must really be intended as a string, so quote it
4200 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4201 buffer_fadd( sql, " %s = %s", field_name, value );
4203 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4204 osrfAppSessionStatus(
4206 OSRF_STATUS_INTERNALSERVERERROR,
4207 "osrfMethodException",
4209 "Error quoting string -- please see the error log for more details"
4219 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
4222 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4223 if (first) first = 0;
4224 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4225 buffer_fadd( sql, " %s = %s", field_name, value );
4228 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4229 osrfAppSessionStatus(
4231 OSRF_STATUS_INTERNALSERVERERROR,
4232 "osrfMethodException",
4234 "Error quoting string -- please see the error log for more details"
4248 jsonObject* obj = jsonNewObject(id);
4250 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4251 dbi_conn_quote_string(dbhandle, &id);
4253 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
4255 char* query = buffer_release(sql);
4256 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
4258 dbi_result result = dbi_conn_query(dbhandle, query);
4262 jsonObjectFree(obj);
4263 obj = jsonNewObject(NULL);
4266 "%s ERROR updating %s object with %s = %s",
4268 osrfHashGet(meta, "fieldmapper"),
4279 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4281 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4283 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4284 osrfAppSessionStatus(
4286 OSRF_STATUS_BADREQUEST,
4287 "osrfMethodException",
4289 "No active transaction -- required for DELETE"
4295 // The following test is harmless but redundant. If a class is
4296 // readonly, we don't register a delete method for it.
4297 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4298 osrfAppSessionStatus(
4300 OSRF_STATUS_BADREQUEST,
4301 "osrfMethodException",
4303 "Cannot DELETE readonly class"
4309 dbhandle = writehandle;
4313 char* pkey = osrfHashGet(meta, "primarykey");
4321 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4322 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4327 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4330 if (!verifyObjectPCRUD( ctx, NULL )) {
4335 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4340 "%s deleting %s object with %s = %s",
4342 osrfHashGet(meta, "fieldmapper"),
4347 obj = jsonNewObject(id);
4349 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4350 dbi_conn_quote_string(writehandle, &id);
4352 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4355 jsonObjectFree(obj);
4356 obj = jsonNewObject(NULL);
4359 "%s ERROR deleting %s object with %s = %s",
4361 osrfHashGet(meta, "fieldmapper"),
4374 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4375 if(!(result && meta)) return jsonNULL;
4377 jsonObject* object = jsonNewObject(NULL);
4378 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4380 osrfHash* fields = osrfHashGet(meta, "fields");
4382 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4386 char dt_string[256];
4390 int columnIndex = 1;
4392 unsigned short type;
4393 const char* columnName;
4395 /* cycle through the column list */
4396 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4398 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4400 fmIndex = -1; // reset the position
4402 /* determine the field type and storage attributes */
4403 type = dbi_result_get_field_type(result, columnName);
4404 attr = dbi_result_get_field_attribs(result, columnName);
4406 /* fetch the fieldmapper index */
4407 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4409 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4412 const char* pos = (char*)osrfHashGet(_f, "array_position");
4413 if ( !pos ) continue;
4415 fmIndex = atoi( pos );
4416 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
4421 if (dbi_result_field_is_null(result, columnName)) {
4422 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
4427 case DBI_TYPE_INTEGER :
4429 if( attr & DBI_INTEGER_SIZE8 )
4430 jsonObjectSetIndex( object, fmIndex,
4431 jsonNewNumberObject(dbi_result_get_longlong(result, columnName)));
4433 jsonObjectSetIndex( object, fmIndex,
4434 jsonNewNumberObject(dbi_result_get_int(result, columnName)));
4438 case DBI_TYPE_DECIMAL :
4439 jsonObjectSetIndex( object, fmIndex,
4440 jsonNewNumberObject(dbi_result_get_double(result, columnName)));
4443 case DBI_TYPE_STRING :
4449 jsonNewObject( dbi_result_get_string(result, columnName) )
4454 case DBI_TYPE_DATETIME :
4456 memset(dt_string, '\0', sizeof(dt_string));
4457 memset(&gmdt, '\0', sizeof(gmdt));
4459 _tmp_dt = dbi_result_get_datetime(result, columnName);
4462 if (!(attr & DBI_DATETIME_DATE)) {
4463 gmtime_r( &_tmp_dt, &gmdt );
4464 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4465 } else if (!(attr & DBI_DATETIME_TIME)) {
4466 localtime_r( &_tmp_dt, &gmdt );
4467 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4469 localtime_r( &_tmp_dt, &gmdt );
4470 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4473 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
4477 case DBI_TYPE_BINARY :
4478 osrfLogError( OSRF_LOG_MARK,
4479 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4487 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
4488 if(!result) return jsonNULL;
4490 jsonObject* object = jsonNewObject(NULL);
4493 char dt_string[256];
4497 int columnIndex = 1;
4499 unsigned short type;
4500 const char* columnName;
4502 /* cycle through the column list */
4503 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4505 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4507 fmIndex = -1; // reset the position
4509 /* determine the field type and storage attributes */
4510 type = dbi_result_get_field_type(result, columnName);
4511 attr = dbi_result_get_field_attribs(result, columnName);
4513 if (dbi_result_field_is_null(result, columnName)) {
4514 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
4519 case DBI_TYPE_INTEGER :
4521 if( attr & DBI_INTEGER_SIZE8 )
4522 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_longlong(result, columnName)) );
4524 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_int(result, columnName)) );
4527 case DBI_TYPE_DECIMAL :
4528 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_double(result, columnName)) );
4531 case DBI_TYPE_STRING :
4532 jsonObjectSetKey( object, columnName, jsonNewObject(dbi_result_get_string(result, columnName)) );
4535 case DBI_TYPE_DATETIME :
4537 memset(dt_string, '\0', sizeof(dt_string));
4538 memset(&gmdt, '\0', sizeof(gmdt));
4540 _tmp_dt = dbi_result_get_datetime(result, columnName);
4543 if (!(attr & DBI_DATETIME_DATE)) {
4544 gmtime_r( &_tmp_dt, &gmdt );
4545 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4546 } else if (!(attr & DBI_DATETIME_TIME)) {
4547 localtime_r( &_tmp_dt, &gmdt );
4548 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4550 localtime_r( &_tmp_dt, &gmdt );
4551 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4554 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
4557 case DBI_TYPE_BINARY :
4558 osrfLogError( OSRF_LOG_MARK,
4559 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4567 // Interpret a string as true or false
4568 static int str_is_true( const char* str ) {
4569 if( NULL == str || strcasecmp( str, "true" ) )
4575 // Interpret a jsonObject as true or false
4576 static int obj_is_true( const jsonObject* obj ) {
4579 else switch( obj->type )
4587 if( strcasecmp( obj->value.s, "true" ) )
4591 case JSON_NUMBER : // Support 1/0 for perl's sake
4592 if( jsonObjectGetNumber( obj ) == 1.0 )
4601 // Translate a numeric code into a text string identifying a type of
4602 // jsonObject. To be used for building error messages.
4603 static const char* json_type( int code ) {
4609 return "JSON_ARRAY";
4611 return "JSON_STRING";
4613 return "JSON_NUMBER";
4619 return "(unrecognized)";
4623 // Extract the "primitive" attribute from an IDL field definition.
4624 // If we haven't initialized the app, then we must be running in
4625 // some kind of testbed. In that case, default to "string".
4626 static const char* get_primitive( osrfHash* field ) {
4627 const char* s = osrfHashGet( field, "primitive" );
4629 if( child_initialized )
4632 "%s ERROR No \"datatype\" attribute for field \"%s\"",
4634 osrfHashGet( field, "name" )
4642 // Extract the "datatype" attribute from an IDL field definition.
4643 // If we haven't initialized the app, then we must be running in
4644 // some kind of testbed. In that case, default to to NUMERIC,
4645 // since we look at the datatype only for numbers.
4646 static const char* get_datatype( osrfHash* field ) {
4647 const char* s = osrfHashGet( field, "datatype" );
4649 if( child_initialized )
4652 "%s ERROR No \"datatype\" attribute for field \"%s\"",
4654 osrfHashGet( field, "name" )
4663 If the input string is potentially a valid SQL identifier, return 1.
4666 Purpose: to prevent certain kinds of SQL injection. To that end we
4667 don't necessarily need to follow all the rules exactly, such as requiring
4668 that the first character not be a digit.
4670 We allow leading and trailing white space. In between, we do not allow
4671 punctuation (except for underscores and dollar signs), control
4672 characters, or embedded white space.
4674 More pedantically we should allow quoted identifiers containing arbitrary
4675 characters, but for the foreseeable future such quoted identifiers are not
4676 likely to be an issue.
4678 static int is_identifier( const char* s) {
4682 // Skip leading white space
4683 while( isspace( (unsigned char) *s ) )
4687 return 0; // Nothing but white space? Not okay.
4689 // Check each character until we reach white space or
4690 // end-of-string. Letters, digits, underscores, and
4691 // dollar signs are okay. Control characters and other
4692 // punctuation characters are not okay. Anything else
4693 // is okay -- it could for example be part of a multibyte
4694 // UTF8 character such as a letter with diacritical marks,
4695 // and those are allowed.
4697 if( isalnum( (unsigned char) *s )
4700 ; // Fine; keep going
4701 else if( ispunct( (unsigned char) *s )
4702 || iscntrl( (unsigned char) *s ) )
4705 } while( *s && ! isspace( (unsigned char) *s ) );
4707 // If we found any white space in the above loop,
4708 // the rest had better be all white space.
4710 while( isspace( (unsigned char) *s ) )
4714 return 0; // White space was embedded within non-white space
4720 Determine whether to accept a character string as a comparison operator.
4721 Return 1 if it's good, or 0 if it's bad.
4723 We don't validate it for real. We just make sure that it doesn't contain
4724 any semicolons or white space (with a special exception for the
4725 "SIMILAR TO" operator). The idea is to block certain kinds of SQL
4726 injection. If it has no semicolons or white space but it's still not a
4727 valid operator, then the database will complain.
4729 Another approach would be to compare the string against a short list of
4730 approved operators. We don't do that because we want to allow custom
4731 operators like ">100*", which would be difficult or impossible to
4732 express otherwise in a JSON query.
4734 static int is_good_operator( const char* op ) {
4735 if( !op ) return 0; // Sanity check
4739 if( isspace( (unsigned char) *s ) ) {
4740 // Special exception for SIMILAR TO. Someday we might make
4741 // exceptions for IS DISTINCT FROM and IS NOT DISTINCT FROM.
4742 if( !strcasecmp( op, "similar to" ) )
4747 else if( ';' == *s )