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" ),
1685 buffer_add(sql_buf, subpred);
1688 buffer_free( sql_buf );
1692 } else if (node->type == JSON_ARRAY) {
1693 // literal value list
1694 int in_item_index = 0;
1695 int in_item_first = 1;
1696 const jsonObject* in_item;
1697 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1702 buffer_add(sql_buf, ", ");
1705 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1706 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1707 MODULENAME, json_type( in_item->type ) );
1708 buffer_free(sql_buf);
1712 // Append the literal value -- quoted if not a number
1713 if ( JSON_NUMBER == in_item->type ) {
1714 char* val = jsonNumberToDBString( field, in_item );
1715 OSRF_BUFFER_ADD( sql_buf, val );
1718 } else if ( !strcmp( get_primitive( field ), "number") ) {
1719 char* val = jsonNumberToDBString( field, in_item );
1720 OSRF_BUFFER_ADD( sql_buf, val );
1724 char* key_string = jsonObjectToSimpleString(in_item);
1725 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1726 OSRF_BUFFER_ADD( sql_buf, key_string );
1729 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1731 buffer_free(sql_buf);
1737 if( in_item_first ) {
1738 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1739 buffer_free( sql_buf );
1743 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1744 MODULENAME, json_type( node->type ) );
1745 buffer_free(sql_buf);
1749 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1751 return buffer_release(sql_buf);
1754 // Receive a JSON_ARRAY representing a function call. The first
1755 // entry in the array is the function name. The rest are parameters.
1756 static char* searchValueTransform( const jsonObject* array ) {
1758 if( array->size < 1 ) {
1759 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
1763 // Get the function name
1764 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
1765 if( func_item->type != JSON_STRING ) {
1766 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
1767 MODULENAME, json_type( func_item->type ) );
1771 growing_buffer* sql_buf = buffer_init(32);
1773 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1774 OSRF_BUFFER_ADD( sql_buf, "( " );
1776 // Get the parameters
1777 int func_item_index = 1; // We already grabbed the zeroth entry
1778 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1780 // Add a separator comma, if we need one
1781 if( func_item_index > 2 )
1782 buffer_add( sql_buf, ", " );
1784 // Add the current parameter
1785 if (func_item->type == JSON_NULL) {
1786 buffer_add( sql_buf, "NULL" );
1788 char* val = jsonObjectToSimpleString(func_item);
1789 if ( dbi_conn_quote_string(dbhandle, &val) ) {
1790 OSRF_BUFFER_ADD( sql_buf, val );
1793 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1794 buffer_free(sql_buf);
1801 buffer_add( sql_buf, " )" );
1803 return buffer_release(sql_buf);
1806 static char* searchFunctionPredicate (const char* class, osrfHash* field,
1807 const jsonObject* node, const char* op) {
1809 if( ! is_good_operator( op ) ) {
1810 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
1814 char* val = searchValueTransform(node);
1818 growing_buffer* sql_buf = buffer_init(32);
1823 osrfHashGet(field, "name"),
1830 return buffer_release(sql_buf);
1833 // class is a class name
1834 // field is a field definition as stored in the IDL
1835 // node comes from the method parameter, and may represent an entry in the SELECT list
1836 static char* searchFieldTransform (const char* class, osrfHash* field, const jsonObject* node) {
1837 growing_buffer* sql_buf = buffer_init(32);
1839 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
1840 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
1842 if(transform_subcolumn)
1843 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
1845 if (field_transform) {
1846 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class, osrfHashGet(field, "name"));
1847 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
1850 if( array->type != JSON_ARRAY ) {
1851 osrfLogError( OSRF_LOG_MARK,
1852 "%s: Expected JSON_ARRAY for function params; found %s",
1853 MODULENAME, json_type( array->type ) );
1854 buffer_free( sql_buf );
1857 int func_item_index = 0;
1858 jsonObject* func_item;
1859 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1861 char* val = jsonObjectToSimpleString(func_item);
1864 buffer_add( sql_buf, ",NULL" );
1865 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
1866 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
1867 OSRF_BUFFER_ADD( sql_buf, val );
1869 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1871 buffer_free(sql_buf);
1878 buffer_add( sql_buf, " )" );
1881 buffer_fadd( sql_buf, "\"%s\".%s", class, osrfHashGet(field, "name"));
1884 if (transform_subcolumn)
1885 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
1887 return buffer_release(sql_buf);
1890 static char* searchFieldTransformPredicate (const char* class, osrfHash* field,
1891 const jsonObject* node, const char* op ) {
1893 if( ! is_good_operator( op ) ) {
1894 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
1898 char* field_transform = searchFieldTransform( class, field, node );
1900 int extra_parens = 0; // boolean
1902 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
1903 if ( ! value_obj ) {
1904 value = searchWHERE( node, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1906 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
1907 free(field_transform);
1911 } else if ( value_obj->type == JSON_ARRAY ) {
1912 value = searchValueTransform( value_obj );
1914 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
1915 free( field_transform );
1918 } else if ( value_obj->type == JSON_HASH ) {
1919 value = searchWHERE( value_obj, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1921 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
1922 free(field_transform);
1926 } else if ( value_obj->type == JSON_NUMBER ) {
1927 value = jsonNumberToDBString( field, value_obj );
1928 } else if ( value_obj->type == JSON_NULL ) {
1929 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
1930 free(field_transform);
1932 } else if ( value_obj->type == JSON_BOOL ) {
1933 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
1934 free(field_transform);
1937 if ( !strcmp( get_primitive( field ), "number") ) {
1938 value = jsonNumberToDBString( field, value_obj );
1940 value = jsonObjectToSimpleString( value_obj );
1941 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
1942 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
1944 free(field_transform);
1950 const char* left_parens = "";
1951 const char* right_parens = "";
1953 if( extra_parens ) {
1958 growing_buffer* sql_buf = buffer_init(32);
1962 "%s%s %s %s %s %s%s",
1973 free(field_transform);
1975 return buffer_release(sql_buf);
1978 static char* searchSimplePredicate (const char* op, const char* class,
1979 osrfHash* field, const jsonObject* node) {
1981 if( ! is_good_operator( op ) ) {
1982 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
1988 // Get the value to which we are comparing the specified column
1989 if (node->type != JSON_NULL) {
1990 if ( node->type == JSON_NUMBER ) {
1991 val = jsonNumberToDBString( field, node );
1992 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
1993 val = jsonNumberToDBString( field, node );
1995 val = jsonObjectToSimpleString(node);
2000 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2001 // Value is not numeric; enclose it in quotes
2002 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2003 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2009 // Compare to a null value
2010 val = strdup( "NULL" );
2011 if (strcmp( op, "=" ))
2017 growing_buffer* sql_buf = buffer_init(32);
2018 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class, osrfHashGet(field, "name"), op, val );
2019 char* pred = buffer_release( sql_buf );
2026 static char* searchBETWEENPredicate (const char* class, osrfHash* field, const jsonObject* node) {
2028 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2029 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2031 if( NULL == y_node ) {
2032 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2035 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2036 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2043 if ( !strcmp( get_primitive( field ), "number") ) {
2044 x_string = jsonNumberToDBString(field, x_node);
2045 y_string = jsonNumberToDBString(field, y_node);
2048 x_string = jsonObjectToSimpleString(x_node);
2049 y_string = jsonObjectToSimpleString(y_node);
2050 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2051 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2052 MODULENAME, x_string, y_string);
2059 growing_buffer* sql_buf = buffer_init(32);
2060 buffer_fadd( sql_buf, "%s BETWEEN %s AND %s", osrfHashGet(field, "name"), x_string, y_string );
2064 return buffer_release(sql_buf);
2067 static char* searchPredicate ( const char* class, osrfHash* field,
2068 jsonObject* node, osrfMethodContext* ctx ) {
2071 if (node->type == JSON_ARRAY) { // equality IN search
2072 pred = searchINPredicate( class, field, node, NULL, ctx );
2073 } else if (node->type == JSON_HASH) { // other search
2074 jsonIterator* pred_itr = jsonNewIterator( node );
2075 if( !jsonIteratorHasNext( pred_itr ) ) {
2076 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2077 MODULENAME, osrfHashGet(field, "name") );
2079 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2081 // Verify that there are no additional predicates
2082 if( jsonIteratorHasNext( pred_itr ) ) {
2083 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2084 MODULENAME, osrfHashGet(field, "name") );
2085 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2086 pred = searchBETWEENPredicate( class, field, pred_node );
2087 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2088 pred = searchINPredicate( class, field, pred_node, pred_itr->key, ctx );
2089 else if ( pred_node->type == JSON_ARRAY )
2090 pred = searchFunctionPredicate( class, field, pred_node, pred_itr->key );
2091 else if ( pred_node->type == JSON_HASH )
2092 pred = searchFieldTransformPredicate( class, field, pred_node, pred_itr->key );
2094 pred = searchSimplePredicate( pred_itr->key, class, field, pred_node );
2096 jsonIteratorFree(pred_itr);
2098 } else if (node->type == JSON_NULL) { // IS NULL search
2099 growing_buffer* _p = buffer_init(64);
2102 "\"%s\".%s IS NULL",
2104 osrfHashGet(field, "name")
2106 pred = buffer_release(_p);
2107 } else { // equality search
2108 pred = searchSimplePredicate( "=", class, field, node );
2127 field : call_number,
2143 static char* searchJOIN ( const jsonObject* join_hash, osrfHash* leftmeta ) {
2145 const jsonObject* working_hash;
2146 jsonObject* freeable_hash = NULL;
2148 if (join_hash->type == JSON_STRING) {
2149 // create a wrapper around a copy of the original
2150 const char* _tmp = jsonObjectGetString( join_hash );
2151 freeable_hash = jsonNewObjectType(JSON_HASH);
2152 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2153 working_hash = freeable_hash;
2156 if( join_hash->type != JSON_HASH ) {
2159 "%s: JOIN failed; expected JSON object type not found",
2164 working_hash = join_hash;
2167 growing_buffer* join_buf = buffer_init(128);
2168 const char* leftclass = osrfHashGet(leftmeta, "classname");
2170 jsonObject* snode = NULL;
2171 jsonIterator* search_itr = jsonNewIterator( working_hash );
2173 while ( (snode = jsonIteratorNext( search_itr )) ) {
2174 const char* class = search_itr->key;
2175 osrfHash* idlClass = osrfHashGet( oilsIDL(), class );
2179 "%s: JOIN failed. No class \"%s\" defined in IDL",
2183 jsonIteratorFree( search_itr );
2184 buffer_free( join_buf );
2186 jsonObjectFree( freeable_hash );
2190 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2191 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2193 if (field && !fkey) {
2194 fkey = (const char*)oilsIDLFindPath("/%s/links/%s/key", class, field);
2198 "%s: JOIN failed. No link defined from %s.%s to %s",
2204 buffer_free(join_buf);
2206 jsonObjectFree(freeable_hash);
2207 jsonIteratorFree(search_itr);
2211 } else if (!field && fkey) {
2212 field = (const char*)oilsIDLFindPath("/%s/links/%s/key", leftclass, fkey );
2216 "%s: JOIN failed. No link defined from %s.%s to %s",
2222 buffer_free(join_buf);
2224 jsonObjectFree(freeable_hash);
2225 jsonIteratorFree(search_itr);
2230 } else if (!field && !fkey) {
2231 osrfHash* _links = oilsIDL_links( leftclass );
2233 // For each link defined for the left class:
2234 // see if the link references the joined class
2235 osrfHashIterator* itr = osrfNewHashIterator( _links );
2236 osrfHash* curr_link = NULL;
2237 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2238 const char* other_class = osrfHashGet( curr_link, "class" );
2239 if( other_class && !strcmp( other_class, class ) ) {
2241 // Found a link between the classes
2242 fkey = osrfHashIteratorKey( itr );
2243 field = osrfHashGet( curr_link, "key" );
2247 osrfHashIteratorFree( itr );
2249 if (!field || !fkey) {
2250 // Do another such search, with the classes reversed
2251 _links = oilsIDL_links( class );
2253 // For each link defined for the joined class:
2254 // see if the link references the left class
2255 osrfHashIterator* itr = osrfNewHashIterator( _links );
2256 osrfHash* curr_link = NULL;
2257 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2258 const char* other_class = osrfHashGet( curr_link, "class" );
2259 if( other_class && !strcmp( other_class, leftclass ) ) {
2261 // Found a link between the classes
2262 fkey = osrfHashIteratorKey( itr );
2263 field = osrfHashGet( curr_link, "key" );
2267 osrfHashIteratorFree( itr );
2270 if (!field || !fkey) {
2273 "%s: JOIN failed. No link defined between %s and %s",
2278 buffer_free(join_buf);
2280 jsonObjectFree(freeable_hash);
2281 jsonIteratorFree(search_itr);
2287 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2289 if ( !strcasecmp(type,"left") ) {
2290 buffer_add(join_buf, " LEFT JOIN");
2291 } else if ( !strcasecmp(type,"right") ) {
2292 buffer_add(join_buf, " RIGHT JOIN");
2293 } else if ( !strcasecmp(type,"full") ) {
2294 buffer_add(join_buf, " FULL JOIN");
2296 buffer_add(join_buf, " INNER JOIN");
2299 buffer_add(join_buf, " INNER JOIN");
2302 char* table = getSourceDefinition(idlClass);
2304 jsonIteratorFree( search_itr );
2305 buffer_free( join_buf );
2307 jsonObjectFree( freeable_hash );
2311 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2312 table, class, class, field, leftclass, fkey);
2315 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2317 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2318 if ( filter_op && !strcasecmp("or",filter_op) ) {
2319 buffer_add( join_buf, " OR " );
2321 buffer_add( join_buf, " AND " );
2324 char* jpred = searchWHERE( filter, idlClass, AND_OP_JOIN, NULL );
2326 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2327 OSRF_BUFFER_ADD( join_buf, jpred );
2332 "%s: JOIN failed. Invalid conditional expression.",
2335 jsonIteratorFree( search_itr );
2336 buffer_free( join_buf );
2338 jsonObjectFree( freeable_hash );
2343 buffer_add(join_buf, " ) ");
2345 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2347 char* jpred = searchJOIN( join_filter, idlClass );
2348 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2349 OSRF_BUFFER_ADD( join_buf, jpred );
2355 jsonObjectFree(freeable_hash);
2356 jsonIteratorFree(search_itr);
2358 return buffer_release(join_buf);
2363 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2364 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2365 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2367 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2369 search_hash is the JSON expression of the conditions.
2370 meta is the class definition from the IDL, for the relevant table.
2371 opjoin_type indicates whether multiple conditions, if present, should be
2372 connected by AND or OR.
2373 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2374 to pass it to other functions -- and all they do with it is to use the session
2375 and request members to send error messages back to the client.
2379 static char* searchWHERE ( const jsonObject* search_hash, osrfHash* meta, int opjoin_type, osrfMethodContext* ctx ) {
2383 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2391 growing_buffer* sql_buf = buffer_init(128);
2393 jsonObject* node = NULL;
2396 if ( search_hash->type == JSON_ARRAY ) {
2397 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2398 jsonIterator* search_itr = jsonNewIterator( search_hash );
2399 if( !jsonIteratorHasNext( search_itr ) ) {
2402 "%s: Invalid predicate structure: empty JSON array",
2405 jsonIteratorFree( search_itr );
2406 buffer_free( sql_buf );
2410 while ( (node = jsonIteratorNext( search_itr )) ) {
2414 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2415 else buffer_add(sql_buf, " AND ");
2418 char* subpred = searchWHERE( node, meta, opjoin_type, ctx );
2420 buffer_fadd(sql_buf, "( %s )", subpred);
2423 jsonIteratorFree( search_itr );
2424 buffer_free( sql_buf );
2428 jsonIteratorFree(search_itr);
2430 } else if ( search_hash->type == JSON_HASH ) {
2431 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2432 jsonIterator* search_itr = jsonNewIterator( search_hash );
2433 if( !jsonIteratorHasNext( search_itr ) ) {
2436 "%s: Invalid predicate structure: empty JSON object",
2439 jsonIteratorFree( search_itr );
2440 buffer_free( sql_buf );
2444 while ( (node = jsonIteratorNext( search_itr )) ) {
2449 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2450 else buffer_add(sql_buf, " AND ");
2453 if ( '+' == search_itr->key[ 0 ] ) {
2454 if ( node->type == JSON_STRING ) {
2455 // Intended purpose; to allow reference to a Boolean column
2457 // Verify that the class alias is not empty
2458 if( '\0' == search_itr->key[ 1 ] ) {
2461 "%s: Table alias is empty",
2464 jsonIteratorFree( search_itr );
2465 buffer_free( sql_buf );
2469 // Verify that the string looks like an identifier.
2470 const char* subpred = jsonObjectGetString( node );
2471 if( ! is_identifier( subpred ) ) {
2474 "%s: Invalid boolean identifier in WHERE clause: \"%s\"",
2478 jsonIteratorFree( search_itr );
2479 buffer_free( sql_buf );
2483 buffer_fadd(sql_buf, " \"%s\".%s ", search_itr->key + 1, subpred);
2485 char* subpred = searchWHERE( node, osrfHashGet( oilsIDL(), search_itr->key + 1 ), AND_OP_JOIN, ctx );
2487 buffer_fadd(sql_buf, "( %s )", subpred);
2490 jsonIteratorFree( search_itr );
2491 buffer_free( sql_buf );
2495 } else if ( !strcasecmp("-or",search_itr->key) ) {
2496 char* subpred = searchWHERE( node, meta, OR_OP_JOIN, ctx );
2498 buffer_fadd(sql_buf, "( %s )", subpred);
2501 buffer_free( sql_buf );
2504 } else if ( !strcasecmp("-and",search_itr->key) ) {
2505 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2507 buffer_fadd(sql_buf, "( %s )", subpred);
2510 buffer_free( sql_buf );
2513 } else if ( !strcasecmp("-not",search_itr->key) ) {
2514 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2516 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2519 buffer_free( sql_buf );
2522 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2523 char* subpred = SELECT(
2525 jsonObjectGetKey( node, "select" ),
2526 jsonObjectGetKey( node, "from" ),
2527 jsonObjectGetKey( node, "where" ),
2528 jsonObjectGetKey( node, "having" ),
2529 jsonObjectGetKey( node, "order_by" ),
2530 jsonObjectGetKey( node, "limit" ),
2531 jsonObjectGetKey( node, "offset" ),
2536 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2539 buffer_free( sql_buf );
2542 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2543 char* subpred = SELECT(
2545 jsonObjectGetKey( node, "select" ),
2546 jsonObjectGetKey( node, "from" ),
2547 jsonObjectGetKey( node, "where" ),
2548 jsonObjectGetKey( node, "having" ),
2549 jsonObjectGetKey( node, "order_by" ),
2550 jsonObjectGetKey( node, "limit" ),
2551 jsonObjectGetKey( node, "offset" ),
2556 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2559 buffer_free( sql_buf );
2565 char* class = osrfHashGet(meta, "classname");
2566 osrfHash* fields = osrfHashGet(meta, "fields");
2567 osrfHash* field = osrfHashGet( fields, search_itr->key );
2571 char* table = getSourceDefinition(meta);
2573 table = strdup( "(?)" );
2576 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2582 buffer_free(sql_buf);
2584 jsonIteratorFree(search_itr);
2588 char* subpred = searchPredicate( class, field, node, ctx );
2590 buffer_add( sql_buf, subpred );
2593 buffer_free(sql_buf);
2594 jsonIteratorFree(search_itr);
2599 jsonIteratorFree(search_itr);
2602 // ERROR ... only hash and array allowed at this level
2603 char* predicate_string = jsonObjectToJSON( search_hash );
2606 "%s: Invalid predicate structure: %s",
2610 buffer_free(sql_buf);
2611 free(predicate_string);
2615 return buffer_release(sql_buf);
2619 /* method context */ osrfMethodContext* ctx,
2621 /* SELECT */ jsonObject* selhash,
2622 /* FROM */ jsonObject* join_hash,
2623 /* WHERE */ jsonObject* search_hash,
2624 /* HAVING */ jsonObject* having_hash,
2625 /* ORDER BY */ jsonObject* order_hash,
2626 /* LIMIT */ jsonObject* limit,
2627 /* OFFSET */ jsonObject* offset,
2628 /* flags */ int flags
2630 const char* locale = osrf_message_get_last_locale();
2632 // in case we don't get a select list
2633 jsonObject* defaultselhash = NULL;
2635 // general tmp objects
2636 const jsonObject* tmp_const;
2637 jsonObject* selclass = NULL;
2638 jsonObject* selfield = NULL;
2639 jsonObject* snode = NULL;
2640 jsonObject* onode = NULL;
2642 char* string = NULL;
2643 int from_function = 0;
2648 // the core search class
2649 char* core_class = NULL;
2651 // metadata about the core search class
2652 osrfHash* core_meta = NULL;
2654 // punt if there's no core class
2655 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2658 "%s: FROM clause is missing or empty",
2662 osrfAppSessionStatus(
2664 OSRF_STATUS_INTERNALSERVERERROR,
2665 "osrfMethodException",
2667 "FROM clause is missing or empty in JSON query"
2672 // get the core class -- the only key of the top level FROM clause, or a string
2673 if (join_hash->type == JSON_HASH) {
2674 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2675 snode = jsonIteratorNext( tmp_itr );
2677 core_class = strdup( tmp_itr->key );
2680 jsonObject* extra = jsonIteratorNext( tmp_itr );
2682 jsonIteratorFree( tmp_itr );
2685 // There shouldn't be more than one entry in join_hash
2689 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2693 osrfAppSessionStatus(
2695 OSRF_STATUS_INTERNALSERVERERROR,
2696 "osrfMethodException",
2698 "Malformed FROM clause in JSON query"
2701 return NULL; // Malformed join_hash; extra entry
2703 } else if (join_hash->type == JSON_ARRAY) {
2705 core_class = jsonObjectToSimpleString( jsonObjectGetIndex(join_hash, 0) );
2708 } else if (join_hash->type == JSON_STRING) {
2709 core_class = jsonObjectToSimpleString( join_hash );
2715 "%s: FROM clause is unexpected JSON type: %s",
2717 json_type( join_hash->type )
2720 osrfAppSessionStatus(
2722 OSRF_STATUS_INTERNALSERVERERROR,
2723 "osrfMethodException",
2725 "Ill-formed FROM clause in JSON query"
2731 if (!from_function) {
2732 // Get the IDL class definition for the core class
2733 core_meta = osrfHashGet( oilsIDL(), core_class );
2734 if( !core_meta ) { // Didn't find it?
2737 "%s: SELECT clause references undefined class: \"%s\"",
2742 osrfAppSessionStatus(
2744 OSRF_STATUS_INTERNALSERVERERROR,
2745 "osrfMethodException",
2747 "SELECT clause references undefined class in JSON query"
2753 // Make sure the class isn't virtual
2754 if( str_is_true( osrfHashGet( core_meta, "virtual" ) ) ) {
2757 "%s: Core class is virtual: \"%s\"",
2762 osrfAppSessionStatus(
2764 OSRF_STATUS_INTERNALSERVERERROR,
2765 "osrfMethodException",
2767 "FROM clause references virtual class in JSON query"
2774 // if the select list is empty, or the core class field list is '*',
2775 // build the default select list ...
2777 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
2778 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2779 } else if( selhash->type != JSON_HASH ) {
2782 "%s: Expected JSON_HASH for SELECT clause; found %s",
2784 json_type( selhash->type )
2788 osrfAppSessionStatus(
2790 OSRF_STATUS_INTERNALSERVERERROR,
2791 "osrfMethodException",
2793 "Malformed SELECT clause in JSON query"
2797 } else if ( (tmp_const = jsonObjectGetKeyConst( selhash, core_class )) && tmp_const->type == JSON_STRING ) {
2798 const char* _x = jsonObjectGetString( tmp_const );
2799 if (!strncmp( "*", _x, 1 )) {
2800 jsonObjectRemoveKey( selhash, core_class );
2801 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2806 growing_buffer* sql_buf = buffer_init(128);
2808 // temp buffer for the SELECT list
2809 growing_buffer* select_buf = buffer_init(128);
2810 growing_buffer* order_buf = buffer_init(128);
2811 growing_buffer* group_buf = buffer_init(128);
2812 growing_buffer* having_buf = buffer_init(128);
2814 int aggregate_found = 0; // boolean
2816 // Build a select list
2817 if(from_function) // From a function we select everything
2818 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
2821 // If we need to build a default list, prepare to do so
2822 jsonObject* _tmp = jsonObjectGetKey( selhash, core_class );
2823 if ( _tmp && !_tmp->size ) {
2825 osrfHash* core_fields = osrfHashGet( core_meta, "fields" );
2827 osrfHashIterator* field_itr = osrfNewHashIterator( core_fields );
2828 osrfHash* field_def;
2829 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2830 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2831 // This field is not virtual, so add it to the list
2832 jsonObjectPush( _tmp, jsonNewObject( osrfHashIteratorKey( field_itr ) ) );
2835 osrfHashIteratorFree( field_itr );
2838 // Now build the actual select list
2842 jsonIterator* selclass_itr = jsonNewIterator( selhash );
2843 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
2845 // Make sure the class is defined in the IDL
2846 const char* cname = selclass_itr->key;
2847 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
2851 "%s: Selected class \"%s\" not defined in IDL",
2857 osrfAppSessionStatus(
2859 OSRF_STATUS_INTERNALSERVERERROR,
2860 "osrfMethodException",
2862 "Selected class is not defined"
2864 jsonIteratorFree( selclass_itr );
2865 buffer_free( sql_buf );
2866 buffer_free( select_buf );
2867 buffer_free( order_buf );
2868 buffer_free( group_buf );
2869 buffer_free( having_buf );
2870 if( defaultselhash ) jsonObjectFree( defaultselhash );
2875 // Make sure the target relation is in the join tree.
2877 // At this point join_hash is a step down from the join_hash we
2878 // received as a parameter. If the original was a JSON_STRING,
2879 // then json_hash is now NULL. If the original was a JSON_HASH,
2880 // then json_hash is now the first (and only) entry in it,
2881 // denoting the core class. We've already excluded the
2882 // possibility that the original was a JSON_ARRAY, because in
2883 // that case from_function would be non-NULL, and we wouldn't
2886 int class_in_from_clause; // boolean
2888 if ( ! strcmp( core_class, cname ))
2889 // This is the core class -- no problem
2890 class_in_from_clause = 1;
2893 // There's only one class in the FROM clause, and this isn't it
2894 class_in_from_clause = 0;
2895 else if (join_hash->type == JSON_STRING) {
2896 // There's only one class in the FROM clause
2897 const char* str = jsonObjectGetString(join_hash);
2898 if ( strcmp( str, cname ) )
2899 class_in_from_clause = 0; // This isn't it
2901 class_in_from_clause = 1; // This is it
2903 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", cname);
2904 if ( 0 == found->size )
2905 class_in_from_clause = 0; // Nowhere in the join tree
2907 class_in_from_clause = 1; // Found it
2908 jsonObjectFree( found );
2912 // If the class isn't in the FROM clause, bail out
2913 if( ! class_in_from_clause ) {
2916 "%s: SELECT clause references class not in FROM clause: \"%s\"",
2921 osrfAppSessionStatus(
2923 OSRF_STATUS_INTERNALSERVERERROR,
2924 "osrfMethodException",
2926 "Selected class not in FROM clause in JSON query"
2928 jsonIteratorFree( selclass_itr );
2929 buffer_free( sql_buf );
2930 buffer_free( select_buf );
2931 buffer_free( order_buf );
2932 buffer_free( group_buf );
2933 buffer_free( having_buf );
2934 if( defaultselhash ) jsonObjectFree( defaultselhash );
2939 // Look up some attributes of the current class, so that we
2940 // don't have to look them up again for each field
2941 osrfHash* class_field_set = osrfHashGet( idlClass, "fields" );
2942 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
2943 const char* class_tname = osrfHashGet( idlClass, "tablename" );
2945 // stitch together the column list ...
2946 jsonIterator* select_itr = jsonNewIterator( selclass );
2947 while ( (selfield = jsonIteratorNext( select_itr )) ) { // for each SELECT column
2949 // If we need a separator comma, add one
2953 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
2956 // ... if it's a string, just toss it on the pile
2957 if (selfield->type == JSON_STRING) {
2959 // Look up the field in the IDL
2960 const char* col_name = jsonObjectGetString( selfield );
2961 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
2963 // No such field in current class
2966 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
2972 osrfAppSessionStatus(
2974 OSRF_STATUS_INTERNALSERVERERROR,
2975 "osrfMethodException",
2977 "Selected column not defined in JSON query"
2979 jsonIteratorFree( select_itr );
2980 jsonIteratorFree( selclass_itr );
2981 buffer_free( sql_buf );
2982 buffer_free( select_buf );
2983 buffer_free( order_buf );
2984 buffer_free( group_buf );
2985 buffer_free( having_buf );
2986 if( defaultselhash ) jsonObjectFree( defaultselhash );
2989 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2990 // Virtual field not allowed
2993 "%s: Selected column \"%s\" for class \"%s\" is virtual",
2999 osrfAppSessionStatus(
3001 OSRF_STATUS_INTERNALSERVERERROR,
3002 "osrfMethodException",
3004 "Selected column may not be virtual in JSON query"
3006 jsonIteratorFree( select_itr );
3007 jsonIteratorFree( selclass_itr );
3008 buffer_free( sql_buf );
3009 buffer_free( select_buf );
3010 buffer_free( order_buf );
3011 buffer_free( group_buf );
3012 buffer_free( having_buf );
3013 if( defaultselhash ) jsonObjectFree( defaultselhash );
3020 if (flags & DISABLE_I18N)
3023 i18n = osrfHashGet(field_def, "i18n");
3025 if( str_is_true( i18n ) ) {
3026 buffer_fadd( select_buf,
3027 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3028 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3030 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3033 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3036 // ... but it could be an object, in which case we check for a Field Transform
3037 } else if (selfield->type == JSON_HASH) {
3039 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3041 // Get the field definition from the IDL
3042 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3044 // No such field in current class
3047 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3053 osrfAppSessionStatus(
3055 OSRF_STATUS_INTERNALSERVERERROR,
3056 "osrfMethodException",
3058 "Selected column is not defined in JSON query"
3060 jsonIteratorFree( select_itr );
3061 jsonIteratorFree( selclass_itr );
3062 buffer_free( sql_buf );
3063 buffer_free( select_buf );
3064 buffer_free( order_buf );
3065 buffer_free( group_buf );
3066 buffer_free( having_buf );
3067 if( defaultselhash ) jsonObjectFree( defaultselhash );
3070 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3071 // No such field in current class
3074 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3080 osrfAppSessionStatus(
3082 OSRF_STATUS_INTERNALSERVERERROR,
3083 "osrfMethodException",
3085 "Selected column is virtual in JSON query"
3087 jsonIteratorFree( select_itr );
3088 jsonIteratorFree( selclass_itr );
3089 buffer_free( sql_buf );
3090 buffer_free( select_buf );
3091 buffer_free( order_buf );
3092 buffer_free( group_buf );
3093 buffer_free( having_buf );
3094 if( defaultselhash ) jsonObjectFree( defaultselhash );
3099 // Decide what to use as a column alias
3101 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3102 _alias = jsonObjectGetString( tmp_const );
3103 } else { // Use field name as the alias
3107 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3108 char* transform_str = searchFieldTransform(cname, field_def, selfield);
3109 if( transform_str ) {
3110 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3111 free(transform_str);
3114 osrfAppSessionStatus(
3116 OSRF_STATUS_INTERNALSERVERERROR,
3117 "osrfMethodException",
3119 "Unable to generate transform function in JSON query"
3121 jsonIteratorFree( select_itr );
3122 jsonIteratorFree( selclass_itr );
3123 buffer_free( sql_buf );
3124 buffer_free( select_buf );
3125 buffer_free( order_buf );
3126 buffer_free( group_buf );
3127 buffer_free( having_buf );
3128 if( defaultselhash ) jsonObjectFree( defaultselhash );
3136 if (flags & DISABLE_I18N)
3139 i18n = osrfHashGet(field_def, "i18n");
3141 if( str_is_true( i18n ) ) {
3142 buffer_fadd( select_buf,
3143 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3144 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3146 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3149 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3156 "%s: Selected item is unexpected JSON type: %s",
3158 json_type( selfield->type )
3161 osrfAppSessionStatus(
3163 OSRF_STATUS_INTERNALSERVERERROR,
3164 "osrfMethodException",
3166 "Ill-formed SELECT item in JSON query"
3168 jsonIteratorFree( select_itr );
3169 jsonIteratorFree( selclass_itr );
3170 buffer_free( sql_buf );
3171 buffer_free( select_buf );
3172 buffer_free( order_buf );
3173 buffer_free( group_buf );
3174 buffer_free( having_buf );
3175 if( defaultselhash ) jsonObjectFree( defaultselhash );
3180 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3181 if( obj_is_true( agg_obj ) )
3182 aggregate_found = 1;
3184 // Append a comma (except for the first one)
3185 // and add the column to a GROUP BY clause
3189 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3191 buffer_fadd(group_buf, " %d", sel_pos);
3195 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3197 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3198 if ( ! obj_is_true( aggregate_obj ) ) {
3202 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3205 buffer_fadd(group_buf, " %d", sel_pos);
3208 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3212 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3215 _column = searchFieldTransform(cname, field, selfield);
3216 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3217 OSRF_BUFFER_ADD(group_buf, _column);
3218 _column = searchFieldTransform(cname, field, selfield);
3225 } // end while -- iterating across SELECT columns
3227 jsonIteratorFree(select_itr);
3228 } // end while -- iterating across classes
3230 jsonIteratorFree(selclass_itr);
3234 char* col_list = buffer_release(select_buf);
3236 if (from_function) table = searchValueTransform(join_hash);
3237 else table = getSourceDefinition(core_meta);
3241 osrfAppSessionStatus(
3243 OSRF_STATUS_INTERNALSERVERERROR,
3244 "osrfMethodException",
3246 "Unable to identify table for core class"
3249 buffer_free( sql_buf );
3250 buffer_free( order_buf );
3251 buffer_free( group_buf );
3252 buffer_free( having_buf );
3253 if( defaultselhash ) jsonObjectFree( defaultselhash );
3258 // Put it all together
3259 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3263 if (!from_function) {
3264 // Now, walk the join tree and add that clause
3266 char* join_clause = searchJOIN( join_hash, core_meta );
3268 buffer_add(sql_buf, join_clause);
3272 osrfAppSessionStatus(
3274 OSRF_STATUS_INTERNALSERVERERROR,
3275 "osrfMethodException",
3277 "Unable to construct JOIN clause(s)"
3279 buffer_free( sql_buf );
3280 buffer_free( order_buf );
3281 buffer_free( group_buf );
3282 buffer_free( having_buf );
3283 if( defaultselhash ) jsonObjectFree( defaultselhash );
3289 // Build a WHERE clause, if there is one
3290 if ( search_hash ) {
3291 buffer_add(sql_buf, " WHERE ");
3293 // and it's on the WHERE clause
3294 char* pred = searchWHERE( search_hash, core_meta, AND_OP_JOIN, ctx );
3297 buffer_add(sql_buf, pred);
3301 osrfAppSessionStatus(
3303 OSRF_STATUS_INTERNALSERVERERROR,
3304 "osrfMethodException",
3306 "Severe query error in WHERE predicate -- see error log for more details"
3310 buffer_free(having_buf);
3311 buffer_free(group_buf);
3312 buffer_free(order_buf);
3313 buffer_free(sql_buf);
3314 if (defaultselhash) jsonObjectFree(defaultselhash);
3319 // Build a HAVING clause, if there is one
3320 if ( having_hash ) {
3321 buffer_add(sql_buf, " HAVING ");
3323 // and it's on the the WHERE clause
3324 char* pred = searchWHERE( having_hash, core_meta, AND_OP_JOIN, ctx );
3327 buffer_add(sql_buf, pred);
3331 osrfAppSessionStatus(
3333 OSRF_STATUS_INTERNALSERVERERROR,
3334 "osrfMethodException",
3336 "Severe query error in HAVING predicate -- see error log for more details"
3340 buffer_free(having_buf);
3341 buffer_free(group_buf);
3342 buffer_free(order_buf);
3343 buffer_free(sql_buf);
3344 if (defaultselhash) jsonObjectFree(defaultselhash);
3349 // Build an ORDER BY clause, if there is one
3351 jsonIterator* class_itr = jsonNewIterator( order_hash );
3352 while ( (snode = jsonIteratorNext( class_itr )) ) {
3354 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
3357 if ( snode->type == JSON_HASH ) {
3359 jsonIterator* order_itr = jsonNewIterator( snode );
3360 while ( (onode = jsonIteratorNext( order_itr )) ) {
3362 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ))
3365 const char* direction = NULL;
3366 if ( onode->type == JSON_HASH ) {
3367 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3368 string = searchFieldTransform(
3370 oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ),
3374 growing_buffer* field_buf = buffer_init(16);
3375 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3376 string = buffer_release(field_buf);
3379 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3380 const char* dir = jsonObjectGetString(tmp_const);
3381 if (!strncasecmp(dir, "d", 1)) {
3382 direction = " DESC";
3389 string = strdup(order_itr->key);
3390 const char* dir = jsonObjectGetString(onode);
3391 if (!strncasecmp(dir, "d", 1)) {
3392 direction = " DESC";
3401 buffer_add(order_buf, ", ");
3404 buffer_add(order_buf, string);
3408 buffer_add(order_buf, direction);
3412 // jsonIteratorFree(order_itr);
3414 } else if ( snode->type == JSON_ARRAY ) {
3416 jsonIterator* order_itr = jsonNewIterator( snode );
3417 while ( (onode = jsonIteratorNext( order_itr )) ) {
3419 const char* _f = jsonObjectGetString( onode );
3421 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, _f))
3427 buffer_add(order_buf, ", ");
3430 buffer_add(order_buf, _f);
3433 // jsonIteratorFree(order_itr);
3436 // IT'S THE OOOOOOOOOOOLD STYLE!
3438 osrfLogError(OSRF_LOG_MARK, "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3440 osrfAppSessionStatus(
3442 OSRF_STATUS_INTERNALSERVERERROR,
3443 "osrfMethodException",
3445 "Severe query error -- see error log for more details"
3450 buffer_free(having_buf);
3451 buffer_free(group_buf);
3452 buffer_free(order_buf);
3453 buffer_free(sql_buf);
3454 if (defaultselhash) jsonObjectFree(defaultselhash);
3455 jsonIteratorFree(class_itr);
3460 // jsonIteratorFree(class_itr);
3464 string = buffer_release(group_buf);
3466 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
3467 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3468 OSRF_BUFFER_ADD( sql_buf, string );
3473 string = buffer_release(having_buf);
3476 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3477 OSRF_BUFFER_ADD( sql_buf, string );
3482 string = buffer_release(order_buf);
3485 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3486 OSRF_BUFFER_ADD( sql_buf, string );
3492 const char* str = jsonObjectGetString(limit);
3493 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
3497 const char* str = jsonObjectGetString(offset);
3498 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
3501 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3504 if (defaultselhash) jsonObjectFree(defaultselhash);
3506 return buffer_release(sql_buf);
3510 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
3512 const char* locale = osrf_message_get_last_locale();
3514 osrfHash* fields = osrfHashGet(meta, "fields");
3515 char* core_class = osrfHashGet(meta, "classname");
3517 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
3519 jsonObject* node = NULL;
3520 jsonObject* snode = NULL;
3521 jsonObject* onode = NULL;
3522 const jsonObject* _tmp = NULL;
3523 jsonObject* selhash = NULL;
3524 jsonObject* defaultselhash = NULL;
3526 growing_buffer* sql_buf = buffer_init(128);
3527 growing_buffer* select_buf = buffer_init(128);
3529 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
3530 defaultselhash = jsonNewObjectType(JSON_HASH);
3531 selhash = defaultselhash;
3534 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
3535 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
3536 jsonObject* flist = jsonObjectGetKey( selhash, core_class );
3541 osrfStringArray* keys = osrfHashKeys( fields );
3542 while ( (field = osrfStringArrayGetString(keys, i++)) ) {
3543 if( ! str_is_true( osrfHashGet( osrfHashGet( fields, field ), "virtual" ) ) )
3544 jsonObjectPush( flist, jsonNewObject( field ) );
3546 osrfStringArrayFree(keys);
3550 jsonIterator* class_itr = jsonNewIterator( selhash );
3551 while ( (snode = jsonIteratorNext( class_itr )) ) {
3553 char* cname = class_itr->key;
3554 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
3555 if (!idlClass) continue;
3557 if (strcmp(core_class,class_itr->key)) {
3558 if (!join_hash) continue;
3560 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
3562 jsonObjectFree(found);
3566 jsonObjectFree(found);
3569 jsonIterator* select_itr = jsonNewIterator( snode );
3570 while ( (node = jsonIteratorNext( select_itr )) ) {
3571 const char* item_str = jsonObjectGetString( node );
3572 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
3573 char* fname = osrfHashGet(field, "name");
3575 if (!field) continue;
3580 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
3585 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
3586 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
3589 i18n = osrfHashGet(field, "i18n");
3591 if( str_is_true( i18n ) ) {
3592 char* pkey = osrfHashGet(idlClass, "primarykey");
3593 char* tname = osrfHashGet(idlClass, "tablename");
3595 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);
3597 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3600 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3604 jsonIteratorFree(select_itr);
3607 jsonIteratorFree(class_itr);
3609 char* col_list = buffer_release(select_buf);
3610 char* table = getSourceDefinition(meta);
3612 table = strdup( "(null)" );
3614 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
3619 char* join_clause = searchJOIN( join_hash, meta );
3620 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
3621 OSRF_BUFFER_ADD(sql_buf, join_clause);
3625 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
3626 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
3628 buffer_add(sql_buf, " WHERE ");
3630 char* pred = searchWHERE( search_hash, meta, AND_OP_JOIN, ctx );
3632 osrfAppSessionStatus(
3634 OSRF_STATUS_INTERNALSERVERERROR,
3635 "osrfMethodException",
3637 "Severe query error -- see error log for more details"
3639 buffer_free(sql_buf);
3640 if(defaultselhash) jsonObjectFree(defaultselhash);
3643 buffer_add(sql_buf, pred);
3648 char* string = NULL;
3649 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
3651 growing_buffer* order_buf = buffer_init(128);
3654 jsonIterator* class_itr = jsonNewIterator( _tmp );
3655 while ( (snode = jsonIteratorNext( class_itr )) ) {
3657 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
3660 if ( snode->type == JSON_HASH ) {
3662 jsonIterator* order_itr = jsonNewIterator( snode );
3663 while ( (onode = jsonIteratorNext( order_itr )) ) {
3665 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ))
3668 char* direction = NULL;
3669 if ( onode->type == JSON_HASH ) {
3670 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3671 string = searchFieldTransform(
3673 oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ),
3677 growing_buffer* field_buf = buffer_init(16);
3678 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3679 string = buffer_release(field_buf);
3682 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
3683 const char* dir = jsonObjectGetString(_tmp);
3684 if (!strncasecmp(dir, "d", 1)) {
3685 direction = " DESC";
3692 string = strdup(order_itr->key);
3693 const char* dir = jsonObjectGetString(onode);
3694 if (!strncasecmp(dir, "d", 1)) {
3695 direction = " DESC";
3704 buffer_add(order_buf, ", ");
3707 buffer_add(order_buf, string);
3711 buffer_add(order_buf, direction);
3716 jsonIteratorFree(order_itr);
3719 const char* str = jsonObjectGetString(snode);
3720 buffer_add(order_buf, str);
3726 jsonIteratorFree(class_itr);
3728 string = buffer_release(order_buf);
3731 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3732 OSRF_BUFFER_ADD( sql_buf, string );
3738 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
3739 const char* str = jsonObjectGetString(_tmp);
3747 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
3749 const char* str = jsonObjectGetString(_tmp);
3758 if (defaultselhash) jsonObjectFree(defaultselhash);
3760 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3761 return buffer_release(sql_buf);
3764 int doJSONSearch ( osrfMethodContext* ctx ) {
3765 if(osrfMethodVerifyContext( ctx )) {
3766 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
3770 osrfLogDebug(OSRF_LOG_MARK, "Recieved query request");
3775 dbhandle = writehandle;
3777 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
3781 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
3782 flags |= SELECT_DISTINCT;
3784 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
3785 flags |= DISABLE_I18N;
3787 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
3790 jsonObjectGetKey( hash, "select" ),
3791 jsonObjectGetKey( hash, "from" ),
3792 jsonObjectGetKey( hash, "where" ),
3793 jsonObjectGetKey( hash, "having" ),
3794 jsonObjectGetKey( hash, "order_by" ),
3795 jsonObjectGetKey( hash, "limit" ),
3796 jsonObjectGetKey( hash, "offset" ),
3805 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
3806 dbi_result result = dbi_conn_query(dbhandle, sql);
3809 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
3811 if (dbi_result_first_row(result)) {
3812 /* JSONify the result */
3813 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
3816 jsonObject* return_val = oilsMakeJSONFromResult( result );
3817 osrfAppRespond( ctx, return_val );
3818 jsonObjectFree( return_val );
3819 } while (dbi_result_next_row(result));
3822 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
3825 osrfAppRespondComplete( ctx, NULL );
3827 /* clean up the query */
3828 dbi_result_free(result);
3832 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
3833 osrfAppSessionStatus(
3835 OSRF_STATUS_INTERNALSERVERERROR,
3836 "osrfMethodException",
3838 "Severe query error -- see error log for more details"
3846 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
3847 const jsonObject* params, int* err ) {
3850 dbhandle = writehandle;
3852 osrfHash* links = osrfHashGet(meta, "links");
3853 osrfHash* fields = osrfHashGet(meta, "fields");
3854 char* core_class = osrfHashGet(meta, "classname");
3855 char* pkey = osrfHashGet(meta, "primarykey");
3857 const jsonObject* _tmp;
3859 jsonObject* search_hash = jsonObjectGetIndex(params, 0);
3860 jsonObject* order_hash = jsonObjectGetIndex(params, 1);
3862 char* sql = buildSELECT( search_hash, order_hash, meta, ctx );
3864 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
3869 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
3871 dbi_result result = dbi_conn_query(dbhandle, sql);
3872 if( NULL == result ) {
3873 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
3874 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
3875 osrfAppSessionStatus(
3877 OSRF_STATUS_INTERNALSERVERERROR,
3878 "osrfMethodException",
3880 "Severe query error -- see error log for more details"
3887 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
3890 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
3891 osrfHash* dedup = osrfNewHash();
3893 if (dbi_result_first_row(result)) {
3894 /* JSONify the result */
3895 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
3897 obj = oilsMakeFieldmapperFromResult( result, meta );
3898 char* pkey_val = oilsFMGetString( obj, pkey );
3899 if ( osrfHashGet( dedup, pkey_val ) ) {
3900 jsonObjectFree(obj);
3903 osrfHashSet( dedup, pkey_val, pkey_val );
3904 jsonObjectPush(res_list, obj);
3906 } while (dbi_result_next_row(result));
3908 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
3912 osrfHashFree(dedup);
3913 /* clean up the query */
3914 dbi_result_free(result);
3917 if (res_list->size && order_hash) {
3918 _tmp = jsonObjectGetKeyConst( order_hash, "flesh" );
3920 int x = (int)jsonObjectGetNumber(_tmp);
3921 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
3923 const jsonObject* temp_blob;
3924 if ((temp_blob = jsonObjectGetKeyConst( order_hash, "flesh_fields" )) && x > 0) {
3926 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
3927 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
3929 osrfStringArray* link_fields = NULL;
3932 if (flesh_fields->size == 1) {
3933 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
3934 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
3939 link_fields = osrfNewStringArray(1);
3940 jsonIterator* _i = jsonNewIterator( flesh_fields );
3941 while ((_f = jsonIteratorNext( _i ))) {
3942 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
3944 jsonIteratorFree(_i);
3949 jsonIterator* itr = jsonNewIterator( res_list );
3950 while ((cur = jsonIteratorNext( itr ))) {
3955 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
3957 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
3959 osrfHash* kid_link = osrfHashGet(links, link_field);
3960 if (!kid_link) continue;
3962 osrfHash* field = osrfHashGet(fields, link_field);
3963 if (!field) continue;
3965 osrfHash* value_field = field;
3967 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
3968 if (!kid_idl) continue;
3970 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
3971 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
3974 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
3975 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
3978 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
3980 if (link_map->size > 0) {
3981 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
3984 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
3989 osrfHashGet(kid_link, "class"),
3996 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
3997 osrfHashGet(kid_link, "field"),
3998 osrfHashGet(kid_link, "class"),
3999 osrfHashGet(kid_link, "key"),
4000 osrfHashGet(kid_link, "reltype")
4003 jsonObject* fake_params = jsonNewObjectType(JSON_ARRAY);
4004 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH)); // search hash
4005 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH)); // order/flesh hash
4007 osrfLogDebug(OSRF_LOG_MARK, "Creating dummy params object...");
4009 const char* search_key = jsonObjectGetString(
4012 atoi( osrfHashGet(value_field, "array_position") )
4017 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4022 jsonObjectGetIndex(fake_params, 0),
4023 osrfHashGet(kid_link, "key"),
4024 jsonNewObject( search_key )
4028 jsonObjectGetIndex(fake_params, 1),
4030 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4034 jsonObjectSetKey( jsonObjectGetIndex(fake_params, 1), "flesh_fields", jsonObjectClone(flesh_blob) );
4036 if (jsonObjectGetKeyConst(order_hash, "order_by")) {
4038 jsonObjectGetIndex(fake_params, 1),
4040 jsonObjectClone(jsonObjectGetKeyConst(order_hash, "order_by"))
4044 if (jsonObjectGetKeyConst(order_hash, "select")) {
4046 jsonObjectGetIndex(fake_params, 1),
4048 jsonObjectClone(jsonObjectGetKeyConst(order_hash, "select"))
4052 jsonObject* kids = doFieldmapperSearch(ctx, kid_idl, fake_params, err);
4055 jsonObjectFree( fake_params );
4056 osrfStringArrayFree(link_fields);
4057 jsonIteratorFree(itr);
4058 jsonObjectFree(res_list);
4059 jsonObjectFree(flesh_blob);
4063 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4065 jsonObject* X = NULL;
4066 if ( link_map->size > 0 && kids->size > 0 ) {
4068 kids = jsonNewObjectType(JSON_ARRAY);
4070 jsonObject* _k_node;
4071 jsonIterator* _k = jsonNewIterator( X );
4072 while ((_k_node = jsonIteratorNext( _k ))) {
4078 (unsigned long)atoi(
4084 osrfHashGet(kid_link, "class")
4088 osrfStringArrayGetString( link_map, 0 )
4097 jsonIteratorFree(_k);
4100 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4101 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4104 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4105 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4109 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4110 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4113 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4114 jsonObjectClone( kids )
4119 jsonObjectFree(kids);
4123 jsonObjectFree( kids );
4124 jsonObjectFree( fake_params );
4126 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4127 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4131 jsonObjectFree( flesh_blob );
4132 osrfStringArrayFree(link_fields);
4133 jsonIteratorFree(itr);
4142 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
4144 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4146 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
4148 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
4151 if (!verifyObjectClass(ctx, target)) {
4156 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4157 osrfAppSessionStatus(
4159 OSRF_STATUS_BADREQUEST,
4160 "osrfMethodException",
4162 "No active transaction -- required for UPDATE"
4168 // The following test is harmless but redundant. If a class is
4169 // readonly, we don't register an update method for it.
4170 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4171 osrfAppSessionStatus(
4173 OSRF_STATUS_BADREQUEST,
4174 "osrfMethodException",
4176 "Cannot UPDATE readonly class"
4182 dbhandle = writehandle;
4184 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4186 // Set the last_xact_id
4187 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4189 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
4190 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4193 char* pkey = osrfHashGet(meta, "primarykey");
4194 osrfHash* fields = osrfHashGet(meta, "fields");
4196 char* id = oilsFMGetString( target, pkey );
4200 "%s updating %s object with %s = %s",
4202 osrfHashGet(meta, "fieldmapper"),
4207 growing_buffer* sql = buffer_init(128);
4208 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4213 osrfStringArray* field_list = osrfHashKeys( fields );
4214 while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
4216 osrfHash* field = osrfHashGet( fields, field_name );
4218 if(!( strcmp( field_name, pkey ) )) continue;
4219 if( str_is_true( osrfHashGet(osrfHashGet(fields,field_name), "virtual") ) )
4222 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4224 int value_is_numeric = 0; // boolean
4226 if (field_object && field_object->classname) {
4227 value = oilsFMGetString(
4229 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4232 value = jsonObjectToSimpleString( field_object );
4233 if( field_object && JSON_NUMBER == field_object->type )
4234 value_is_numeric = 1;
4237 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s", osrfHashGet(meta, "fieldmapper"), field_name, value);
4239 if (!field_object || field_object->type == JSON_NULL) {
4240 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) ) && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
4241 if (first) first = 0;
4242 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4243 buffer_fadd( sql, " %s = NULL", field_name );
4246 } else if ( value_is_numeric || !strcmp( get_primitive( field ), "number") ) {
4247 if (first) first = 0;
4248 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4250 const char* numtype = get_datatype( field );
4251 if ( !strncmp( numtype, "INT", 3 ) ) {
4252 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
4253 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
4254 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
4256 // Must really be intended as a string, so quote it
4257 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4258 buffer_fadd( sql, " %s = %s", field_name, value );
4260 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4261 osrfAppSessionStatus(
4263 OSRF_STATUS_INTERNALSERVERERROR,
4264 "osrfMethodException",
4266 "Error quoting string -- please see the error log for more details"
4276 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
4279 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4280 if (first) first = 0;
4281 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4282 buffer_fadd( sql, " %s = %s", field_name, value );
4285 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4286 osrfAppSessionStatus(
4288 OSRF_STATUS_INTERNALSERVERERROR,
4289 "osrfMethodException",
4291 "Error quoting string -- please see the error log for more details"
4305 jsonObject* obj = jsonNewObject(id);
4307 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4308 dbi_conn_quote_string(dbhandle, &id);
4310 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
4312 char* query = buffer_release(sql);
4313 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
4315 dbi_result result = dbi_conn_query(dbhandle, query);
4319 jsonObjectFree(obj);
4320 obj = jsonNewObject(NULL);
4323 "%s ERROR updating %s object with %s = %s",
4325 osrfHashGet(meta, "fieldmapper"),
4336 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4338 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4340 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4341 osrfAppSessionStatus(
4343 OSRF_STATUS_BADREQUEST,
4344 "osrfMethodException",
4346 "No active transaction -- required for DELETE"
4352 // The following test is harmless but redundant. If a class is
4353 // readonly, we don't register a delete method for it.
4354 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4355 osrfAppSessionStatus(
4357 OSRF_STATUS_BADREQUEST,
4358 "osrfMethodException",
4360 "Cannot DELETE readonly class"
4366 dbhandle = writehandle;
4370 char* pkey = osrfHashGet(meta, "primarykey");
4378 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4379 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4384 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4387 if (!verifyObjectPCRUD( ctx, NULL )) {
4392 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4397 "%s deleting %s object with %s = %s",
4399 osrfHashGet(meta, "fieldmapper"),
4404 obj = jsonNewObject(id);
4406 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4407 dbi_conn_quote_string(writehandle, &id);
4409 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4412 jsonObjectFree(obj);
4413 obj = jsonNewObject(NULL);
4416 "%s ERROR deleting %s object with %s = %s",
4418 osrfHashGet(meta, "fieldmapper"),
4431 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4432 if(!(result && meta)) return jsonNULL;
4434 jsonObject* object = jsonNewObject(NULL);
4435 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4437 osrfHash* fields = osrfHashGet(meta, "fields");
4439 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4443 char dt_string[256];
4447 int columnIndex = 1;
4449 unsigned short type;
4450 const char* columnName;
4452 /* cycle through the column list */
4453 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4455 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4457 fmIndex = -1; // reset the position
4459 /* determine the field type and storage attributes */
4460 type = dbi_result_get_field_type(result, columnName);
4461 attr = dbi_result_get_field_attribs(result, columnName);
4463 /* fetch the fieldmapper index */
4464 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4466 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4469 const char* pos = (char*)osrfHashGet(_f, "array_position");
4470 if ( !pos ) continue;
4472 fmIndex = atoi( pos );
4473 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
4478 if (dbi_result_field_is_null(result, columnName)) {
4479 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
4484 case DBI_TYPE_INTEGER :
4486 if( attr & DBI_INTEGER_SIZE8 )
4487 jsonObjectSetIndex( object, fmIndex,
4488 jsonNewNumberObject(dbi_result_get_longlong(result, columnName)));
4490 jsonObjectSetIndex( object, fmIndex,
4491 jsonNewNumberObject(dbi_result_get_int(result, columnName)));
4495 case DBI_TYPE_DECIMAL :
4496 jsonObjectSetIndex( object, fmIndex,
4497 jsonNewNumberObject(dbi_result_get_double(result, columnName)));
4500 case DBI_TYPE_STRING :
4506 jsonNewObject( dbi_result_get_string(result, columnName) )
4511 case DBI_TYPE_DATETIME :
4513 memset(dt_string, '\0', sizeof(dt_string));
4514 memset(&gmdt, '\0', sizeof(gmdt));
4516 _tmp_dt = dbi_result_get_datetime(result, columnName);
4519 if (!(attr & DBI_DATETIME_DATE)) {
4520 gmtime_r( &_tmp_dt, &gmdt );
4521 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4522 } else if (!(attr & DBI_DATETIME_TIME)) {
4523 localtime_r( &_tmp_dt, &gmdt );
4524 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4526 localtime_r( &_tmp_dt, &gmdt );
4527 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4530 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
4534 case DBI_TYPE_BINARY :
4535 osrfLogError( OSRF_LOG_MARK,
4536 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4544 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
4545 if(!result) return jsonNULL;
4547 jsonObject* object = jsonNewObject(NULL);
4550 char dt_string[256];
4554 int columnIndex = 1;
4556 unsigned short type;
4557 const char* columnName;
4559 /* cycle through the column list */
4560 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4562 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4564 fmIndex = -1; // reset the position
4566 /* determine the field type and storage attributes */
4567 type = dbi_result_get_field_type(result, columnName);
4568 attr = dbi_result_get_field_attribs(result, columnName);
4570 if (dbi_result_field_is_null(result, columnName)) {
4571 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
4576 case DBI_TYPE_INTEGER :
4578 if( attr & DBI_INTEGER_SIZE8 )
4579 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_longlong(result, columnName)) );
4581 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_int(result, columnName)) );
4584 case DBI_TYPE_DECIMAL :
4585 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_double(result, columnName)) );
4588 case DBI_TYPE_STRING :
4589 jsonObjectSetKey( object, columnName, jsonNewObject(dbi_result_get_string(result, columnName)) );
4592 case DBI_TYPE_DATETIME :
4594 memset(dt_string, '\0', sizeof(dt_string));
4595 memset(&gmdt, '\0', sizeof(gmdt));
4597 _tmp_dt = dbi_result_get_datetime(result, columnName);
4600 if (!(attr & DBI_DATETIME_DATE)) {
4601 gmtime_r( &_tmp_dt, &gmdt );
4602 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4603 } else if (!(attr & DBI_DATETIME_TIME)) {
4604 localtime_r( &_tmp_dt, &gmdt );
4605 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4607 localtime_r( &_tmp_dt, &gmdt );
4608 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4611 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
4614 case DBI_TYPE_BINARY :
4615 osrfLogError( OSRF_LOG_MARK,
4616 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4624 // Interpret a string as true or false
4625 static int str_is_true( const char* str ) {
4626 if( NULL == str || strcasecmp( str, "true" ) )
4632 // Interpret a jsonObject as true or false
4633 static int obj_is_true( const jsonObject* obj ) {
4636 else switch( obj->type )
4644 if( strcasecmp( obj->value.s, "true" ) )
4648 case JSON_NUMBER : // Support 1/0 for perl's sake
4649 if( jsonObjectGetNumber( obj ) == 1.0 )
4658 // Translate a numeric code into a text string identifying a type of
4659 // jsonObject. To be used for building error messages.
4660 static const char* json_type( int code ) {
4666 return "JSON_ARRAY";
4668 return "JSON_STRING";
4670 return "JSON_NUMBER";
4676 return "(unrecognized)";
4680 // Extract the "primitive" attribute from an IDL field definition.
4681 // If we haven't initialized the app, then we must be running in
4682 // some kind of testbed. In that case, default to "string".
4683 static const char* get_primitive( osrfHash* field ) {
4684 const char* s = osrfHashGet( field, "primitive" );
4686 if( child_initialized )
4689 "%s ERROR No \"datatype\" attribute for field \"%s\"",
4691 osrfHashGet( field, "name" )
4699 // Extract the "datatype" attribute from an IDL field definition.
4700 // If we haven't initialized the app, then we must be running in
4701 // some kind of testbed. In that case, default to to NUMERIC,
4702 // since we look at the datatype only for numbers.
4703 static const char* get_datatype( osrfHash* field ) {
4704 const char* s = osrfHashGet( field, "datatype" );
4706 if( child_initialized )
4709 "%s ERROR No \"datatype\" attribute for field \"%s\"",
4711 osrfHashGet( field, "name" )
4720 If the input string is potentially a valid SQL identifier, return 1.
4723 Purpose: to prevent certain kinds of SQL injection. To that end we
4724 don't necessarily need to follow all the rules exactly, such as requiring
4725 that the first character not be a digit.
4727 We allow leading and trailing white space. In between, we do not allow
4728 punctuation (except for underscores and dollar signs), control
4729 characters, or embedded white space.
4731 More pedantically we should allow quoted identifiers containing arbitrary
4732 characters, but for the foreseeable future such quoted identifiers are not
4733 likely to be an issue.
4735 static int is_identifier( const char* s) {
4739 // Skip leading white space
4740 while( isspace( (unsigned char) *s ) )
4744 return 0; // Nothing but white space? Not okay.
4746 // Check each character until we reach white space or
4747 // end-of-string. Letters, digits, underscores, and
4748 // dollar signs are okay. Control characters and other
4749 // punctuation characters are not okay. Anything else
4750 // is okay -- it could for example be part of a multibyte
4751 // UTF8 character such as a letter with diacritical marks,
4752 // and those are allowed.
4754 if( isalnum( (unsigned char) *s )
4757 ; // Fine; keep going
4758 else if( ispunct( (unsigned char) *s )
4759 || iscntrl( (unsigned char) *s ) )
4762 } while( *s && ! isspace( (unsigned char) *s ) );
4764 // If we found any white space in the above loop,
4765 // the rest had better be all white space.
4767 while( isspace( (unsigned char) *s ) )
4771 return 0; // White space was embedded within non-white space
4777 Determine whether to accept a character string as a comparison operator.
4778 Return 1 if it's good, or 0 if it's bad.
4780 We don't validate it for real. We just make sure that it doesn't contain
4781 any semicolons or white space (with a special exception for the
4782 "SIMILAR TO" operator). The idea is to block certain kinds of SQL
4783 injection. If it has no semicolons or white space but it's still not a
4784 valid operator, then the database will complain.
4786 Another approach would be to compare the string against a short list of
4787 approved operators. We don't do that because we want to allow custom
4788 operators like ">100*", which would be difficult or impossible to
4789 express otherwise in a JSON query.
4791 static int is_good_operator( const char* op ) {
4792 if( !op ) return 0; // Sanity check
4796 if( isspace( (unsigned char) *s ) ) {
4797 // Special exception for SIMILAR TO. Someday we might make
4798 // exceptions for IS DISTINCT FROM and IS NOT DISTINCT FROM.
4799 if( !strcasecmp( op, "similar to" ) )
4804 else if( ';' == *s )