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, ", ");
1699 // Append the literal value -- quoted if not a number
1700 if ( JSON_NUMBER == in_item->type ) {
1701 char* val = jsonNumberToDBString( field, in_item );
1702 OSRF_BUFFER_ADD( sql_buf, val );
1705 } else if ( !strcmp( get_primitive( field ), "number") ) {
1706 char* val = jsonNumberToDBString( field, in_item );
1707 OSRF_BUFFER_ADD( sql_buf, val );
1711 char* key_string = jsonObjectToSimpleString(in_item);
1712 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1713 OSRF_BUFFER_ADD( sql_buf, key_string );
1716 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1718 buffer_free(sql_buf);
1724 if( in_item_first ) {
1725 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1726 buffer_free( sql_buf );
1731 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1733 return buffer_release(sql_buf);
1736 // Receive a JSON_ARRAY representing a function call. The first
1737 // entry in the array is the function name. The rest are parameters.
1738 static char* searchValueTransform( const jsonObject* array ) {
1739 growing_buffer* sql_buf = buffer_init(32);
1741 jsonObject* func_item;
1743 // Get the function name
1744 if( array->size > 0 ) {
1745 func_item = jsonObjectGetIndex( array, 0 );
1746 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1747 OSRF_BUFFER_ADD( sql_buf, "( " );
1750 // Get the parameters
1751 int func_item_index = 1; // We already grabbed the zeroth entry
1752 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1754 // Add a separator comma, if we need one
1755 if( func_item_index > 2 )
1756 buffer_add( sql_buf, ", " );
1758 // Add the current parameter
1759 if (func_item->type == JSON_NULL) {
1760 buffer_add( sql_buf, "NULL" );
1762 char* val = jsonObjectToSimpleString(func_item);
1763 if ( dbi_conn_quote_string(dbhandle, &val) ) {
1764 OSRF_BUFFER_ADD( sql_buf, val );
1767 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1768 buffer_free(sql_buf);
1775 buffer_add( sql_buf, " )" );
1777 return buffer_release(sql_buf);
1780 static char* searchFunctionPredicate (const char* class, osrfHash* field,
1781 const jsonObject* node, const char* node_key) {
1783 char* val = searchValueTransform(node);
1787 growing_buffer* sql_buf = buffer_init(32);
1792 osrfHashGet(field, "name"),
1799 return buffer_release(sql_buf);
1802 // class is a class name
1803 // field is a field definition as stored in the IDL
1804 // node comes from the method parameter, and represents an entry in the SELECT list
1805 static char* searchFieldTransform (const char* class, osrfHash* field, const jsonObject* node) {
1806 growing_buffer* sql_buf = buffer_init(32);
1808 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
1809 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
1811 if(transform_subcolumn)
1812 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
1814 if (field_transform) {
1815 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class, osrfHashGet(field, "name"));
1816 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
1819 if( array->type != JSON_ARRAY ) {
1820 osrfLogError( OSRF_LOG_MARK,
1821 "%s: Expected JSON_ARRAY for function params; found %s",
1822 MODULENAME, json_type( array->type ) );
1823 buffer_free( sql_buf );
1826 int func_item_index = 0;
1827 jsonObject* func_item;
1828 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1830 char* val = jsonObjectToSimpleString(func_item);
1833 buffer_add( sql_buf, ",NULL" );
1834 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
1835 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
1836 OSRF_BUFFER_ADD( sql_buf, val );
1838 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1840 buffer_free(sql_buf);
1847 buffer_add( sql_buf, " )" );
1850 buffer_fadd( sql_buf, "\"%s\".%s", class, osrfHashGet(field, "name"));
1853 if (transform_subcolumn)
1854 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
1856 return buffer_release(sql_buf);
1859 static char* searchFieldTransformPredicate (const char* class, osrfHash* field,
1860 const jsonObject* node, const char* node_key) {
1861 char* field_transform = searchFieldTransform( class, field, node );
1864 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
1865 if ( ! value_obj ) {
1866 value = searchWHERE( node, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1868 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME, value);
1869 free(field_transform);
1872 } else if ( value_obj->type == JSON_ARRAY ) {
1873 value = searchValueTransform( value_obj );
1875 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform",
1877 free( field_transform );
1880 } else if ( value_obj->type == JSON_HASH ) {
1881 value = searchWHERE( value_obj, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1883 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME, value);
1884 free(field_transform);
1887 } else if ( value_obj->type == JSON_NUMBER ) {
1888 value = jsonNumberToDBString( field, value_obj );
1889 } else if ( value_obj->type != JSON_NULL ) {
1890 if ( !strcmp( get_primitive( field ), "number") ) {
1891 value = jsonNumberToDBString( field, value_obj );
1893 value = jsonObjectToSimpleString( value_obj );
1894 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
1895 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
1897 free(field_transform);
1903 growing_buffer* sql_buf = buffer_init(32);
1914 free(field_transform);
1916 return buffer_release(sql_buf);
1919 static char* searchSimplePredicate (const char* op, const char* class,
1920 osrfHash* field, const jsonObject* node) {
1922 if( ! is_good_operator( op ) ) {
1923 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
1929 // Get the value to which we are comparing the specified column
1930 if (node->type != JSON_NULL) {
1931 if ( node->type == JSON_NUMBER ) {
1932 val = jsonNumberToDBString( field, node );
1933 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
1934 val = jsonNumberToDBString( field, node );
1936 val = jsonObjectToSimpleString(node);
1941 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
1942 // Value is not numeric; enclose it in quotes
1943 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
1944 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
1950 // Compare to a null value
1951 val = strdup( "NULL" );
1952 if (strcmp( op, "=" ))
1958 growing_buffer* sql_buf = buffer_init(32);
1959 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class, osrfHashGet(field, "name"), op, val );
1960 char* pred = buffer_release( sql_buf );
1967 static char* searchBETWEENPredicate (const char* class, osrfHash* field, const jsonObject* node) {
1969 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
1970 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
1972 if( NULL == y_node ) {
1973 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
1976 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
1977 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
1984 if ( !strcmp( get_primitive( field ), "number") ) {
1985 x_string = jsonNumberToDBString(field, x_node);
1986 y_string = jsonNumberToDBString(field, y_node);
1989 x_string = jsonObjectToSimpleString(x_node);
1990 y_string = jsonObjectToSimpleString(y_node);
1991 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
1992 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
1993 MODULENAME, x_string, y_string);
2000 growing_buffer* sql_buf = buffer_init(32);
2001 buffer_fadd( sql_buf, "%s BETWEEN %s AND %s", osrfHashGet(field, "name"), x_string, y_string );
2005 return buffer_release(sql_buf);
2008 static char* searchPredicate ( const char* class, osrfHash* field,
2009 jsonObject* node, osrfMethodContext* ctx ) {
2012 if (node->type == JSON_ARRAY) { // equality IN search
2013 pred = searchINPredicate( class, field, node, NULL, ctx );
2014 } else if (node->type == JSON_HASH) { // non-equality search
2015 jsonIterator* pred_itr = jsonNewIterator( node );
2016 if( !jsonIteratorHasNext( pred_itr ) ) {
2017 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2018 MODULENAME, osrfHashGet(field, "name") );
2020 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2022 // Verify that there are no additional predicates
2023 if( jsonIteratorHasNext( pred_itr ) ) {
2024 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2025 MODULENAME, osrfHashGet(field, "name") );
2026 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2027 pred = searchBETWEENPredicate( class, field, pred_node );
2028 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2029 pred = searchINPredicate( class, field, pred_node, pred_itr->key, ctx );
2030 else if ( pred_node->type == JSON_ARRAY )
2031 pred = searchFunctionPredicate( class, field, pred_node, pred_itr->key );
2032 else if ( pred_node->type == JSON_HASH )
2033 pred = searchFieldTransformPredicate( class, field, pred_node, pred_itr->key );
2035 pred = searchSimplePredicate( pred_itr->key, class, field, pred_node );
2037 jsonIteratorFree(pred_itr);
2039 } else if (node->type == JSON_NULL) { // IS NULL search
2040 growing_buffer* _p = buffer_init(64);
2043 "\"%s\".%s IS NULL",
2045 osrfHashGet(field, "name")
2047 pred = buffer_release(_p);
2048 } else { // equality search
2049 pred = searchSimplePredicate( "=", class, field, node );
2068 field : call_number,
2084 static char* searchJOIN ( const jsonObject* join_hash, osrfHash* leftmeta ) {
2086 const jsonObject* working_hash;
2087 jsonObject* freeable_hash = NULL;
2089 if (join_hash->type == JSON_STRING) {
2090 // create a wrapper around a copy of the original
2091 const char* _tmp = jsonObjectGetString( join_hash );
2092 freeable_hash = jsonNewObjectType(JSON_HASH);
2093 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2094 working_hash = freeable_hash;
2097 if( join_hash->type != JSON_HASH ) {
2100 "%s: JOIN failed; expected JSON object type not found",
2105 working_hash = join_hash;
2108 growing_buffer* join_buf = buffer_init(128);
2109 const char* leftclass = osrfHashGet(leftmeta, "classname");
2111 jsonObject* snode = NULL;
2112 jsonIterator* search_itr = jsonNewIterator( working_hash );
2114 while ( (snode = jsonIteratorNext( search_itr )) ) {
2115 const char* class = search_itr->key;
2116 osrfHash* idlClass = osrfHashGet( oilsIDL(), class );
2120 "%s: JOIN failed. No class \"%s\" defined in IDL",
2124 jsonIteratorFree( search_itr );
2125 buffer_free( join_buf );
2127 jsonObjectFree( freeable_hash );
2131 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2132 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2134 if (field && !fkey) {
2135 fkey = (const char*)oilsIDLFindPath("/%s/links/%s/key", class, field);
2139 "%s: JOIN failed. No link defined from %s.%s to %s",
2145 buffer_free(join_buf);
2147 jsonObjectFree(freeable_hash);
2148 jsonIteratorFree(search_itr);
2152 } else if (!field && fkey) {
2153 field = (const char*)oilsIDLFindPath("/%s/links/%s/key", leftclass, fkey );
2157 "%s: JOIN failed. No link defined from %s.%s to %s",
2163 buffer_free(join_buf);
2165 jsonObjectFree(freeable_hash);
2166 jsonIteratorFree(search_itr);
2171 } else if (!field && !fkey) {
2172 osrfHash* _links = oilsIDL_links( leftclass );
2174 // For each link defined for the left class:
2175 // see if the link references the joined class
2176 osrfHashIterator* itr = osrfNewHashIterator( _links );
2177 osrfHash* curr_link = NULL;
2178 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2179 const char* other_class = osrfHashGet( curr_link, "class" );
2180 if( other_class && !strcmp( other_class, class ) ) {
2182 // Found a link between the classes
2183 fkey = osrfHashIteratorKey( itr );
2184 field = osrfHashGet( curr_link, "key" );
2188 osrfHashIteratorFree( itr );
2190 if (!field || !fkey) {
2191 // Do another such search, with the classes reversed
2192 _links = oilsIDL_links( class );
2194 // For each link defined for the joined class:
2195 // see if the link references the left class
2196 osrfHashIterator* itr = osrfNewHashIterator( _links );
2197 osrfHash* curr_link = NULL;
2198 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2199 const char* other_class = osrfHashGet( curr_link, "class" );
2200 if( other_class && !strcmp( other_class, leftclass ) ) {
2202 // Found a link between the classes
2203 fkey = osrfHashIteratorKey( itr );
2204 field = osrfHashGet( curr_link, "key" );
2208 osrfHashIteratorFree( itr );
2211 if (!field || !fkey) {
2214 "%s: JOIN failed. No link defined between %s and %s",
2219 buffer_free(join_buf);
2221 jsonObjectFree(freeable_hash);
2222 jsonIteratorFree(search_itr);
2228 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2230 if ( !strcasecmp(type,"left") ) {
2231 buffer_add(join_buf, " LEFT JOIN");
2232 } else if ( !strcasecmp(type,"right") ) {
2233 buffer_add(join_buf, " RIGHT JOIN");
2234 } else if ( !strcasecmp(type,"full") ) {
2235 buffer_add(join_buf, " FULL JOIN");
2237 buffer_add(join_buf, " INNER JOIN");
2240 buffer_add(join_buf, " INNER JOIN");
2243 char* table = getSourceDefinition(idlClass);
2245 jsonIteratorFree( search_itr );
2246 buffer_free( join_buf );
2248 jsonObjectFree( freeable_hash );
2252 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2253 table, class, class, field, leftclass, fkey);
2256 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2258 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2259 if ( filter_op && !strcasecmp("or",filter_op) ) {
2260 buffer_add( join_buf, " OR " );
2262 buffer_add( join_buf, " AND " );
2265 char* jpred = searchWHERE( filter, idlClass, AND_OP_JOIN, NULL );
2267 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2268 OSRF_BUFFER_ADD( join_buf, jpred );
2273 "%s: JOIN failed. Invalid conditional expression.",
2276 jsonIteratorFree( search_itr );
2277 buffer_free( join_buf );
2279 jsonObjectFree( freeable_hash );
2284 buffer_add(join_buf, " ) ");
2286 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2288 char* jpred = searchJOIN( join_filter, idlClass );
2289 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2290 OSRF_BUFFER_ADD( join_buf, jpred );
2296 jsonObjectFree(freeable_hash);
2297 jsonIteratorFree(search_itr);
2299 return buffer_release(join_buf);
2304 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2305 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2306 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2308 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2310 search_hash is the JSON expression of the conditions.
2311 meta is the class definition from the IDL, for the relevant table.
2312 opjoin_type indicates whether multiple conditions, if present, should be
2313 connected by AND or OR.
2314 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2315 to pass it to other functions -- and all they do with it is to use the session
2316 and request members to send error messages back to the client.
2320 static char* searchWHERE ( const jsonObject* search_hash, osrfHash* meta, int opjoin_type, osrfMethodContext* ctx ) {
2324 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2332 growing_buffer* sql_buf = buffer_init(128);
2334 jsonObject* node = NULL;
2337 if ( search_hash->type == JSON_ARRAY ) {
2338 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2339 jsonIterator* search_itr = jsonNewIterator( search_hash );
2340 if( !jsonIteratorHasNext( search_itr ) ) {
2343 "%s: Invalid predicate structure: empty JSON array",
2346 jsonIteratorFree( search_itr );
2347 buffer_free( sql_buf );
2351 while ( (node = jsonIteratorNext( search_itr )) ) {
2355 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2356 else buffer_add(sql_buf, " AND ");
2359 char* subpred = searchWHERE( node, meta, opjoin_type, ctx );
2361 buffer_fadd(sql_buf, "( %s )", subpred);
2364 jsonIteratorFree( search_itr );
2365 buffer_free( sql_buf );
2369 jsonIteratorFree(search_itr);
2371 } else if ( search_hash->type == JSON_HASH ) {
2372 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2373 jsonIterator* search_itr = jsonNewIterator( search_hash );
2374 if( !jsonIteratorHasNext( search_itr ) ) {
2377 "%s: Invalid predicate structure: empty JSON object",
2380 jsonIteratorFree( search_itr );
2381 buffer_free( sql_buf );
2385 while ( (node = jsonIteratorNext( search_itr )) ) {
2390 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2391 else buffer_add(sql_buf, " AND ");
2394 if ( '+' == search_itr->key[ 0 ] ) {
2395 if ( node->type == JSON_STRING ) {
2396 // Intended purpose; to allow reference to a Boolean column
2398 // Verify that the class alias is not empty
2399 if( '\0' == search_itr->key[ 1 ] ) {
2402 "%s: Table alias is empty",
2405 jsonIteratorFree( search_itr );
2406 buffer_free( sql_buf );
2410 // Verify that the string looks like an identifier.
2411 const char* subpred = jsonObjectGetString( node );
2412 if( ! is_identifier( subpred ) ) {
2415 "%s: Invalid boolean identifier in WHERE clause: \"%s\"",
2419 jsonIteratorFree( search_itr );
2420 buffer_free( sql_buf );
2424 buffer_fadd(sql_buf, " \"%s\".%s ", search_itr->key + 1, subpred);
2426 char* subpred = searchWHERE( node, osrfHashGet( oilsIDL(), search_itr->key + 1 ), AND_OP_JOIN, ctx );
2428 buffer_fadd(sql_buf, "( %s )", subpred);
2431 jsonIteratorFree( search_itr );
2432 buffer_free( sql_buf );
2436 } else if ( !strcasecmp("-or",search_itr->key) ) {
2437 char* subpred = searchWHERE( node, meta, OR_OP_JOIN, ctx );
2439 buffer_fadd(sql_buf, "( %s )", subpred);
2442 buffer_free( sql_buf );
2445 } else if ( !strcasecmp("-and",search_itr->key) ) {
2446 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2448 buffer_fadd(sql_buf, "( %s )", subpred);
2451 buffer_free( sql_buf );
2454 } else if ( !strcasecmp("-not",search_itr->key) ) {
2455 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2457 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2460 buffer_free( sql_buf );
2463 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2464 char* subpred = SELECT(
2466 jsonObjectGetKey( node, "select" ),
2467 jsonObjectGetKey( node, "from" ),
2468 jsonObjectGetKey( node, "where" ),
2469 jsonObjectGetKey( node, "having" ),
2470 jsonObjectGetKey( node, "order_by" ),
2471 jsonObjectGetKey( node, "limit" ),
2472 jsonObjectGetKey( node, "offset" ),
2476 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2478 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2479 char* subpred = SELECT(
2481 jsonObjectGetKey( node, "select" ),
2482 jsonObjectGetKey( node, "from" ),
2483 jsonObjectGetKey( node, "where" ),
2484 jsonObjectGetKey( node, "having" ),
2485 jsonObjectGetKey( node, "order_by" ),
2486 jsonObjectGetKey( node, "limit" ),
2487 jsonObjectGetKey( node, "offset" ),
2491 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2495 char* class = osrfHashGet(meta, "classname");
2496 osrfHash* fields = osrfHashGet(meta, "fields");
2497 osrfHash* field = osrfHashGet( fields, search_itr->key );
2501 char* table = getSourceDefinition(meta);
2503 table = strdup( "(?)" );
2506 "%s: Attempt to reference non-existent column %s on %s (%s)",
2512 buffer_free(sql_buf);
2514 jsonIteratorFree(search_itr);
2518 char* subpred = searchPredicate( class, field, node, ctx );
2520 buffer_add( sql_buf, subpred );
2523 buffer_free(sql_buf);
2524 jsonIteratorFree(search_itr);
2529 jsonIteratorFree(search_itr);
2532 // ERROR ... only hash and array allowed at this level
2533 char* predicate_string = jsonObjectToJSON( search_hash );
2536 "%s: Invalid predicate structure: %s",
2540 buffer_free(sql_buf);
2541 free(predicate_string);
2545 return buffer_release(sql_buf);
2549 /* method context */ osrfMethodContext* ctx,
2551 /* SELECT */ jsonObject* selhash,
2552 /* FROM */ jsonObject* join_hash,
2553 /* WHERE */ jsonObject* search_hash,
2554 /* HAVING */ jsonObject* having_hash,
2555 /* ORDER BY */ jsonObject* order_hash,
2556 /* LIMIT */ jsonObject* limit,
2557 /* OFFSET */ jsonObject* offset,
2558 /* flags */ int flags
2560 const char* locale = osrf_message_get_last_locale();
2562 // in case we don't get a select list
2563 jsonObject* defaultselhash = NULL;
2565 // general tmp objects
2566 const jsonObject* tmp_const;
2567 jsonObject* selclass = NULL;
2568 jsonObject* selfield = NULL;
2569 jsonObject* snode = NULL;
2570 jsonObject* onode = NULL;
2572 char* string = NULL;
2573 int from_function = 0;
2578 // the core search class
2579 char* core_class = NULL;
2581 // metadata about the core search class
2582 osrfHash* core_meta = NULL;
2584 // punt if there's no core class
2585 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2588 "%s: FROM clause is missing or empty",
2592 osrfAppSessionStatus(
2594 OSRF_STATUS_INTERNALSERVERERROR,
2595 "osrfMethodException",
2597 "FROM clause is missing or empty in JSON query"
2602 // get the core class -- the only key of the top level FROM clause, or a string
2603 if (join_hash->type == JSON_HASH) {
2604 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2605 snode = jsonIteratorNext( tmp_itr );
2607 core_class = strdup( tmp_itr->key );
2610 jsonObject* extra = jsonIteratorNext( tmp_itr );
2612 jsonIteratorFree( tmp_itr );
2615 // There shouldn't be more than one entry in join_hash
2619 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2623 osrfAppSessionStatus(
2625 OSRF_STATUS_INTERNALSERVERERROR,
2626 "osrfMethodException",
2628 "Malformed FROM clause in JSON query"
2631 return NULL; // Malformed join_hash; extra entry
2633 } else if (join_hash->type == JSON_ARRAY) {
2635 core_class = jsonObjectToSimpleString( jsonObjectGetIndex(join_hash, 0) );
2638 } else if (join_hash->type == JSON_STRING) {
2639 core_class = jsonObjectToSimpleString( join_hash );
2645 "%s: FROM clause is unexpected JSON type: %s",
2647 json_type( join_hash->type )
2650 osrfAppSessionStatus(
2652 OSRF_STATUS_INTERNALSERVERERROR,
2653 "osrfMethodException",
2655 "Ill-formed FROM clause in JSON query"
2661 if (!from_function) {
2662 // Get the IDL class definition for the core class
2663 core_meta = osrfHashGet( oilsIDL(), core_class );
2664 if( !core_meta ) { // Didn't find it?
2667 "%s: SELECT clause references undefined class: \"%s\"",
2672 osrfAppSessionStatus(
2674 OSRF_STATUS_INTERNALSERVERERROR,
2675 "osrfMethodException",
2677 "SELECT clause references undefined class in JSON query"
2683 // Make sure the class isn't virtual
2684 if( str_is_true( osrfHashGet( core_meta, "virtual" ) ) ) {
2687 "%s: Core class is virtual: \"%s\"",
2692 osrfAppSessionStatus(
2694 OSRF_STATUS_INTERNALSERVERERROR,
2695 "osrfMethodException",
2697 "FROM clause references virtual class in JSON query"
2704 // if the select list is empty, or the core class field list is '*',
2705 // build the default select list ...
2707 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
2708 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2709 } else if( selhash->type != JSON_HASH ) {
2712 "%s: Expected JSON_HASH for SELECT clause; found %s",
2714 json_type( selhash->type )
2718 osrfAppSessionStatus(
2720 OSRF_STATUS_INTERNALSERVERERROR,
2721 "osrfMethodException",
2723 "Malformed SELECT clause in JSON query"
2727 } else if ( (tmp_const = jsonObjectGetKeyConst( selhash, core_class )) && tmp_const->type == JSON_STRING ) {
2728 const char* _x = jsonObjectGetString( tmp_const );
2729 if (!strncmp( "*", _x, 1 )) {
2730 jsonObjectRemoveKey( selhash, core_class );
2731 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2736 growing_buffer* sql_buf = buffer_init(128);
2738 // temp buffer for the SELECT list
2739 growing_buffer* select_buf = buffer_init(128);
2740 growing_buffer* order_buf = buffer_init(128);
2741 growing_buffer* group_buf = buffer_init(128);
2742 growing_buffer* having_buf = buffer_init(128);
2744 int aggregate_found = 0; // boolean
2746 // Build a select list
2747 if(from_function) // From a function we select everything
2748 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
2751 // If we need to build a default list, prepare to do so
2752 jsonObject* _tmp = jsonObjectGetKey( selhash, core_class );
2753 if ( _tmp && !_tmp->size ) {
2755 osrfHash* core_fields = osrfHashGet( core_meta, "fields" );
2757 osrfHashIterator* field_itr = osrfNewHashIterator( core_fields );
2758 osrfHash* field_def;
2759 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2760 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2761 // This field is not virtual, so add it to the list
2762 jsonObjectPush( _tmp, jsonNewObject( osrfHashIteratorKey( field_itr ) ) );
2765 osrfHashIteratorFree( field_itr );
2768 // Now build the actual select list
2772 jsonIterator* selclass_itr = jsonNewIterator( selhash );
2773 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
2775 // Make sure the class is defined in the IDL
2776 const char* cname = selclass_itr->key;
2777 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
2781 "%s: Selected class \"%s\" not defined in IDL",
2787 osrfAppSessionStatus(
2789 OSRF_STATUS_INTERNALSERVERERROR,
2790 "osrfMethodException",
2792 "Selected class is not defined"
2794 jsonIteratorFree( selclass_itr );
2795 buffer_free( sql_buf );
2796 buffer_free( select_buf );
2797 buffer_free( order_buf );
2798 buffer_free( group_buf );
2799 buffer_free( having_buf );
2800 if( defaultselhash ) jsonObjectFree( defaultselhash );
2805 // Make sure the target relation is in the join tree.
2807 // At this point join_hash is a step down from the join_hash we
2808 // received as a parameter. If the original was a JSON_STRING,
2809 // then json_hash is now NULL. If the original was a JSON_HASH,
2810 // then json_hash is now the first (and only) entry in it,
2811 // denoting the core class. We've already excluded the
2812 // possibility that the original was a JSON_ARRAY, because in
2813 // that case from_function would be non-NULL, and we wouldn't
2816 int class_in_from_clause; // boolean
2818 if ( ! strcmp( core_class, cname ))
2819 // This is the core class -- no problem
2820 class_in_from_clause = 1;
2823 // There's only one class in the FROM clause, and this isn't it
2824 class_in_from_clause = 0;
2825 else if (join_hash->type == JSON_STRING) {
2826 // There's only one class in the FROM clause
2827 const char* str = jsonObjectGetString(join_hash);
2828 if ( strcmp( str, cname ) )
2829 class_in_from_clause = 0; // This isn't it
2831 class_in_from_clause = 1; // This is it
2833 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", cname);
2834 if ( 0 == found->size )
2835 class_in_from_clause = 0; // Nowhere in the join tree
2837 class_in_from_clause = 1; // Found it
2838 jsonObjectFree( found );
2842 // If the class isn't in the FROM clause, bail out
2843 if( ! class_in_from_clause ) {
2846 "%s: SELECT clause references class not in FROM clause: \"%s\"",
2851 osrfAppSessionStatus(
2853 OSRF_STATUS_INTERNALSERVERERROR,
2854 "osrfMethodException",
2856 "Selected class not in FROM clause in JSON query"
2858 jsonIteratorFree( selclass_itr );
2859 buffer_free( sql_buf );
2860 buffer_free( select_buf );
2861 buffer_free( order_buf );
2862 buffer_free( group_buf );
2863 buffer_free( having_buf );
2864 if( defaultselhash ) jsonObjectFree( defaultselhash );
2869 // Look up some attributes of the current class, so that we
2870 // don't have to look them up again for each field
2871 osrfHash* class_field_set = osrfHashGet( idlClass, "fields" );
2872 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
2873 const char* class_tname = osrfHashGet( idlClass, "tablename" );
2875 // stitch together the column list ...
2876 jsonIterator* select_itr = jsonNewIterator( selclass );
2877 while ( (selfield = jsonIteratorNext( select_itr )) ) { // for each SELECT column
2879 // If we need a separator comma, add one
2883 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
2886 // ... if it's a string, just toss it on the pile
2887 if (selfield->type == JSON_STRING) {
2889 // Look up the field in the IDL
2890 const char* col_name = jsonObjectGetString( selfield );
2891 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
2893 // No such field in current class
2896 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
2902 osrfAppSessionStatus(
2904 OSRF_STATUS_INTERNALSERVERERROR,
2905 "osrfMethodException",
2907 "Selected column not defined in JSON query"
2909 jsonIteratorFree( select_itr );
2910 jsonIteratorFree( selclass_itr );
2911 buffer_free( sql_buf );
2912 buffer_free( select_buf );
2913 buffer_free( order_buf );
2914 buffer_free( group_buf );
2915 buffer_free( having_buf );
2916 if( defaultselhash ) jsonObjectFree( defaultselhash );
2919 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2920 // Virtual field not allowed
2923 "%s: Selected column \"%s\" for class \"%s\" is virtual",
2929 osrfAppSessionStatus(
2931 OSRF_STATUS_INTERNALSERVERERROR,
2932 "osrfMethodException",
2934 "Selected column may not be virtual in JSON query"
2936 jsonIteratorFree( select_itr );
2937 jsonIteratorFree( selclass_itr );
2938 buffer_free( sql_buf );
2939 buffer_free( select_buf );
2940 buffer_free( order_buf );
2941 buffer_free( group_buf );
2942 buffer_free( having_buf );
2943 if( defaultselhash ) jsonObjectFree( defaultselhash );
2950 if (flags & DISABLE_I18N)
2953 i18n = osrfHashGet(field_def, "i18n");
2955 if( str_is_true( i18n ) ) {
2956 buffer_fadd( select_buf,
2957 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
2958 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
2960 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
2963 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
2966 // ... but it could be an object, in which case we check for a Field Transform
2967 } else if (selfield->type == JSON_HASH) {
2969 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
2971 // Get the field definition from the IDL
2972 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
2974 // No such field in current class
2977 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
2983 osrfAppSessionStatus(
2985 OSRF_STATUS_INTERNALSERVERERROR,
2986 "osrfMethodException",
2988 "Selected column is not defined in JSON query"
2990 jsonIteratorFree( select_itr );
2991 jsonIteratorFree( selclass_itr );
2992 buffer_free( sql_buf );
2993 buffer_free( select_buf );
2994 buffer_free( order_buf );
2995 buffer_free( group_buf );
2996 buffer_free( having_buf );
2997 if( defaultselhash ) jsonObjectFree( defaultselhash );
3000 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3001 // No such field in current class
3004 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3010 osrfAppSessionStatus(
3012 OSRF_STATUS_INTERNALSERVERERROR,
3013 "osrfMethodException",
3015 "Selected column is virtual in JSON query"
3017 jsonIteratorFree( select_itr );
3018 jsonIteratorFree( selclass_itr );
3019 buffer_free( sql_buf );
3020 buffer_free( select_buf );
3021 buffer_free( order_buf );
3022 buffer_free( group_buf );
3023 buffer_free( having_buf );
3024 if( defaultselhash ) jsonObjectFree( defaultselhash );
3029 // Decide what to use as a column alias
3031 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3032 _alias = jsonObjectGetString( tmp_const );
3033 } else { // Use field name as the alias
3037 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3038 char* transform_str = searchFieldTransform(cname, field_def, selfield);
3039 if( transform_str ) {
3040 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3041 free(transform_str);
3044 osrfAppSessionStatus(
3046 OSRF_STATUS_INTERNALSERVERERROR,
3047 "osrfMethodException",
3049 "Unable to generate transform function in JSON query"
3051 jsonIteratorFree( select_itr );
3052 jsonIteratorFree( selclass_itr );
3053 buffer_free( sql_buf );
3054 buffer_free( select_buf );
3055 buffer_free( order_buf );
3056 buffer_free( group_buf );
3057 buffer_free( having_buf );
3058 if( defaultselhash ) jsonObjectFree( defaultselhash );
3066 if (flags & DISABLE_I18N)
3069 i18n = osrfHashGet(field_def, "i18n");
3071 if( str_is_true( i18n ) ) {
3072 buffer_fadd( select_buf,
3073 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3074 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3076 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3079 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3086 "%s: Selected item is unexpected JSON type: %s",
3088 json_type( selfield->type )
3091 osrfAppSessionStatus(
3093 OSRF_STATUS_INTERNALSERVERERROR,
3094 "osrfMethodException",
3096 "Ill-formed SELECT item in JSON query"
3098 jsonIteratorFree( select_itr );
3099 jsonIteratorFree( selclass_itr );
3100 buffer_free( sql_buf );
3101 buffer_free( select_buf );
3102 buffer_free( order_buf );
3103 buffer_free( group_buf );
3104 buffer_free( having_buf );
3105 if( defaultselhash ) jsonObjectFree( defaultselhash );
3110 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3111 if( obj_is_true( agg_obj ) )
3112 aggregate_found = 1;
3114 // Append a comma (except for the first one)
3115 // and add the column to a GROUP BY clause
3119 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3121 buffer_fadd(group_buf, " %d", sel_pos);
3125 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3127 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3128 if ( ! obj_is_true( aggregate_obj ) ) {
3132 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3135 buffer_fadd(group_buf, " %d", sel_pos);
3138 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3142 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3145 _column = searchFieldTransform(cname, field, selfield);
3146 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3147 OSRF_BUFFER_ADD(group_buf, _column);
3148 _column = searchFieldTransform(cname, field, selfield);
3155 } // end while -- iterating across SELECT columns
3157 jsonIteratorFree(select_itr);
3158 } // end while -- iterating across classes
3160 jsonIteratorFree(selclass_itr);
3164 char* col_list = buffer_release(select_buf);
3166 if (from_function) table = searchValueTransform(join_hash);
3167 else table = getSourceDefinition(core_meta);
3171 osrfAppSessionStatus(
3173 OSRF_STATUS_INTERNALSERVERERROR,
3174 "osrfMethodException",
3176 "Unable to identify table for core class"
3179 buffer_free( sql_buf );
3180 buffer_free( order_buf );
3181 buffer_free( group_buf );
3182 buffer_free( having_buf );
3183 if( defaultselhash ) jsonObjectFree( defaultselhash );
3188 // Put it all together
3189 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3193 if (!from_function) {
3194 // Now, walk the join tree and add that clause
3196 char* join_clause = searchJOIN( join_hash, core_meta );
3198 buffer_add(sql_buf, join_clause);
3202 osrfAppSessionStatus(
3204 OSRF_STATUS_INTERNALSERVERERROR,
3205 "osrfMethodException",
3207 "Unable to construct JOIN clause(s)"
3209 buffer_free( sql_buf );
3210 buffer_free( order_buf );
3211 buffer_free( group_buf );
3212 buffer_free( having_buf );
3213 if( defaultselhash ) jsonObjectFree( defaultselhash );
3219 // Build a WHERE clause, if there is one
3220 if ( search_hash ) {
3221 buffer_add(sql_buf, " WHERE ");
3223 // and it's on the WHERE clause
3224 char* pred = searchWHERE( search_hash, core_meta, AND_OP_JOIN, ctx );
3227 buffer_add(sql_buf, pred);
3231 osrfAppSessionStatus(
3233 OSRF_STATUS_INTERNALSERVERERROR,
3234 "osrfMethodException",
3236 "Severe query error in WHERE predicate -- see error log for more details"
3240 buffer_free(having_buf);
3241 buffer_free(group_buf);
3242 buffer_free(order_buf);
3243 buffer_free(sql_buf);
3244 if (defaultselhash) jsonObjectFree(defaultselhash);
3249 // Build a HAVING clause, if there is one
3250 if ( having_hash ) {
3251 buffer_add(sql_buf, " HAVING ");
3253 // and it's on the the WHERE clause
3254 char* pred = searchWHERE( having_hash, core_meta, AND_OP_JOIN, ctx );
3257 buffer_add(sql_buf, pred);
3261 osrfAppSessionStatus(
3263 OSRF_STATUS_INTERNALSERVERERROR,
3264 "osrfMethodException",
3266 "Severe query error in HAVING predicate -- see error log for more details"
3270 buffer_free(having_buf);
3271 buffer_free(group_buf);
3272 buffer_free(order_buf);
3273 buffer_free(sql_buf);
3274 if (defaultselhash) jsonObjectFree(defaultselhash);
3279 // Build an ORDER BY clause, if there is one
3281 jsonIterator* class_itr = jsonNewIterator( order_hash );
3282 while ( (snode = jsonIteratorNext( class_itr )) ) {
3284 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
3287 if ( snode->type == JSON_HASH ) {
3289 jsonIterator* order_itr = jsonNewIterator( snode );
3290 while ( (onode = jsonIteratorNext( order_itr )) ) {
3292 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ))
3295 const char* direction = NULL;
3296 if ( onode->type == JSON_HASH ) {
3297 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3298 string = searchFieldTransform(
3300 oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ),
3304 growing_buffer* field_buf = buffer_init(16);
3305 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3306 string = buffer_release(field_buf);
3309 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3310 const char* dir = jsonObjectGetString(tmp_const);
3311 if (!strncasecmp(dir, "d", 1)) {
3312 direction = " DESC";
3319 string = strdup(order_itr->key);
3320 const char* dir = jsonObjectGetString(onode);
3321 if (!strncasecmp(dir, "d", 1)) {
3322 direction = " DESC";
3331 buffer_add(order_buf, ", ");
3334 buffer_add(order_buf, string);
3338 buffer_add(order_buf, direction);
3342 // jsonIteratorFree(order_itr);
3344 } else if ( snode->type == JSON_ARRAY ) {
3346 jsonIterator* order_itr = jsonNewIterator( snode );
3347 while ( (onode = jsonIteratorNext( order_itr )) ) {
3349 const char* _f = jsonObjectGetString( onode );
3351 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, _f))
3357 buffer_add(order_buf, ", ");
3360 buffer_add(order_buf, _f);
3363 // jsonIteratorFree(order_itr);
3366 // IT'S THE OOOOOOOOOOOLD STYLE!
3368 osrfLogError(OSRF_LOG_MARK, "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3370 osrfAppSessionStatus(
3372 OSRF_STATUS_INTERNALSERVERERROR,
3373 "osrfMethodException",
3375 "Severe query error -- see error log for more details"
3380 buffer_free(having_buf);
3381 buffer_free(group_buf);
3382 buffer_free(order_buf);
3383 buffer_free(sql_buf);
3384 if (defaultselhash) jsonObjectFree(defaultselhash);
3385 jsonIteratorFree(class_itr);
3390 // jsonIteratorFree(class_itr);
3394 string = buffer_release(group_buf);
3396 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
3397 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3398 OSRF_BUFFER_ADD( sql_buf, string );
3403 string = buffer_release(having_buf);
3406 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3407 OSRF_BUFFER_ADD( sql_buf, string );
3412 string = buffer_release(order_buf);
3415 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3416 OSRF_BUFFER_ADD( sql_buf, string );
3422 const char* str = jsonObjectGetString(limit);
3423 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
3427 const char* str = jsonObjectGetString(offset);
3428 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
3431 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3434 if (defaultselhash) jsonObjectFree(defaultselhash);
3436 return buffer_release(sql_buf);
3440 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
3442 const char* locale = osrf_message_get_last_locale();
3444 osrfHash* fields = osrfHashGet(meta, "fields");
3445 char* core_class = osrfHashGet(meta, "classname");
3447 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
3449 jsonObject* node = NULL;
3450 jsonObject* snode = NULL;
3451 jsonObject* onode = NULL;
3452 const jsonObject* _tmp = NULL;
3453 jsonObject* selhash = NULL;
3454 jsonObject* defaultselhash = NULL;
3456 growing_buffer* sql_buf = buffer_init(128);
3457 growing_buffer* select_buf = buffer_init(128);
3459 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
3460 defaultselhash = jsonNewObjectType(JSON_HASH);
3461 selhash = defaultselhash;
3464 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
3465 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
3466 jsonObject* flist = jsonObjectGetKey( selhash, core_class );
3471 osrfStringArray* keys = osrfHashKeys( fields );
3472 while ( (field = osrfStringArrayGetString(keys, i++)) ) {
3473 if( ! str_is_true( osrfHashGet( osrfHashGet( fields, field ), "virtual" ) ) )
3474 jsonObjectPush( flist, jsonNewObject( field ) );
3476 osrfStringArrayFree(keys);
3480 jsonIterator* class_itr = jsonNewIterator( selhash );
3481 while ( (snode = jsonIteratorNext( class_itr )) ) {
3483 char* cname = class_itr->key;
3484 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
3485 if (!idlClass) continue;
3487 if (strcmp(core_class,class_itr->key)) {
3488 if (!join_hash) continue;
3490 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
3492 jsonObjectFree(found);
3496 jsonObjectFree(found);
3499 jsonIterator* select_itr = jsonNewIterator( snode );
3500 while ( (node = jsonIteratorNext( select_itr )) ) {
3501 const char* item_str = jsonObjectGetString( node );
3502 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
3503 char* fname = osrfHashGet(field, "name");
3505 if (!field) continue;
3510 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
3515 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
3516 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
3519 i18n = osrfHashGet(field, "i18n");
3521 if( str_is_true( i18n ) ) {
3522 char* pkey = osrfHashGet(idlClass, "primarykey");
3523 char* tname = osrfHashGet(idlClass, "tablename");
3525 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);
3527 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3530 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3534 jsonIteratorFree(select_itr);
3537 jsonIteratorFree(class_itr);
3539 char* col_list = buffer_release(select_buf);
3540 char* table = getSourceDefinition(meta);
3542 table = strdup( "(null)" );
3544 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
3549 char* join_clause = searchJOIN( join_hash, meta );
3550 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
3551 OSRF_BUFFER_ADD(sql_buf, join_clause);
3555 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
3556 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
3558 buffer_add(sql_buf, " WHERE ");
3560 char* pred = searchWHERE( search_hash, meta, AND_OP_JOIN, ctx );
3562 osrfAppSessionStatus(
3564 OSRF_STATUS_INTERNALSERVERERROR,
3565 "osrfMethodException",
3567 "Severe query error -- see error log for more details"
3569 buffer_free(sql_buf);
3570 if(defaultselhash) jsonObjectFree(defaultselhash);
3573 buffer_add(sql_buf, pred);
3578 char* string = NULL;
3579 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
3581 growing_buffer* order_buf = buffer_init(128);
3584 jsonIterator* class_itr = jsonNewIterator( _tmp );
3585 while ( (snode = jsonIteratorNext( class_itr )) ) {
3587 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
3590 if ( snode->type == JSON_HASH ) {
3592 jsonIterator* order_itr = jsonNewIterator( snode );
3593 while ( (onode = jsonIteratorNext( order_itr )) ) {
3595 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ))
3598 char* direction = NULL;
3599 if ( onode->type == JSON_HASH ) {
3600 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3601 string = searchFieldTransform(
3603 oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ),
3607 growing_buffer* field_buf = buffer_init(16);
3608 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3609 string = buffer_release(field_buf);
3612 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
3613 const char* dir = jsonObjectGetString(_tmp);
3614 if (!strncasecmp(dir, "d", 1)) {
3615 direction = " DESC";
3622 string = strdup(order_itr->key);
3623 const char* dir = jsonObjectGetString(onode);
3624 if (!strncasecmp(dir, "d", 1)) {
3625 direction = " DESC";
3634 buffer_add(order_buf, ", ");
3637 buffer_add(order_buf, string);
3641 buffer_add(order_buf, direction);
3646 jsonIteratorFree(order_itr);
3649 const char* str = jsonObjectGetString(snode);
3650 buffer_add(order_buf, str);
3656 jsonIteratorFree(class_itr);
3658 string = buffer_release(order_buf);
3661 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3662 OSRF_BUFFER_ADD( sql_buf, string );
3668 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
3669 const char* str = jsonObjectGetString(_tmp);
3677 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
3679 const char* str = jsonObjectGetString(_tmp);
3688 if (defaultselhash) jsonObjectFree(defaultselhash);
3690 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3691 return buffer_release(sql_buf);
3694 int doJSONSearch ( osrfMethodContext* ctx ) {
3695 if(osrfMethodVerifyContext( ctx )) {
3696 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
3700 osrfLogDebug(OSRF_LOG_MARK, "Recieved query request");
3705 dbhandle = writehandle;
3707 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
3711 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
3712 flags |= SELECT_DISTINCT;
3714 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
3715 flags |= DISABLE_I18N;
3717 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
3720 jsonObjectGetKey( hash, "select" ),
3721 jsonObjectGetKey( hash, "from" ),
3722 jsonObjectGetKey( hash, "where" ),
3723 jsonObjectGetKey( hash, "having" ),
3724 jsonObjectGetKey( hash, "order_by" ),
3725 jsonObjectGetKey( hash, "limit" ),
3726 jsonObjectGetKey( hash, "offset" ),
3735 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
3736 dbi_result result = dbi_conn_query(dbhandle, sql);
3739 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
3741 if (dbi_result_first_row(result)) {
3742 /* JSONify the result */
3743 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
3746 jsonObject* return_val = oilsMakeJSONFromResult( result );
3747 osrfAppRespond( ctx, return_val );
3748 jsonObjectFree( return_val );
3749 } while (dbi_result_next_row(result));
3752 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
3755 osrfAppRespondComplete( ctx, NULL );
3757 /* clean up the query */
3758 dbi_result_free(result);
3762 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
3763 osrfAppSessionStatus(
3765 OSRF_STATUS_INTERNALSERVERERROR,
3766 "osrfMethodException",
3768 "Severe query error -- see error log for more details"
3776 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
3777 const jsonObject* params, int* err ) {
3780 dbhandle = writehandle;
3782 osrfHash* links = osrfHashGet(meta, "links");
3783 osrfHash* fields = osrfHashGet(meta, "fields");
3784 char* core_class = osrfHashGet(meta, "classname");
3785 char* pkey = osrfHashGet(meta, "primarykey");
3787 const jsonObject* _tmp;
3789 jsonObject* search_hash = jsonObjectGetIndex(params, 0);
3790 jsonObject* order_hash = jsonObjectGetIndex(params, 1);
3792 char* sql = buildSELECT( search_hash, order_hash, meta, ctx );
3794 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
3799 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
3801 dbi_result result = dbi_conn_query(dbhandle, sql);
3802 if( NULL == result ) {
3803 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
3804 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
3805 osrfAppSessionStatus(
3807 OSRF_STATUS_INTERNALSERVERERROR,
3808 "osrfMethodException",
3810 "Severe query error -- see error log for more details"
3817 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
3820 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
3821 osrfHash* dedup = osrfNewHash();
3823 if (dbi_result_first_row(result)) {
3824 /* JSONify the result */
3825 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
3827 obj = oilsMakeFieldmapperFromResult( result, meta );
3828 char* pkey_val = oilsFMGetString( obj, pkey );
3829 if ( osrfHashGet( dedup, pkey_val ) ) {
3830 jsonObjectFree(obj);
3833 osrfHashSet( dedup, pkey_val, pkey_val );
3834 jsonObjectPush(res_list, obj);
3836 } while (dbi_result_next_row(result));
3838 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
3842 osrfHashFree(dedup);
3843 /* clean up the query */
3844 dbi_result_free(result);
3847 if (res_list->size && order_hash) {
3848 _tmp = jsonObjectGetKeyConst( order_hash, "flesh" );
3850 int x = (int)jsonObjectGetNumber(_tmp);
3851 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
3853 const jsonObject* temp_blob;
3854 if ((temp_blob = jsonObjectGetKeyConst( order_hash, "flesh_fields" )) && x > 0) {
3856 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
3857 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
3859 osrfStringArray* link_fields = NULL;
3862 if (flesh_fields->size == 1) {
3863 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
3864 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
3869 link_fields = osrfNewStringArray(1);
3870 jsonIterator* _i = jsonNewIterator( flesh_fields );
3871 while ((_f = jsonIteratorNext( _i ))) {
3872 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
3874 jsonIteratorFree(_i);
3879 jsonIterator* itr = jsonNewIterator( res_list );
3880 while ((cur = jsonIteratorNext( itr ))) {
3885 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
3887 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
3889 osrfHash* kid_link = osrfHashGet(links, link_field);
3890 if (!kid_link) continue;
3892 osrfHash* field = osrfHashGet(fields, link_field);
3893 if (!field) continue;
3895 osrfHash* value_field = field;
3897 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
3898 if (!kid_idl) continue;
3900 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
3901 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
3904 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
3905 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
3908 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
3910 if (link_map->size > 0) {
3911 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
3914 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
3919 osrfHashGet(kid_link, "class"),
3926 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
3927 osrfHashGet(kid_link, "field"),
3928 osrfHashGet(kid_link, "class"),
3929 osrfHashGet(kid_link, "key"),
3930 osrfHashGet(kid_link, "reltype")
3933 jsonObject* fake_params = jsonNewObjectType(JSON_ARRAY);
3934 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH)); // search hash
3935 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH)); // order/flesh hash
3937 osrfLogDebug(OSRF_LOG_MARK, "Creating dummy params object...");
3939 const char* search_key = jsonObjectGetString(
3942 atoi( osrfHashGet(value_field, "array_position") )
3947 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
3952 jsonObjectGetIndex(fake_params, 0),
3953 osrfHashGet(kid_link, "key"),
3954 jsonNewObject( search_key )
3958 jsonObjectGetIndex(fake_params, 1),
3960 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
3964 jsonObjectSetKey( jsonObjectGetIndex(fake_params, 1), "flesh_fields", jsonObjectClone(flesh_blob) );
3966 if (jsonObjectGetKeyConst(order_hash, "order_by")) {
3968 jsonObjectGetIndex(fake_params, 1),
3970 jsonObjectClone(jsonObjectGetKeyConst(order_hash, "order_by"))
3974 if (jsonObjectGetKeyConst(order_hash, "select")) {
3976 jsonObjectGetIndex(fake_params, 1),
3978 jsonObjectClone(jsonObjectGetKeyConst(order_hash, "select"))
3982 jsonObject* kids = doFieldmapperSearch(ctx, kid_idl, fake_params, err);
3985 jsonObjectFree( fake_params );
3986 osrfStringArrayFree(link_fields);
3987 jsonIteratorFree(itr);
3988 jsonObjectFree(res_list);
3989 jsonObjectFree(flesh_blob);
3993 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
3995 jsonObject* X = NULL;
3996 if ( link_map->size > 0 && kids->size > 0 ) {
3998 kids = jsonNewObjectType(JSON_ARRAY);
4000 jsonObject* _k_node;
4001 jsonIterator* _k = jsonNewIterator( X );
4002 while ((_k_node = jsonIteratorNext( _k ))) {
4008 (unsigned long)atoi(
4014 osrfHashGet(kid_link, "class")
4018 osrfStringArrayGetString( link_map, 0 )
4027 jsonIteratorFree(_k);
4030 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4031 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4034 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4035 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4039 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4040 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4043 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4044 jsonObjectClone( kids )
4049 jsonObjectFree(kids);
4053 jsonObjectFree( kids );
4054 jsonObjectFree( fake_params );
4056 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4057 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4061 jsonObjectFree( flesh_blob );
4062 osrfStringArrayFree(link_fields);
4063 jsonIteratorFree(itr);
4072 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
4074 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4076 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
4078 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
4081 if (!verifyObjectClass(ctx, target)) {
4086 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4087 osrfAppSessionStatus(
4089 OSRF_STATUS_BADREQUEST,
4090 "osrfMethodException",
4092 "No active transaction -- required for UPDATE"
4098 // The following test is harmless but redundant. If a class is
4099 // readonly, we don't register an update method for it.
4100 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4101 osrfAppSessionStatus(
4103 OSRF_STATUS_BADREQUEST,
4104 "osrfMethodException",
4106 "Cannot UPDATE readonly class"
4112 dbhandle = writehandle;
4114 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4116 // Set the last_xact_id
4117 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4119 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
4120 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4123 char* pkey = osrfHashGet(meta, "primarykey");
4124 osrfHash* fields = osrfHashGet(meta, "fields");
4126 char* id = oilsFMGetString( target, pkey );
4130 "%s updating %s object with %s = %s",
4132 osrfHashGet(meta, "fieldmapper"),
4137 growing_buffer* sql = buffer_init(128);
4138 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4143 osrfStringArray* field_list = osrfHashKeys( fields );
4144 while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
4146 osrfHash* field = osrfHashGet( fields, field_name );
4148 if(!( strcmp( field_name, pkey ) )) continue;
4149 if( str_is_true( osrfHashGet(osrfHashGet(fields,field_name), "virtual") ) )
4152 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4154 int value_is_numeric = 0; // boolean
4156 if (field_object && field_object->classname) {
4157 value = oilsFMGetString(
4159 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4162 value = jsonObjectToSimpleString( field_object );
4163 if( field_object && JSON_NUMBER == field_object->type )
4164 value_is_numeric = 1;
4167 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s", osrfHashGet(meta, "fieldmapper"), field_name, value);
4169 if (!field_object || field_object->type == JSON_NULL) {
4170 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) ) && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
4171 if (first) first = 0;
4172 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4173 buffer_fadd( sql, " %s = NULL", field_name );
4176 } else if ( value_is_numeric || !strcmp( get_primitive( field ), "number") ) {
4177 if (first) first = 0;
4178 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4180 const char* numtype = get_datatype( field );
4181 if ( !strncmp( numtype, "INT", 3 ) ) {
4182 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
4183 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
4184 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
4186 // Must really be intended as a string, so quote it
4187 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4188 buffer_fadd( sql, " %s = %s", field_name, value );
4190 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4191 osrfAppSessionStatus(
4193 OSRF_STATUS_INTERNALSERVERERROR,
4194 "osrfMethodException",
4196 "Error quoting string -- please see the error log for more details"
4206 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
4209 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4210 if (first) first = 0;
4211 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4212 buffer_fadd( sql, " %s = %s", field_name, value );
4215 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4216 osrfAppSessionStatus(
4218 OSRF_STATUS_INTERNALSERVERERROR,
4219 "osrfMethodException",
4221 "Error quoting string -- please see the error log for more details"
4235 jsonObject* obj = jsonNewObject(id);
4237 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4238 dbi_conn_quote_string(dbhandle, &id);
4240 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
4242 char* query = buffer_release(sql);
4243 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
4245 dbi_result result = dbi_conn_query(dbhandle, query);
4249 jsonObjectFree(obj);
4250 obj = jsonNewObject(NULL);
4253 "%s ERROR updating %s object with %s = %s",
4255 osrfHashGet(meta, "fieldmapper"),
4266 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4268 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4270 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4271 osrfAppSessionStatus(
4273 OSRF_STATUS_BADREQUEST,
4274 "osrfMethodException",
4276 "No active transaction -- required for DELETE"
4282 // The following test is harmless but redundant. If a class is
4283 // readonly, we don't register a delete method for it.
4284 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4285 osrfAppSessionStatus(
4287 OSRF_STATUS_BADREQUEST,
4288 "osrfMethodException",
4290 "Cannot DELETE readonly class"
4296 dbhandle = writehandle;
4300 char* pkey = osrfHashGet(meta, "primarykey");
4308 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4309 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4314 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4317 if (!verifyObjectPCRUD( ctx, NULL )) {
4322 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4327 "%s deleting %s object with %s = %s",
4329 osrfHashGet(meta, "fieldmapper"),
4334 obj = jsonNewObject(id);
4336 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4337 dbi_conn_quote_string(writehandle, &id);
4339 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4342 jsonObjectFree(obj);
4343 obj = jsonNewObject(NULL);
4346 "%s ERROR deleting %s object with %s = %s",
4348 osrfHashGet(meta, "fieldmapper"),
4361 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4362 if(!(result && meta)) return jsonNULL;
4364 jsonObject* object = jsonNewObject(NULL);
4365 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4367 osrfHash* fields = osrfHashGet(meta, "fields");
4369 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4373 char dt_string[256];
4377 int columnIndex = 1;
4379 unsigned short type;
4380 const char* columnName;
4382 /* cycle through the column list */
4383 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4385 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4387 fmIndex = -1; // reset the position
4389 /* determine the field type and storage attributes */
4390 type = dbi_result_get_field_type(result, columnName);
4391 attr = dbi_result_get_field_attribs(result, columnName);
4393 /* fetch the fieldmapper index */
4394 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4396 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4399 const char* pos = (char*)osrfHashGet(_f, "array_position");
4400 if ( !pos ) continue;
4402 fmIndex = atoi( pos );
4403 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
4408 if (dbi_result_field_is_null(result, columnName)) {
4409 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
4414 case DBI_TYPE_INTEGER :
4416 if( attr & DBI_INTEGER_SIZE8 )
4417 jsonObjectSetIndex( object, fmIndex,
4418 jsonNewNumberObject(dbi_result_get_longlong(result, columnName)));
4420 jsonObjectSetIndex( object, fmIndex,
4421 jsonNewNumberObject(dbi_result_get_int(result, columnName)));
4425 case DBI_TYPE_DECIMAL :
4426 jsonObjectSetIndex( object, fmIndex,
4427 jsonNewNumberObject(dbi_result_get_double(result, columnName)));
4430 case DBI_TYPE_STRING :
4436 jsonNewObject( dbi_result_get_string(result, columnName) )
4441 case DBI_TYPE_DATETIME :
4443 memset(dt_string, '\0', sizeof(dt_string));
4444 memset(&gmdt, '\0', sizeof(gmdt));
4446 _tmp_dt = dbi_result_get_datetime(result, columnName);
4449 if (!(attr & DBI_DATETIME_DATE)) {
4450 gmtime_r( &_tmp_dt, &gmdt );
4451 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4452 } else if (!(attr & DBI_DATETIME_TIME)) {
4453 localtime_r( &_tmp_dt, &gmdt );
4454 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4456 localtime_r( &_tmp_dt, &gmdt );
4457 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4460 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
4464 case DBI_TYPE_BINARY :
4465 osrfLogError( OSRF_LOG_MARK,
4466 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4474 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
4475 if(!result) return jsonNULL;
4477 jsonObject* object = jsonNewObject(NULL);
4480 char dt_string[256];
4484 int columnIndex = 1;
4486 unsigned short type;
4487 const char* columnName;
4489 /* cycle through the column list */
4490 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4492 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4494 fmIndex = -1; // reset the position
4496 /* determine the field type and storage attributes */
4497 type = dbi_result_get_field_type(result, columnName);
4498 attr = dbi_result_get_field_attribs(result, columnName);
4500 if (dbi_result_field_is_null(result, columnName)) {
4501 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
4506 case DBI_TYPE_INTEGER :
4508 if( attr & DBI_INTEGER_SIZE8 )
4509 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_longlong(result, columnName)) );
4511 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_int(result, columnName)) );
4514 case DBI_TYPE_DECIMAL :
4515 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_double(result, columnName)) );
4518 case DBI_TYPE_STRING :
4519 jsonObjectSetKey( object, columnName, jsonNewObject(dbi_result_get_string(result, columnName)) );
4522 case DBI_TYPE_DATETIME :
4524 memset(dt_string, '\0', sizeof(dt_string));
4525 memset(&gmdt, '\0', sizeof(gmdt));
4527 _tmp_dt = dbi_result_get_datetime(result, columnName);
4530 if (!(attr & DBI_DATETIME_DATE)) {
4531 gmtime_r( &_tmp_dt, &gmdt );
4532 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4533 } else if (!(attr & DBI_DATETIME_TIME)) {
4534 localtime_r( &_tmp_dt, &gmdt );
4535 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4537 localtime_r( &_tmp_dt, &gmdt );
4538 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4541 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
4544 case DBI_TYPE_BINARY :
4545 osrfLogError( OSRF_LOG_MARK,
4546 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4554 // Interpret a string as true or false
4555 static int str_is_true( const char* str ) {
4556 if( NULL == str || strcasecmp( str, "true" ) )
4562 // Interpret a jsonObject as true or false
4563 static int obj_is_true( const jsonObject* obj ) {
4566 else switch( obj->type )
4574 if( strcasecmp( obj->value.s, "true" ) )
4578 case JSON_NUMBER : // Support 1/0 for perl's sake
4579 if( jsonObjectGetNumber( obj ) == 1.0 )
4588 // Translate a numeric code into a text string identifying a type of
4589 // jsonObject. To be used for building error messages.
4590 static const char* json_type( int code ) {
4596 return "JSON_ARRAY";
4598 return "JSON_STRING";
4600 return "JSON_NUMBER";
4606 return "(unrecognized)";
4610 // Extract the "primitive" attribute from an IDL field definition.
4611 // If we haven't initialized the app, then we must be running in
4612 // some kind of testbed. In that case, default to "string".
4613 static const char* get_primitive( osrfHash* field ) {
4614 const char* s = osrfHashGet( field, "primitive" );
4616 if( child_initialized )
4619 "%s ERROR No \"datatype\" attribute for field \"%s\"",
4621 osrfHashGet( field, "name" )
4629 // Extract the "datatype" attribute from an IDL field definition.
4630 // If we haven't initialized the app, then we must be running in
4631 // some kind of testbed. In that case, default to to NUMERIC,
4632 // since we look at the datatype only for numbers.
4633 static const char* get_datatype( osrfHash* field ) {
4634 const char* s = osrfHashGet( field, "datatype" );
4636 if( child_initialized )
4639 "%s ERROR No \"datatype\" attribute for field \"%s\"",
4641 osrfHashGet( field, "name" )
4650 If the input string is potentially a valid SQL identifier, return 1.
4653 Purpose: to prevent certain kinds of SQL injection. To that end we
4654 don't necessarily need to follow all the rules exactly, such as requiring
4655 that the first character not be a digit.
4657 We allow leading and trailing white space. In between, we do not allow
4658 punctuation (except for underscores and dollar signs), control
4659 characters, or embedded white space.
4661 More pedantically we should allow quoted identifiers containing arbitrary
4662 characters, but for the foreseeable future such quoted identifiers are not
4663 likely to be an issue.
4665 static int is_identifier( const char* s) {
4669 // Skip leading white space
4670 while( isspace( (unsigned char) *s ) )
4674 return 0; // Nothing but white space? Not okay.
4676 // Check each character until we reach white space or
4677 // end-of-string. Letters, digits, underscores, and
4678 // dollar signs are okay. Control characters and other
4679 // punctuation characters are not okay. Anything else
4680 // is okay -- it could for example be part of a multibyte
4681 // UTF8 character such as a letter with diacritical marks,
4682 // and those are allowed.
4684 if( isalnum( (unsigned char) *s )
4687 ; // Fine; keep going
4688 else if( ispunct( (unsigned char) *s )
4689 || iscntrl( (unsigned char) *s ) )
4692 } while( *s && ! isspace( (unsigned char) *s ) );
4694 // If we found any white space in the above loop,
4695 // the rest had better be all white space.
4697 while( isspace( (unsigned char) *s ) )
4701 return 0; // White space was embedded within non-white space
4707 Determine whether to accept a character string as a comparison operator.
4708 Return 1 if it's good, or 0 if it's bad.
4710 We don't validate it for real. We just make sure that it doesn't contain
4711 any semicolons or white space (with a special exception for the
4712 "SIMILAR TO" operator). The idea is to block certain kinds of SQL
4713 injection. If it has no semicolons or white space but it's still not a
4714 valid operator, then the database will complain.
4716 Another approach would be to compare the string against a short list of
4717 approved operators. We don't do that because we want to allow custom
4718 operators like ">100*", which would be difficult or impossible to
4719 express otherwise in a JSON query.
4721 static int is_good_operator( const char* op ) {
4722 if( !op ) return 0; // Sanity check
4726 if( isspace( (unsigned char) *s ) ) {
4727 // Special exception for SIMILAR TO. Someday we might make
4728 // exceptions for IS DISTINCT FROM and IS NOT DISTINCT FROM.
4729 if( !strcasecmp( op, "similar to" ) )
4734 else if( ';' == *s )