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);
371 osrfStringArray* classes = osrfHashKeys( oilsIDL() );
373 while ( (classname = osrfStringArrayGetString(classes, i++)) ) {
374 osrfHash* class = osrfHashGet( oilsIDL(), classname );
375 osrfHash* fields = osrfHashGet( class, "fields" );
377 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
378 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
382 char* tabledef = getSourceDefinition(class);
384 tabledef = strdup( "(null)" );
386 growing_buffer* sql_buf = buffer_init(32);
387 buffer_fadd( sql_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
391 char* sql = buffer_release(sql_buf);
392 osrfLogDebug(OSRF_LOG_MARK, "%s Investigatory SQL = %s", MODULENAME, sql);
394 dbi_result result = dbi_conn_query(writehandle, sql);
400 const char* columnName;
402 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
404 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
406 /* fetch the fieldmapper index */
407 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
409 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", (char*)columnName);
411 /* determine the field type and storage attributes */
412 type = dbi_result_get_field_type(result, columnName);
416 case DBI_TYPE_INTEGER : {
418 if ( !osrfHashGet(_f, "primitive") )
419 osrfHashSet(_f,"number", "primitive");
421 int attr = dbi_result_get_field_attribs(result, columnName);
422 if( attr & DBI_INTEGER_SIZE8 )
423 osrfHashSet(_f,"INT8", "datatype");
425 osrfHashSet(_f,"INT", "datatype");
428 case DBI_TYPE_DECIMAL :
429 if ( !osrfHashGet(_f, "primitive") )
430 osrfHashSet(_f,"number", "primitive");
432 osrfHashSet(_f,"NUMERIC", "datatype");
435 case DBI_TYPE_STRING :
436 if ( !osrfHashGet(_f, "primitive") )
437 osrfHashSet(_f,"string", "primitive");
438 osrfHashSet(_f,"TEXT", "datatype");
441 case DBI_TYPE_DATETIME :
442 if ( !osrfHashGet(_f, "primitive") )
443 osrfHashSet(_f,"string", "primitive");
445 osrfHashSet(_f,"TIMESTAMP", "datatype");
448 case DBI_TYPE_BINARY :
449 if ( !osrfHashGet(_f, "primitive") )
450 osrfHashSet(_f,"string", "primitive");
452 osrfHashSet(_f,"BYTEA", "datatype");
457 "Setting [%s] to primitive [%s] and datatype [%s]...",
459 osrfHashGet(_f, "primitive"),
460 osrfHashGet(_f, "datatype")
464 dbi_result_free(result);
466 osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", (char*)classname);
470 osrfStringArrayFree(classes);
472 child_initialized = 1;
477 This function is a sleazy hack intended *only* for testing and
478 debugging. Any real server process should initialize the
479 database connection by calling osrfAppChildInit().
481 void set_cstore_dbi_conn( dbi_conn conn ) {
482 dbhandle = writehandle = conn;
485 void userDataFree( void* blob ) {
486 osrfHashFree( (osrfHash*)blob );
490 static void sessionDataFree( char* key, void* item ) {
491 if (!(strcmp(key,"xact_id"))) {
493 dbi_conn_query(writehandle, "ROLLBACK;");
500 int beginTransaction ( osrfMethodContext* ctx ) {
501 if(osrfMethodVerifyContext( ctx )) {
502 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
507 jsonObject* user = verifyUserPCRUD( ctx );
508 if (!user) return -1;
509 jsonObjectFree(user);
512 dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
514 osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
515 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error starting transaction" );
518 jsonObject* ret = jsonNewObject(ctx->session->session_id);
519 osrfAppRespondComplete( ctx, ret );
522 if (!ctx->session->userData) {
523 ctx->session->userData = osrfNewHash();
524 osrfHashSetCallback((osrfHash*)ctx->session->userData, &sessionDataFree);
527 osrfHashSet( (osrfHash*)ctx->session->userData, strdup( ctx->session->session_id ), "xact_id" );
528 ctx->session->userDataFree = &userDataFree;
534 int setSavepoint ( osrfMethodContext* ctx ) {
535 if(osrfMethodVerifyContext( ctx )) {
536 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
543 jsonObject* user = verifyUserPCRUD( ctx );
544 if (!user) return -1;
545 jsonObjectFree(user);
548 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
549 osrfAppSessionStatus(
551 OSRF_STATUS_INTERNALSERVERERROR,
552 "osrfMethodException",
554 "No active transaction -- required for savepoints"
559 const char* spName = jsonObjectGetString(jsonObjectGetIndex(ctx->params, spNamePos));
561 dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
565 "%s: Error creating savepoint %s in transaction %s",
568 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
570 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
571 "osrfMethodException", ctx->request, "Error creating savepoint" );
574 jsonObject* ret = jsonNewObject(spName);
575 osrfAppRespondComplete( ctx, ret );
581 int releaseSavepoint ( osrfMethodContext* ctx ) {
582 if(osrfMethodVerifyContext( ctx )) {
583 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
590 jsonObject* user = verifyUserPCRUD( ctx );
591 if (!user) return -1;
592 jsonObjectFree(user);
595 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
596 osrfAppSessionStatus(
598 OSRF_STATUS_INTERNALSERVERERROR,
599 "osrfMethodException",
601 "No active transaction -- required for savepoints"
606 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
608 dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
612 "%s: Error releasing savepoint %s in transaction %s",
615 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
617 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
618 "osrfMethodException", ctx->request, "Error releasing savepoint" );
621 jsonObject* ret = jsonNewObject(spName);
622 osrfAppRespondComplete( ctx, ret );
628 int rollbackSavepoint ( osrfMethodContext* ctx ) {
629 if(osrfMethodVerifyContext( ctx )) {
630 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
637 jsonObject* user = verifyUserPCRUD( ctx );
638 if (!user) return -1;
639 jsonObjectFree(user);
642 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
643 osrfAppSessionStatus(
645 OSRF_STATUS_INTERNALSERVERERROR,
646 "osrfMethodException",
648 "No active transaction -- required for savepoints"
653 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
655 dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
659 "%s: Error rolling back savepoint %s in transaction %s",
662 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
664 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
665 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
668 jsonObject* ret = jsonNewObject(spName);
669 osrfAppRespondComplete( ctx, ret );
675 int commitTransaction ( osrfMethodContext* ctx ) {
676 if(osrfMethodVerifyContext( ctx )) {
677 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
682 jsonObject* user = verifyUserPCRUD( ctx );
683 if (!user) return -1;
684 jsonObjectFree(user);
687 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
688 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to commit" );
692 dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
694 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
695 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error committing transaction" );
698 osrfHashRemove(ctx->session->userData, "xact_id");
699 jsonObject* ret = jsonNewObject(ctx->session->session_id);
700 osrfAppRespondComplete( ctx, ret );
706 int rollbackTransaction ( osrfMethodContext* ctx ) {
707 if(osrfMethodVerifyContext( ctx )) {
708 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
713 jsonObject* user = verifyUserPCRUD( ctx );
714 if (!user) return -1;
715 jsonObjectFree(user);
718 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
719 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to roll back" );
723 dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
725 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
726 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error rolling back transaction" );
729 osrfHashRemove(ctx->session->userData, "xact_id");
730 jsonObject* ret = jsonNewObject(ctx->session->session_id);
731 osrfAppRespondComplete( ctx, ret );
737 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
738 if(osrfMethodVerifyContext( ctx )) {
739 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
743 osrfHash* meta = (osrfHash*) ctx->method->userData;
744 osrfHash* class_obj = osrfHashGet( meta, "class" );
748 const char* methodtype = osrfHashGet(meta, "methodtype");
749 jsonObject * obj = NULL;
751 if (!strcmp(methodtype, "create")) {
752 obj = doCreate(ctx, &err);
753 osrfAppRespondComplete( ctx, obj );
755 else if (!strcmp(methodtype, "retrieve")) {
756 obj = doRetrieve(ctx, &err);
757 osrfAppRespondComplete( ctx, obj );
759 else if (!strcmp(methodtype, "update")) {
760 obj = doUpdate(ctx, &err);
761 osrfAppRespondComplete( ctx, obj );
763 else if (!strcmp(methodtype, "delete")) {
764 obj = doDelete(ctx, &err);
765 osrfAppRespondComplete( ctx, obj );
767 else if (!strcmp(methodtype, "search")) {
769 jsonObject* _p = jsonObjectClone( ctx->params );
772 _p = jsonParseString("[]");
773 jsonObjectPush(_p, jsonObjectClone(jsonObjectGetIndex(ctx->params, 1)));
774 jsonObjectPush(_p, jsonObjectClone(jsonObjectGetIndex(ctx->params, 2)));
777 obj = doFieldmapperSearch(ctx, class_obj, _p, &err);
783 jsonIterator* itr = jsonNewIterator( obj );
784 while ((cur = jsonIteratorNext( itr ))) {
786 if(!verifyObjectPCRUD(ctx, cur)) continue;
788 osrfAppRespond( ctx, cur );
790 jsonIteratorFree(itr);
791 osrfAppRespondComplete( ctx, NULL );
793 } else if (!strcmp(methodtype, "id_list")) {
795 jsonObject* _p = jsonObjectClone( ctx->params );
798 _p = jsonParseString("[]");
799 jsonObjectPush(_p, jsonObjectClone(jsonObjectGetIndex(ctx->params, 1)));
800 jsonObjectPush(_p, jsonObjectClone(jsonObjectGetIndex(ctx->params, 2)));
803 if (jsonObjectGetIndex( _p, 1 )) {
804 jsonObjectRemoveKey( jsonObjectGetIndex( _p, 1 ), "select" );
805 jsonObjectRemoveKey( jsonObjectGetIndex( _p, 1 ), "no_i18n" );
806 jsonObjectRemoveKey( jsonObjectGetIndex( _p, 1 ), "flesh" );
807 jsonObjectRemoveKey( jsonObjectGetIndex( _p, 1 ), "flesh_columns" );
809 jsonObjectSetIndex( _p, 1, jsonNewObjectType(JSON_HASH) );
812 jsonObjectSetKey( jsonObjectGetIndex( _p, 1 ), "no_i18n", jsonNewBoolObject( 1 ) );
815 jsonObjectGetIndex( _p, 1 ),
818 "{ \"%s\":[\"%s\"] }",
819 osrfHashGet( class_obj, "classname" ),
820 osrfHashGet( class_obj, "primarykey" )
824 obj = doFieldmapperSearch(ctx, class_obj, _p, &err);
830 jsonIterator* itr = jsonNewIterator( obj );
831 while ((cur = jsonIteratorNext( itr ))) {
833 if(!verifyObjectPCRUD(ctx, cur)) continue;
837 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
840 jsonIteratorFree(itr);
841 osrfAppRespondComplete( ctx, NULL );
844 osrfAppRespondComplete( ctx, obj );
852 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
855 osrfHash* meta = (osrfHash*) ctx->method->userData;
856 osrfHash* class = osrfHashGet( meta, "class" );
858 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
860 growing_buffer* msg = buffer_init(128);
863 "%s: %s method for type %s was passed a %s",
865 osrfHashGet(meta, "methodtype"),
866 osrfHashGet(class, "classname"),
870 char* m = buffer_release(msg);
871 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException", ctx->request, m );
879 ret = verifyObjectPCRUD( ctx, param );
887 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
888 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
889 jsonObject* auth_object = jsonNewObject(auth);
890 jsonObject* user = oilsUtilsQuickReq("open-ils.auth","open-ils.auth.session.retrieve", auth_object);
891 jsonObjectFree(auth_object);
893 if (!user->classname || strcmp(user->classname, "au")) {
895 growing_buffer* msg = buffer_init(128);
898 "%s: permacrud received a bad auth token: %s",
903 char* m = buffer_release(msg);
904 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException", ctx->request, m );
907 jsonObjectFree(user);
915 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
917 dbhandle = writehandle;
919 osrfHash* meta = (osrfHash*) ctx->method->userData;
920 osrfHash* class = osrfHashGet( meta, "class" );
921 char* method_type = strdup( osrfHashGet(meta, "methodtype") );
924 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
926 method_type = strdup("retrieve"); // search and id_list are equivelant to retrieve for this
927 } else if ( *method_type == 'u' || *method_type == 'd' ) {
928 fetch = 1; // MUST go to the db for the object for update and delete
931 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
935 // No permacrud for this method type on this class
937 growing_buffer* msg = buffer_init(128);
940 "%s: %s on class %s has no permacrud IDL entry",
942 osrfHashGet(meta, "methodtype"),
943 osrfHashGet(class, "classname")
946 char* m = buffer_release(msg);
947 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN, "osrfMethodException", ctx->request, m );
954 jsonObject* user = verifyUserPCRUD( ctx );
957 int userid = atoi( oilsFMGetString( user, "id" ) );
958 jsonObjectFree(user);
960 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
961 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
962 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
964 osrfStringArray* context_org_array = osrfNewStringArray(1);
967 char* pkey_value = NULL;
968 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
969 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions required, fetching top of the org tree" );
971 // check for perm at top of org tree
972 jsonObject* _tmp_params = jsonParseString("[{\"parent_ou\":null}]");
973 jsonObject* _list = doFieldmapperSearch(ctx, osrfHashGet( oilsIDL(), "aou" ), _tmp_params, &err);
975 jsonObject* _tree_top = jsonObjectGetIndex(_list, 0);
978 jsonObjectFree(_tmp_params);
979 jsonObjectFree(_list);
981 growing_buffer* msg = buffer_init(128);
982 OSRF_BUFFER_ADD( msg, MODULENAME );
983 OSRF_BUFFER_ADD( msg,
984 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
986 char* m = buffer_release(msg);
987 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
993 osrfStringArrayAdd( context_org_array, oilsFMGetString( _tree_top, "id" ) );
994 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", osrfStringArrayGetString(context_org_array, 0) );
996 jsonObjectFree(_tmp_params);
997 jsonObjectFree(_list);
1000 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, fetching context org ids" );
1001 char* pkey = osrfHashGet(class, "primarykey");
1002 jsonObject *param = NULL;
1004 if (obj->classname) {
1005 pkey_value = oilsFMGetString( obj, pkey );
1006 if (!fetch) param = jsonObjectClone(obj);
1007 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s", pkey_value );
1009 pkey_value = jsonObjectToSimpleString( obj );
1011 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value of %s and retrieving from the database", pkey_value );
1015 jsonObject* _tmp_params = jsonParseStringFmt("[{\"%s\":\"%s\"}]", pkey, pkey_value);
1016 jsonObject* _list = doFieldmapperSearch(
1023 param = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1025 jsonObjectFree(_tmp_params);
1026 jsonObjectFree(_list);
1030 osrfLogDebug( OSRF_LOG_MARK, "Object not found in the database with primary key %s of %s", pkey, pkey_value );
1032 growing_buffer* msg = buffer_init(128);
1035 "%s: no object found with primary key %s of %s",
1041 char* m = buffer_release(msg);
1042 osrfAppSessionStatus(
1044 OSRF_STATUS_INTERNALSERVERERROR,
1045 "osrfMethodException",
1051 if (pkey_value) free(pkey_value);
1056 if (local_context->size > 0) {
1057 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified", local_context->size);
1059 char* lcontext = NULL;
1060 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1061 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1064 "adding class-local field %s (value: %s) to the context org list",
1066 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1071 osrfStringArray* class_list;
1073 if (foreign_context) {
1074 class_list = osrfHashKeys( foreign_context );
1075 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_list->size);
1077 if (class_list->size > 0) {
1080 char* class_name = NULL;
1081 while ( (class_name = osrfStringArrayGetString(class_list, i++)) ) {
1082 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1086 "%d foreign context fields(s) specified for class %s",
1087 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1091 char* foreign_pkey = osrfHashGet(fcontext, "field");
1092 char* foreign_pkey_value = oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1094 jsonObject* _tmp_params = jsonParseStringFmt(
1095 "[{\"%s\":\"%s\"}]",
1100 jsonObject* _list = doFieldmapperSearch(
1102 osrfHashGet( oilsIDL(), class_name ),
1107 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1108 jsonObjectFree(_tmp_params);
1109 jsonObjectFree(_list);
1111 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1113 if (_fparam && jump_list) {
1116 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1117 free(foreign_pkey_value);
1119 osrfHash* foreign_link_hash = oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1121 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1122 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1124 _tmp_params = jsonParseStringFmt(
1125 "[{\"%s\":\"%s\"}]",
1130 _list = doFieldmapperSearch(
1132 osrfHashGet( oilsIDL(), osrfHashGet( foreign_link_hash, "class" ) ),
1137 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1138 jsonObjectFree(_tmp_params);
1139 jsonObjectFree(_list);
1146 growing_buffer* msg = buffer_init(128);
1149 "%s: no object found with primary key %s of %s",
1155 char* m = buffer_release(msg);
1156 osrfAppSessionStatus(
1158 OSRF_STATUS_INTERNALSERVERERROR,
1159 "osrfMethodException",
1165 osrfStringArrayFree(class_list);
1166 free(foreign_pkey_value);
1167 jsonObjectFree(param);
1172 free(foreign_pkey_value);
1175 char* foreign_field = NULL;
1176 while ( (foreign_field = osrfStringArrayGetString(osrfHashGet(fcontext,"context"), j++)) ) {
1177 osrfStringArrayAdd( context_org_array, oilsFMGetString( _fparam, foreign_field ) );
1180 "adding foreign class %s field %s (value: %s) to the context org list",
1183 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1187 jsonObjectFree(_fparam);
1190 osrfStringArrayFree(class_list);
1194 jsonObjectFree(param);
1197 char* context_org = NULL;
1201 if (permission->size == 0) {
1202 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1207 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1209 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1215 "Checking object permission [%s] for user %d on object %s (class %s) at org %d",
1219 osrfHashGet(class, "classname"),
1223 result = dbi_conn_queryf(
1225 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1228 osrfHashGet(class, "classname"),
1236 "Recieved a result for object permission [%s] for user %d on object %s (class %s) at org %d",
1240 osrfHashGet(class, "classname"),
1244 if (dbi_result_first_row(result)) {
1245 jsonObject* return_val = oilsMakeJSONFromResult( result );
1246 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1250 "Status of object permission [%s] for user %d on object %s (class %s) at org %d is %s",
1254 osrfHashGet(class, "classname"),
1259 if ( *has_perm == 't' ) OK = 1;
1260 jsonObjectFree(return_val);
1263 dbi_result_free(result);
1268 osrfLogDebug( OSRF_LOG_MARK, "Checking non-object permission [%s] for user %d at org %d", perm, userid, atoi(context_org) );
1269 result = dbi_conn_queryf(
1271 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1278 osrfLogDebug( OSRF_LOG_MARK, "Received a result for permission [%s] for user %d at org %d",
1279 perm, userid, atoi(context_org) );
1280 if ( dbi_result_first_row(result) ) {
1281 jsonObject* return_val = oilsMakeJSONFromResult( result );
1282 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1283 osrfLogDebug( OSRF_LOG_MARK, "Status of permission [%s] for user %d at org %d is [%s]",
1284 perm, userid, atoi(context_org), has_perm );
1285 if ( *has_perm == 't' ) OK = 1;
1286 jsonObjectFree(return_val);
1289 dbi_result_free(result);
1297 if (pkey_value) free(pkey_value);
1298 osrfStringArrayFree(context_org_array);
1305 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1307 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1309 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1310 jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1312 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1313 jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1316 if (!verifyObjectClass(ctx, target)) {
1321 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1323 char* trans_id = NULL;
1324 if( ctx->session && ctx->session->userData )
1325 trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
1328 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1330 osrfAppSessionStatus(
1332 OSRF_STATUS_BADREQUEST,
1333 "osrfMethodException",
1335 "No active transaction -- required for CREATE"
1341 // The following test is harmless but redundant. If a class is
1342 // readonly, we don't register a create method for it.
1343 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1344 osrfAppSessionStatus(
1346 OSRF_STATUS_BADREQUEST,
1347 "osrfMethodException",
1349 "Cannot INSERT readonly class"
1355 // Set the last_xact_id
1356 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1358 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
1359 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1362 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1364 dbhandle = writehandle;
1366 osrfHash* fields = osrfHashGet(meta, "fields");
1367 char* pkey = osrfHashGet(meta, "primarykey");
1368 char* seq = osrfHashGet(meta, "sequence");
1370 growing_buffer* table_buf = buffer_init(128);
1371 growing_buffer* col_buf = buffer_init(128);
1372 growing_buffer* val_buf = buffer_init(128);
1374 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1375 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1376 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1377 buffer_add(val_buf,"VALUES (");
1383 osrfStringArray* field_list = osrfHashKeys( fields );
1384 while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
1386 osrfHash* field = osrfHashGet( fields, field_name );
1388 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1391 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1394 if (field_object && field_object->classname) {
1395 value = oilsFMGetString(
1397 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1400 value = jsonObjectToSimpleString( field_object );
1407 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1408 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1411 buffer_add(col_buf, field_name);
1413 if (!field_object || field_object->type == JSON_NULL) {
1414 buffer_add( val_buf, "DEFAULT" );
1416 } else if ( !strcmp(get_primitive( field ), "number") ) {
1417 const char* numtype = get_datatype( field );
1418 if ( !strcmp( numtype, "INT8") ) {
1419 buffer_fadd( val_buf, "%lld", atoll(value) );
1421 } else if ( !strcmp( numtype, "INT") ) {
1422 buffer_fadd( val_buf, "%d", atoi(value) );
1424 } else if ( !strcmp( numtype, "NUMERIC") ) {
1425 buffer_fadd( val_buf, "%f", atof(value) );
1428 if ( dbi_conn_quote_string(writehandle, &value) ) {
1429 OSRF_BUFFER_ADD( val_buf, value );
1432 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1433 osrfAppSessionStatus(
1435 OSRF_STATUS_INTERNALSERVERERROR,
1436 "osrfMethodException",
1438 "Error quoting string -- please see the error log for more details"
1441 buffer_free(table_buf);
1442 buffer_free(col_buf);
1443 buffer_free(val_buf);
1454 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1455 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1457 char* table_str = buffer_release(table_buf);
1458 char* col_str = buffer_release(col_buf);
1459 char* val_str = buffer_release(val_buf);
1460 growing_buffer* sql = buffer_init(128);
1461 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1466 char* query = buffer_release(sql);
1468 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1471 dbi_result result = dbi_conn_query(writehandle, query);
1473 jsonObject* obj = NULL;
1476 obj = jsonNewObject(NULL);
1479 "%s ERROR inserting %s object using query [%s]",
1481 osrfHashGet(meta, "fieldmapper"),
1484 osrfAppSessionStatus(
1486 OSRF_STATUS_INTERNALSERVERERROR,
1487 "osrfMethodException",
1489 "INSERT error -- please see the error log for more details"
1494 char* id = oilsFMGetString(target, pkey);
1496 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1497 growing_buffer* _id = buffer_init(10);
1498 buffer_fadd(_id, "%lld", new_id);
1499 id = buffer_release(_id);
1502 // Find quietness specification, if present
1503 const char* quiet_str = NULL;
1505 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1507 quiet_str = jsonObjectGetString( quiet_obj );
1510 if( str_is_true( quiet_str ) ) { // if quietness is specified
1511 obj = jsonNewObject(id);
1515 jsonObject* fake_params = jsonNewObjectType(JSON_ARRAY);
1516 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH));
1519 jsonObjectGetIndex(fake_params, 0),
1524 jsonObject* list = doFieldmapperSearch( ctx,meta, fake_params, err);
1527 jsonObjectFree( fake_params );
1530 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1533 jsonObjectFree( list );
1534 jsonObjectFree( fake_params );
1547 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1557 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1559 const char* id = jsonObjectGetString(jsonObjectGetIndex(ctx->params, id_pos));
1560 jsonObject* order_hash = jsonObjectGetIndex(ctx->params, order_pos);
1564 "%s retrieving %s object with primary key value of %s",
1566 osrfHashGet(meta, "fieldmapper"),
1570 jsonObject* fake_params = jsonNewObjectType(JSON_ARRAY);
1571 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH));
1574 jsonObjectGetIndex(fake_params, 0),
1575 osrfHashGet(meta, "primarykey"),
1576 jsonObjectClone(jsonObjectGetIndex(ctx->params, id_pos))
1580 if (order_hash) jsonObjectPush(fake_params, jsonObjectClone(order_hash) );
1582 jsonObject* list = doFieldmapperSearch( ctx,meta, fake_params, err);
1585 jsonObjectFree( fake_params );
1589 jsonObject* obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1591 jsonObjectFree( list );
1592 jsonObjectFree( fake_params );
1595 if(!verifyObjectPCRUD(ctx, obj)) {
1596 jsonObjectFree(obj);
1599 growing_buffer* msg = buffer_init(128);
1600 OSRF_BUFFER_ADD( msg, MODULENAME );
1601 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1603 char* m = buffer_release(msg);
1604 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1615 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1616 growing_buffer* val_buf = buffer_init(32);
1617 const char* numtype = get_datatype( field );
1619 if ( !strncmp( numtype, "INT", 3 ) ) {
1620 if (value->type == JSON_NUMBER)
1621 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1622 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1624 //const char* val_str = jsonObjectGetString( value );
1625 //buffer_fadd( val_buf, "%ld", atol(val_str) );
1626 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1629 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
1630 if (value->type == JSON_NUMBER)
1631 //buffer_fadd( val_buf, "%f", jsonObjectGetNumber(value) );
1632 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1634 //const char* val_str = jsonObjectGetString( value );
1635 //buffer_fadd( val_buf, "%f", atof(val_str) );
1636 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1640 // Presumably this was really intended ot be a string, so quote it
1641 char* str = jsonObjectToSimpleString( value );
1642 if ( dbi_conn_quote_string(dbhandle, &str) ) {
1643 OSRF_BUFFER_ADD( val_buf, str );
1646 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
1648 buffer_free(val_buf);
1653 return buffer_release(val_buf);
1656 static char* searchINPredicate (const char* class, osrfHash* field,
1657 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1658 growing_buffer* sql_buf = buffer_init(32);
1664 osrfHashGet(field, "name")
1668 buffer_add(sql_buf, "IN (");
1669 } else if (!(strcasecmp(op,"not in"))) {
1670 buffer_add(sql_buf, "NOT IN (");
1672 buffer_add(sql_buf, "IN (");
1675 if (node->type == JSON_HASH) {
1676 // subquery predicate
1677 char* subpred = SELECT(
1679 jsonObjectGetKey( node, "select" ),
1680 jsonObjectGetKey( node, "from" ),
1681 jsonObjectGetKey( node, "where" ),
1682 jsonObjectGetKey( node, "having" ),
1683 jsonObjectGetKey( node, "order_by" ),
1684 jsonObjectGetKey( node, "limit" ),
1685 jsonObjectGetKey( node, "offset" ),
1690 buffer_add(sql_buf, subpred);
1693 buffer_free( sql_buf );
1697 } else if (node->type == JSON_ARRAY) {
1698 // literal value list
1699 int in_item_index = 0;
1700 int in_item_first = 1;
1701 const jsonObject* in_item;
1702 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1707 buffer_add(sql_buf, ", ");
1710 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1711 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1712 MODULENAME, json_type( in_item->type ) );
1713 buffer_free(sql_buf);
1717 // Append the literal value -- quoted if not a number
1718 if ( JSON_NUMBER == in_item->type ) {
1719 char* val = jsonNumberToDBString( field, in_item );
1720 OSRF_BUFFER_ADD( sql_buf, val );
1723 } else if ( !strcmp( get_primitive( field ), "number") ) {
1724 char* val = jsonNumberToDBString( field, in_item );
1725 OSRF_BUFFER_ADD( sql_buf, val );
1729 char* key_string = jsonObjectToSimpleString(in_item);
1730 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1731 OSRF_BUFFER_ADD( sql_buf, key_string );
1734 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1736 buffer_free(sql_buf);
1742 if( in_item_first ) {
1743 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1744 buffer_free( sql_buf );
1748 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1749 MODULENAME, json_type( node->type ) );
1750 buffer_free(sql_buf);
1754 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1756 return buffer_release(sql_buf);
1759 // Receive a JSON_ARRAY representing a function call. The first
1760 // entry in the array is the function name. The rest are parameters.
1761 static char* searchValueTransform( const jsonObject* array ) {
1763 if( array->size < 1 ) {
1764 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
1768 // Get the function name
1769 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
1770 if( func_item->type != JSON_STRING ) {
1771 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
1772 MODULENAME, json_type( func_item->type ) );
1776 growing_buffer* sql_buf = buffer_init(32);
1778 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1779 OSRF_BUFFER_ADD( sql_buf, "( " );
1781 // Get the parameters
1782 int func_item_index = 1; // We already grabbed the zeroth entry
1783 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1785 // Add a separator comma, if we need one
1786 if( func_item_index > 2 )
1787 buffer_add( sql_buf, ", " );
1789 // Add the current parameter
1790 if (func_item->type == JSON_NULL) {
1791 buffer_add( sql_buf, "NULL" );
1793 char* val = jsonObjectToSimpleString(func_item);
1794 if ( dbi_conn_quote_string(dbhandle, &val) ) {
1795 OSRF_BUFFER_ADD( sql_buf, val );
1798 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1799 buffer_free(sql_buf);
1806 buffer_add( sql_buf, " )" );
1808 return buffer_release(sql_buf);
1811 static char* searchFunctionPredicate (const char* class, osrfHash* field,
1812 const jsonObject* node, const char* op) {
1814 if( ! is_good_operator( op ) ) {
1815 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
1819 char* val = searchValueTransform(node);
1823 growing_buffer* sql_buf = buffer_init(32);
1828 osrfHashGet(field, "name"),
1835 return buffer_release(sql_buf);
1838 // class is a class name
1839 // field is a field definition as stored in the IDL
1840 // node comes from the method parameter, and may represent an entry in the SELECT list
1841 static char* searchFieldTransform (const char* class, osrfHash* field, const jsonObject* node) {
1842 growing_buffer* sql_buf = buffer_init(32);
1844 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
1845 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
1847 if(transform_subcolumn) {
1848 if( ! is_identifier( transform_subcolumn ) ) {
1849 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
1850 MODULENAME, transform_subcolumn );
1851 buffer_free( sql_buf );
1854 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
1857 if (field_transform) {
1859 if( ! is_identifier( field_transform ) ) {
1860 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
1861 MODULENAME, field_transform );
1862 buffer_free( sql_buf );
1866 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class, osrfHashGet(field, "name"));
1867 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
1870 if( array->type != JSON_ARRAY ) {
1871 osrfLogError( OSRF_LOG_MARK,
1872 "%s: Expected JSON_ARRAY for function params; found %s",
1873 MODULENAME, json_type( array->type ) );
1874 buffer_free( sql_buf );
1877 int func_item_index = 0;
1878 jsonObject* func_item;
1879 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1881 char* val = jsonObjectToSimpleString(func_item);
1884 buffer_add( sql_buf, ",NULL" );
1885 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
1886 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
1887 OSRF_BUFFER_ADD( sql_buf, val );
1889 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1891 buffer_free(sql_buf);
1898 buffer_add( sql_buf, " )" );
1901 buffer_fadd( sql_buf, "\"%s\".%s", class, osrfHashGet(field, "name"));
1904 if (transform_subcolumn)
1905 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
1907 return buffer_release(sql_buf);
1910 static char* searchFieldTransformPredicate (const char* class, osrfHash* field,
1911 const jsonObject* node, const char* op ) {
1913 if( ! is_good_operator( op ) ) {
1914 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
1918 char* field_transform = searchFieldTransform( class, field, node );
1919 if( ! field_transform )
1922 int extra_parens = 0; // boolean
1924 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
1925 if ( ! value_obj ) {
1926 value = searchWHERE( node, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1928 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
1929 free(field_transform);
1933 } else if ( value_obj->type == JSON_ARRAY ) {
1934 value = searchValueTransform( value_obj );
1936 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
1937 free( field_transform );
1940 } else if ( value_obj->type == JSON_HASH ) {
1941 value = searchWHERE( value_obj, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1943 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
1944 free(field_transform);
1948 } else if ( value_obj->type == JSON_NUMBER ) {
1949 value = jsonNumberToDBString( field, value_obj );
1950 } else if ( value_obj->type == JSON_NULL ) {
1951 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
1952 free(field_transform);
1954 } else if ( value_obj->type == JSON_BOOL ) {
1955 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
1956 free(field_transform);
1959 if ( !strcmp( get_primitive( field ), "number") ) {
1960 value = jsonNumberToDBString( field, value_obj );
1962 value = jsonObjectToSimpleString( value_obj );
1963 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
1964 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
1966 free(field_transform);
1972 const char* left_parens = "";
1973 const char* right_parens = "";
1975 if( extra_parens ) {
1980 growing_buffer* sql_buf = buffer_init(32);
1984 "%s%s %s %s %s %s%s",
1995 free(field_transform);
1997 return buffer_release(sql_buf);
2000 static char* searchSimplePredicate (const char* op, const char* class,
2001 osrfHash* field, const jsonObject* node) {
2003 if( ! is_good_operator( op ) ) {
2004 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2010 // Get the value to which we are comparing the specified column
2011 if (node->type != JSON_NULL) {
2012 if ( node->type == JSON_NUMBER ) {
2013 val = jsonNumberToDBString( field, node );
2014 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2015 val = jsonNumberToDBString( field, node );
2017 val = jsonObjectToSimpleString(node);
2022 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2023 // Value is not numeric; enclose it in quotes
2024 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2025 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2031 // Compare to a null value
2032 val = strdup( "NULL" );
2033 if (strcmp( op, "=" ))
2039 growing_buffer* sql_buf = buffer_init(32);
2040 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class, osrfHashGet(field, "name"), op, val );
2041 char* pred = buffer_release( sql_buf );
2048 static char* searchBETWEENPredicate (const char* class, osrfHash* field, const jsonObject* node) {
2050 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2051 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2053 if( NULL == y_node ) {
2054 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2057 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2058 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2065 if ( !strcmp( get_primitive( field ), "number") ) {
2066 x_string = jsonNumberToDBString(field, x_node);
2067 y_string = jsonNumberToDBString(field, y_node);
2070 x_string = jsonObjectToSimpleString(x_node);
2071 y_string = jsonObjectToSimpleString(y_node);
2072 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2073 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2074 MODULENAME, x_string, y_string);
2081 growing_buffer* sql_buf = buffer_init(32);
2082 buffer_fadd( sql_buf, "%s BETWEEN %s AND %s", osrfHashGet(field, "name"), x_string, y_string );
2086 return buffer_release(sql_buf);
2089 static char* searchPredicate ( const char* class, osrfHash* field,
2090 jsonObject* node, osrfMethodContext* ctx ) {
2093 if (node->type == JSON_ARRAY) { // equality IN search
2094 pred = searchINPredicate( class, field, node, NULL, ctx );
2095 } else if (node->type == JSON_HASH) { // other search
2096 jsonIterator* pred_itr = jsonNewIterator( node );
2097 if( !jsonIteratorHasNext( pred_itr ) ) {
2098 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2099 MODULENAME, osrfHashGet(field, "name") );
2101 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2103 // Verify that there are no additional predicates
2104 if( jsonIteratorHasNext( pred_itr ) ) {
2105 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2106 MODULENAME, osrfHashGet(field, "name") );
2107 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2108 pred = searchBETWEENPredicate( class, field, pred_node );
2109 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2110 pred = searchINPredicate( class, field, pred_node, pred_itr->key, ctx );
2111 else if ( pred_node->type == JSON_ARRAY )
2112 pred = searchFunctionPredicate( class, field, pred_node, pred_itr->key );
2113 else if ( pred_node->type == JSON_HASH )
2114 pred = searchFieldTransformPredicate( class, field, pred_node, pred_itr->key );
2116 pred = searchSimplePredicate( pred_itr->key, class, field, pred_node );
2118 jsonIteratorFree(pred_itr);
2120 } else if (node->type == JSON_NULL) { // IS NULL search
2121 growing_buffer* _p = buffer_init(64);
2124 "\"%s\".%s IS NULL",
2126 osrfHashGet(field, "name")
2128 pred = buffer_release(_p);
2129 } else { // equality search
2130 pred = searchSimplePredicate( "=", class, field, node );
2149 field : call_number,
2165 static char* searchJOIN ( const jsonObject* join_hash, osrfHash* leftmeta ) {
2167 const jsonObject* working_hash;
2168 jsonObject* freeable_hash = NULL;
2170 if (join_hash->type == JSON_STRING) {
2171 // create a wrapper around a copy of the original
2172 const char* _tmp = jsonObjectGetString( join_hash );
2173 freeable_hash = jsonNewObjectType(JSON_HASH);
2174 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2175 working_hash = freeable_hash;
2178 if( join_hash->type != JSON_HASH ) {
2181 "%s: JOIN failed; expected JSON object type not found",
2186 working_hash = join_hash;
2189 growing_buffer* join_buf = buffer_init(128);
2190 const char* leftclass = osrfHashGet(leftmeta, "classname");
2192 jsonObject* snode = NULL;
2193 jsonIterator* search_itr = jsonNewIterator( working_hash );
2195 while ( (snode = jsonIteratorNext( search_itr )) ) {
2196 const char* class = search_itr->key;
2197 osrfHash* idlClass = osrfHashGet( oilsIDL(), class );
2201 "%s: JOIN failed. No class \"%s\" defined in IDL",
2205 jsonIteratorFree( search_itr );
2206 buffer_free( join_buf );
2208 jsonObjectFree( freeable_hash );
2212 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2213 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2215 if (field && !fkey) {
2216 fkey = (const char*)oilsIDLFindPath("/%s/links/%s/key", class, field);
2220 "%s: JOIN failed. No link defined from %s.%s to %s",
2226 buffer_free(join_buf);
2228 jsonObjectFree(freeable_hash);
2229 jsonIteratorFree(search_itr);
2233 } else if (!field && fkey) {
2234 field = (const char*)oilsIDLFindPath("/%s/links/%s/key", leftclass, fkey );
2238 "%s: JOIN failed. No link defined from %s.%s to %s",
2244 buffer_free(join_buf);
2246 jsonObjectFree(freeable_hash);
2247 jsonIteratorFree(search_itr);
2251 } else if (!field && !fkey) {
2252 osrfHash* _links = oilsIDL_links( leftclass );
2254 // For each link defined for the left class:
2255 // see if the link references the joined class
2256 osrfHashIterator* itr = osrfNewHashIterator( _links );
2257 osrfHash* curr_link = NULL;
2258 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2259 const char* other_class = osrfHashGet( curr_link, "class" );
2260 if( other_class && !strcmp( other_class, class ) ) {
2262 // In the IDL, the parent class doesn't know then names of the child
2263 // columns that are pointing to it, so don't use that end of the link
2264 const char* reltype = osrfHashGet( curr_link, "reltype" );
2265 if( reltype && strcmp( reltype, "has_many" ) ) {
2266 // Found a link between the classes
2267 fkey = osrfHashIteratorKey( itr );
2268 field = osrfHashGet( curr_link, "key" );
2273 osrfHashIteratorFree( itr );
2275 if (!field || !fkey) {
2276 // Do another such search, with the classes reversed
2277 _links = oilsIDL_links( class );
2279 // For each link defined for the joined class:
2280 // see if the link references the left class
2281 osrfHashIterator* itr = osrfNewHashIterator( _links );
2282 osrfHash* curr_link = NULL;
2283 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2284 const char* other_class = osrfHashGet( curr_link, "class" );
2285 if( other_class && !strcmp( other_class, leftclass ) ) {
2287 // In the IDL, the parent class doesn't know then names of the child
2288 // columns that are pointing to it, so don't use that end of the link
2289 const char* reltype = osrfHashGet( curr_link, "reltype" );
2290 if( reltype && strcmp( reltype, "has_many" ) ) {
2291 // Found a link between the classes
2292 field = osrfHashIteratorKey( itr );
2293 fkey = osrfHashGet( curr_link, "key" );
2298 osrfHashIteratorFree( itr );
2301 if (!field || !fkey) {
2304 "%s: JOIN failed. No link defined between %s and %s",
2309 buffer_free(join_buf);
2311 jsonObjectFree(freeable_hash);
2312 jsonIteratorFree(search_itr);
2318 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2320 if ( !strcasecmp(type,"left") ) {
2321 buffer_add(join_buf, " LEFT JOIN");
2322 } else if ( !strcasecmp(type,"right") ) {
2323 buffer_add(join_buf, " RIGHT JOIN");
2324 } else if ( !strcasecmp(type,"full") ) {
2325 buffer_add(join_buf, " FULL JOIN");
2327 buffer_add(join_buf, " INNER JOIN");
2330 buffer_add(join_buf, " INNER JOIN");
2333 char* table = getSourceDefinition(idlClass);
2335 jsonIteratorFree( search_itr );
2336 buffer_free( join_buf );
2338 jsonObjectFree( freeable_hash );
2342 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2343 table, class, class, field, leftclass, fkey);
2346 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2348 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2349 if ( filter_op && !strcasecmp("or",filter_op) ) {
2350 buffer_add( join_buf, " OR " );
2352 buffer_add( join_buf, " AND " );
2355 char* jpred = searchWHERE( filter, idlClass, AND_OP_JOIN, NULL );
2357 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2358 OSRF_BUFFER_ADD( join_buf, jpred );
2363 "%s: JOIN failed. Invalid conditional expression.",
2366 jsonIteratorFree( search_itr );
2367 buffer_free( join_buf );
2369 jsonObjectFree( freeable_hash );
2374 buffer_add(join_buf, " ) ");
2376 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2378 char* jpred = searchJOIN( join_filter, idlClass );
2379 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2380 OSRF_BUFFER_ADD( join_buf, jpred );
2386 jsonObjectFree(freeable_hash);
2387 jsonIteratorFree(search_itr);
2389 return buffer_release(join_buf);
2394 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2395 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2396 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2398 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2400 search_hash is the JSON expression of the conditions.
2401 meta is the class definition from the IDL, for the relevant table.
2402 opjoin_type indicates whether multiple conditions, if present, should be
2403 connected by AND or OR.
2404 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2405 to pass it to other functions -- and all they do with it is to use the session
2406 and request members to send error messages back to the client.
2410 static char* searchWHERE ( const jsonObject* search_hash, osrfHash* meta, int opjoin_type, osrfMethodContext* ctx ) {
2414 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2422 growing_buffer* sql_buf = buffer_init(128);
2424 jsonObject* node = NULL;
2427 if ( search_hash->type == JSON_ARRAY ) {
2428 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2429 jsonIterator* search_itr = jsonNewIterator( search_hash );
2430 if( !jsonIteratorHasNext( search_itr ) ) {
2433 "%s: Invalid predicate structure: empty JSON array",
2436 jsonIteratorFree( search_itr );
2437 buffer_free( sql_buf );
2441 while ( (node = jsonIteratorNext( search_itr )) ) {
2445 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2446 else buffer_add(sql_buf, " AND ");
2449 char* subpred = searchWHERE( node, meta, opjoin_type, ctx );
2451 buffer_fadd(sql_buf, "( %s )", subpred);
2454 jsonIteratorFree( search_itr );
2455 buffer_free( sql_buf );
2459 jsonIteratorFree(search_itr);
2461 } else if ( search_hash->type == JSON_HASH ) {
2462 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2463 jsonIterator* search_itr = jsonNewIterator( search_hash );
2464 if( !jsonIteratorHasNext( search_itr ) ) {
2467 "%s: Invalid predicate structure: empty JSON object",
2470 jsonIteratorFree( search_itr );
2471 buffer_free( sql_buf );
2475 while ( (node = jsonIteratorNext( search_itr )) ) {
2480 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2481 else buffer_add(sql_buf, " AND ");
2484 if ( '+' == search_itr->key[ 0 ] ) {
2485 if ( node->type == JSON_STRING ) {
2486 // Intended purpose; to allow reference to a Boolean column
2488 // Verify that the class alias is not empty
2489 if( '\0' == search_itr->key[ 1 ] ) {
2492 "%s: Table alias is empty",
2495 jsonIteratorFree( search_itr );
2496 buffer_free( sql_buf );
2500 // Verify that the string looks like an identifier.
2501 const char* subpred = jsonObjectGetString( node );
2502 if( ! is_identifier( subpred ) ) {
2505 "%s: Invalid boolean identifier in WHERE clause: \"%s\"",
2509 jsonIteratorFree( search_itr );
2510 buffer_free( sql_buf );
2514 buffer_fadd(sql_buf, " \"%s\".%s ", search_itr->key + 1, subpred);
2516 char* subpred = searchWHERE( node, osrfHashGet( oilsIDL(), search_itr->key + 1 ), AND_OP_JOIN, ctx );
2518 buffer_fadd(sql_buf, "( %s )", subpred);
2521 jsonIteratorFree( search_itr );
2522 buffer_free( sql_buf );
2526 } else if ( !strcasecmp("-or",search_itr->key) ) {
2527 char* subpred = searchWHERE( node, meta, OR_OP_JOIN, ctx );
2529 buffer_fadd(sql_buf, "( %s )", subpred);
2532 buffer_free( sql_buf );
2535 } else if ( !strcasecmp("-and",search_itr->key) ) {
2536 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2538 buffer_fadd(sql_buf, "( %s )", subpred);
2541 buffer_free( sql_buf );
2544 } else if ( !strcasecmp("-not",search_itr->key) ) {
2545 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2547 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2550 buffer_free( sql_buf );
2553 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2554 char* subpred = SELECT(
2556 jsonObjectGetKey( node, "select" ),
2557 jsonObjectGetKey( node, "from" ),
2558 jsonObjectGetKey( node, "where" ),
2559 jsonObjectGetKey( node, "having" ),
2560 jsonObjectGetKey( node, "order_by" ),
2561 jsonObjectGetKey( node, "limit" ),
2562 jsonObjectGetKey( node, "offset" ),
2567 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2570 buffer_free( sql_buf );
2573 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2574 char* subpred = SELECT(
2576 jsonObjectGetKey( node, "select" ),
2577 jsonObjectGetKey( node, "from" ),
2578 jsonObjectGetKey( node, "where" ),
2579 jsonObjectGetKey( node, "having" ),
2580 jsonObjectGetKey( node, "order_by" ),
2581 jsonObjectGetKey( node, "limit" ),
2582 jsonObjectGetKey( node, "offset" ),
2587 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2590 buffer_free( sql_buf );
2596 char* class = osrfHashGet(meta, "classname");
2597 osrfHash* fields = osrfHashGet(meta, "fields");
2598 osrfHash* field = osrfHashGet( fields, search_itr->key );
2602 char* table = getSourceDefinition(meta);
2604 table = strdup( "(?)" );
2607 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2613 buffer_free(sql_buf);
2615 jsonIteratorFree(search_itr);
2619 char* subpred = searchPredicate( class, field, node, ctx );
2621 buffer_add( sql_buf, subpred );
2624 buffer_free(sql_buf);
2625 jsonIteratorFree(search_itr);
2630 jsonIteratorFree(search_itr);
2633 // ERROR ... only hash and array allowed at this level
2634 char* predicate_string = jsonObjectToJSON( search_hash );
2637 "%s: Invalid predicate structure: %s",
2641 buffer_free(sql_buf);
2642 free(predicate_string);
2646 return buffer_release(sql_buf);
2650 /* method context */ osrfMethodContext* ctx,
2652 /* SELECT */ jsonObject* selhash,
2653 /* FROM */ jsonObject* join_hash,
2654 /* WHERE */ jsonObject* search_hash,
2655 /* HAVING */ jsonObject* having_hash,
2656 /* ORDER BY */ jsonObject* order_hash,
2657 /* LIMIT */ jsonObject* limit,
2658 /* OFFSET */ jsonObject* offset,
2659 /* flags */ int flags
2661 const char* locale = osrf_message_get_last_locale();
2663 // in case we don't get a select list
2664 jsonObject* defaultselhash = NULL;
2666 // general tmp objects
2667 const jsonObject* tmp_const;
2668 jsonObject* selclass = NULL;
2669 jsonObject* selfield = NULL;
2670 jsonObject* snode = NULL;
2671 jsonObject* onode = NULL;
2673 char* string = NULL;
2674 int from_function = 0;
2679 // the core search class
2680 char* core_class = NULL;
2682 // metadata about the core search class
2683 osrfHash* core_meta = NULL;
2685 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale);
2687 // punt if there's no core class
2688 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2691 "%s: FROM clause is missing or empty",
2695 osrfAppSessionStatus(
2697 OSRF_STATUS_INTERNALSERVERERROR,
2698 "osrfMethodException",
2700 "FROM clause is missing or empty in JSON query"
2705 // get the core class -- the only key of the top level FROM clause, or a string
2706 if (join_hash->type == JSON_HASH) {
2707 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2708 snode = jsonIteratorNext( tmp_itr );
2710 core_class = strdup( tmp_itr->key );
2713 jsonObject* extra = jsonIteratorNext( tmp_itr );
2715 jsonIteratorFree( tmp_itr );
2718 // There shouldn't be more than one entry in join_hash
2722 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2726 osrfAppSessionStatus(
2728 OSRF_STATUS_INTERNALSERVERERROR,
2729 "osrfMethodException",
2731 "Malformed FROM clause in JSON query"
2734 return NULL; // Malformed join_hash; extra entry
2736 } else if (join_hash->type == JSON_ARRAY) {
2738 core_class = jsonObjectToSimpleString( jsonObjectGetIndex(join_hash, 0) );
2741 } else if (join_hash->type == JSON_STRING) {
2742 core_class = jsonObjectToSimpleString( join_hash );
2748 "%s: FROM clause is unexpected JSON type: %s",
2750 json_type( join_hash->type )
2753 osrfAppSessionStatus(
2755 OSRF_STATUS_INTERNALSERVERERROR,
2756 "osrfMethodException",
2758 "Ill-formed FROM clause in JSON query"
2764 if (!from_function) {
2765 // Get the IDL class definition for the core class
2766 core_meta = osrfHashGet( oilsIDL(), core_class );
2767 if( !core_meta ) { // Didn't find it?
2770 "%s: SELECT clause references undefined class: \"%s\"",
2775 osrfAppSessionStatus(
2777 OSRF_STATUS_INTERNALSERVERERROR,
2778 "osrfMethodException",
2780 "SELECT clause references undefined class in JSON query"
2786 // Make sure the class isn't virtual
2787 if( str_is_true( osrfHashGet( core_meta, "virtual" ) ) ) {
2790 "%s: Core class is virtual: \"%s\"",
2795 osrfAppSessionStatus(
2797 OSRF_STATUS_INTERNALSERVERERROR,
2798 "osrfMethodException",
2800 "FROM clause references virtual class in JSON query"
2807 // if the select list is empty, or the core class field list is '*',
2808 // build the default select list ...
2810 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
2811 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2812 } else if( selhash->type != JSON_HASH ) {
2815 "%s: Expected JSON_HASH for SELECT clause; found %s",
2817 json_type( selhash->type )
2821 osrfAppSessionStatus(
2823 OSRF_STATUS_INTERNALSERVERERROR,
2824 "osrfMethodException",
2826 "Malformed SELECT clause in JSON query"
2830 } else if ( (tmp_const = jsonObjectGetKeyConst( selhash, core_class )) && tmp_const->type == JSON_STRING ) {
2831 const char* _x = jsonObjectGetString( tmp_const );
2832 if (!strncmp( "*", _x, 1 )) {
2833 jsonObjectRemoveKey( selhash, core_class );
2834 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2839 growing_buffer* sql_buf = buffer_init(128);
2841 // temp buffer for the SELECT list
2842 growing_buffer* select_buf = buffer_init(128);
2843 growing_buffer* order_buf = buffer_init(128);
2844 growing_buffer* group_buf = buffer_init(128);
2845 growing_buffer* having_buf = buffer_init(128);
2847 int aggregate_found = 0; // boolean
2849 // Build a select list
2850 if(from_function) // From a function we select everything
2851 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
2854 // If we need to build a default list, prepare to do so
2855 jsonObject* _tmp = jsonObjectGetKey( selhash, core_class );
2856 if ( _tmp && !_tmp->size ) {
2858 osrfHash* core_fields = osrfHashGet( core_meta, "fields" );
2860 osrfHashIterator* field_itr = osrfNewHashIterator( core_fields );
2861 osrfHash* field_def;
2862 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2863 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2864 // This field is not virtual, so add it to the list
2865 jsonObjectPush( _tmp, jsonNewObject( osrfHashIteratorKey( field_itr ) ) );
2868 osrfHashIteratorFree( field_itr );
2871 // Now build the actual select list
2875 jsonIterator* selclass_itr = jsonNewIterator( selhash );
2876 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
2878 // Make sure the class is defined in the IDL
2879 const char* cname = selclass_itr->key;
2880 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
2884 "%s: Selected class \"%s\" not defined in IDL",
2890 osrfAppSessionStatus(
2892 OSRF_STATUS_INTERNALSERVERERROR,
2893 "osrfMethodException",
2895 "Selected class is not defined"
2897 jsonIteratorFree( selclass_itr );
2898 buffer_free( sql_buf );
2899 buffer_free( select_buf );
2900 buffer_free( order_buf );
2901 buffer_free( group_buf );
2902 buffer_free( having_buf );
2903 if( defaultselhash ) jsonObjectFree( defaultselhash );
2908 // Make sure the target relation is in the join tree.
2910 // At this point join_hash is a step down from the join_hash we
2911 // received as a parameter. If the original was a JSON_STRING,
2912 // then json_hash is now NULL. If the original was a JSON_HASH,
2913 // then json_hash is now the first (and only) entry in it,
2914 // denoting the core class. We've already excluded the
2915 // possibility that the original was a JSON_ARRAY, because in
2916 // that case from_function would be non-NULL, and we wouldn't
2919 int class_in_from_clause; // boolean
2921 if ( ! strcmp( core_class, cname ))
2922 // This is the core class -- no problem
2923 class_in_from_clause = 1;
2926 // There's only one class in the FROM clause, and this isn't it
2927 class_in_from_clause = 0;
2928 else if (join_hash->type == JSON_STRING) {
2929 // There's only one class in the FROM clause
2930 const char* str = jsonObjectGetString(join_hash);
2931 if ( strcmp( str, cname ) )
2932 class_in_from_clause = 0; // This isn't it
2934 class_in_from_clause = 1; // This is it
2936 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", cname);
2937 if ( 0 == found->size )
2938 class_in_from_clause = 0; // Nowhere in the join tree
2940 class_in_from_clause = 1; // Found it
2941 jsonObjectFree( found );
2945 // If the class isn't in the FROM clause, bail out
2946 if( ! class_in_from_clause ) {
2949 "%s: SELECT clause references class not in FROM clause: \"%s\"",
2954 osrfAppSessionStatus(
2956 OSRF_STATUS_INTERNALSERVERERROR,
2957 "osrfMethodException",
2959 "Selected class not in FROM clause in JSON query"
2961 jsonIteratorFree( selclass_itr );
2962 buffer_free( sql_buf );
2963 buffer_free( select_buf );
2964 buffer_free( order_buf );
2965 buffer_free( group_buf );
2966 buffer_free( having_buf );
2967 if( defaultselhash ) jsonObjectFree( defaultselhash );
2972 // Look up some attributes of the current class, so that we
2973 // don't have to look them up again for each field
2974 osrfHash* class_field_set = osrfHashGet( idlClass, "fields" );
2975 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
2976 const char* class_tname = osrfHashGet( idlClass, "tablename" );
2978 // stitch together the column list ...
2979 jsonIterator* select_itr = jsonNewIterator( selclass );
2980 while ( (selfield = jsonIteratorNext( select_itr )) ) { // for each SELECT column
2982 // If we need a separator comma, add one
2986 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
2989 // ... if it's a string, just toss it on the pile
2990 if (selfield->type == JSON_STRING) {
2992 // Look up the field in the IDL
2993 const char* col_name = jsonObjectGetString( selfield );
2994 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
2996 // No such field in current class
2999 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3005 osrfAppSessionStatus(
3007 OSRF_STATUS_INTERNALSERVERERROR,
3008 "osrfMethodException",
3010 "Selected column not defined in JSON query"
3012 jsonIteratorFree( select_itr );
3013 jsonIteratorFree( selclass_itr );
3014 buffer_free( sql_buf );
3015 buffer_free( select_buf );
3016 buffer_free( order_buf );
3017 buffer_free( group_buf );
3018 buffer_free( having_buf );
3019 if( defaultselhash ) jsonObjectFree( defaultselhash );
3022 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3023 // Virtual field not allowed
3026 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3032 osrfAppSessionStatus(
3034 OSRF_STATUS_INTERNALSERVERERROR,
3035 "osrfMethodException",
3037 "Selected column may not be virtual in JSON query"
3039 jsonIteratorFree( select_itr );
3040 jsonIteratorFree( selclass_itr );
3041 buffer_free( sql_buf );
3042 buffer_free( select_buf );
3043 buffer_free( order_buf );
3044 buffer_free( group_buf );
3045 buffer_free( having_buf );
3046 if( defaultselhash ) jsonObjectFree( defaultselhash );
3053 if (flags & DISABLE_I18N)
3056 i18n = osrfHashGet(field_def, "i18n");
3058 if( str_is_true( i18n ) ) {
3059 buffer_fadd( select_buf,
3060 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3061 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3063 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3066 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3069 // ... but it could be an object, in which case we check for a Field Transform
3070 } else if (selfield->type == JSON_HASH) {
3072 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3074 // Get the field definition from the IDL
3075 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3077 // No such field in current class
3080 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3086 osrfAppSessionStatus(
3088 OSRF_STATUS_INTERNALSERVERERROR,
3089 "osrfMethodException",
3091 "Selected column is not defined in JSON query"
3093 jsonIteratorFree( select_itr );
3094 jsonIteratorFree( selclass_itr );
3095 buffer_free( sql_buf );
3096 buffer_free( select_buf );
3097 buffer_free( order_buf );
3098 buffer_free( group_buf );
3099 buffer_free( having_buf );
3100 if( defaultselhash ) jsonObjectFree( defaultselhash );
3103 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3104 // No such field in current class
3107 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3113 osrfAppSessionStatus(
3115 OSRF_STATUS_INTERNALSERVERERROR,
3116 "osrfMethodException",
3118 "Selected column is virtual in JSON query"
3120 jsonIteratorFree( select_itr );
3121 jsonIteratorFree( selclass_itr );
3122 buffer_free( sql_buf );
3123 buffer_free( select_buf );
3124 buffer_free( order_buf );
3125 buffer_free( group_buf );
3126 buffer_free( having_buf );
3127 if( defaultselhash ) jsonObjectFree( defaultselhash );
3132 // Decide what to use as a column alias
3134 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3135 _alias = jsonObjectGetString( tmp_const );
3136 } else { // Use field name as the alias
3140 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3141 char* transform_str = searchFieldTransform(cname, field_def, selfield);
3142 if( transform_str ) {
3143 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3144 free(transform_str);
3147 osrfAppSessionStatus(
3149 OSRF_STATUS_INTERNALSERVERERROR,
3150 "osrfMethodException",
3152 "Unable to generate transform function in JSON query"
3154 jsonIteratorFree( select_itr );
3155 jsonIteratorFree( selclass_itr );
3156 buffer_free( sql_buf );
3157 buffer_free( select_buf );
3158 buffer_free( order_buf );
3159 buffer_free( group_buf );
3160 buffer_free( having_buf );
3161 if( defaultselhash ) jsonObjectFree( defaultselhash );
3169 if (flags & DISABLE_I18N)
3172 i18n = osrfHashGet(field_def, "i18n");
3174 if( str_is_true( i18n ) ) {
3175 buffer_fadd( select_buf,
3176 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3177 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3179 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3182 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3189 "%s: Selected item is unexpected JSON type: %s",
3191 json_type( selfield->type )
3194 osrfAppSessionStatus(
3196 OSRF_STATUS_INTERNALSERVERERROR,
3197 "osrfMethodException",
3199 "Ill-formed SELECT item in JSON query"
3201 jsonIteratorFree( select_itr );
3202 jsonIteratorFree( selclass_itr );
3203 buffer_free( sql_buf );
3204 buffer_free( select_buf );
3205 buffer_free( order_buf );
3206 buffer_free( group_buf );
3207 buffer_free( having_buf );
3208 if( defaultselhash ) jsonObjectFree( defaultselhash );
3213 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3214 if( obj_is_true( agg_obj ) )
3215 aggregate_found = 1;
3217 // Append a comma (except for the first one)
3218 // and add the column to a GROUP BY clause
3222 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3224 buffer_fadd(group_buf, " %d", sel_pos);
3228 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3230 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3231 if ( ! obj_is_true( aggregate_obj ) ) {
3235 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3238 buffer_fadd(group_buf, " %d", sel_pos);
3241 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3245 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3248 _column = searchFieldTransform(cname, field, selfield);
3249 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3250 OSRF_BUFFER_ADD(group_buf, _column);
3251 _column = searchFieldTransform(cname, field, selfield);
3258 } // end while -- iterating across SELECT columns
3260 jsonIteratorFree(select_itr);
3261 } // end while -- iterating across classes
3263 jsonIteratorFree(selclass_itr);
3267 char* col_list = buffer_release(select_buf);
3269 if (from_function) table = searchValueTransform(join_hash);
3270 else table = getSourceDefinition(core_meta);
3274 osrfAppSessionStatus(
3276 OSRF_STATUS_INTERNALSERVERERROR,
3277 "osrfMethodException",
3279 "Unable to identify table for core class"
3282 buffer_free( sql_buf );
3283 buffer_free( order_buf );
3284 buffer_free( group_buf );
3285 buffer_free( having_buf );
3286 if( defaultselhash ) jsonObjectFree( defaultselhash );
3291 // Put it all together
3292 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3296 if (!from_function) {
3297 // Now, walk the join tree and add that clause
3299 char* join_clause = searchJOIN( join_hash, core_meta );
3301 buffer_add(sql_buf, join_clause);
3305 osrfAppSessionStatus(
3307 OSRF_STATUS_INTERNALSERVERERROR,
3308 "osrfMethodException",
3310 "Unable to construct JOIN clause(s)"
3312 buffer_free( sql_buf );
3313 buffer_free( order_buf );
3314 buffer_free( group_buf );
3315 buffer_free( having_buf );
3316 if( defaultselhash ) jsonObjectFree( defaultselhash );
3322 // Build a WHERE clause, if there is one
3323 if ( search_hash ) {
3324 buffer_add(sql_buf, " WHERE ");
3326 // and it's on the WHERE clause
3327 char* pred = searchWHERE( search_hash, core_meta, AND_OP_JOIN, ctx );
3330 buffer_add(sql_buf, pred);
3334 osrfAppSessionStatus(
3336 OSRF_STATUS_INTERNALSERVERERROR,
3337 "osrfMethodException",
3339 "Severe query error in WHERE predicate -- see error log for more details"
3343 buffer_free(having_buf);
3344 buffer_free(group_buf);
3345 buffer_free(order_buf);
3346 buffer_free(sql_buf);
3347 if (defaultselhash) jsonObjectFree(defaultselhash);
3352 // Build a HAVING clause, if there is one
3353 if ( having_hash ) {
3354 buffer_add(sql_buf, " HAVING ");
3356 // and it's on the the WHERE clause
3357 char* pred = searchWHERE( having_hash, core_meta, AND_OP_JOIN, ctx );
3360 buffer_add(sql_buf, pred);
3364 osrfAppSessionStatus(
3366 OSRF_STATUS_INTERNALSERVERERROR,
3367 "osrfMethodException",
3369 "Severe query error in HAVING predicate -- see error log for more details"
3373 buffer_free(having_buf);
3374 buffer_free(group_buf);
3375 buffer_free(order_buf);
3376 buffer_free(sql_buf);
3377 if (defaultselhash) jsonObjectFree(defaultselhash);
3382 // Build an ORDER BY clause, if there is one
3384 jsonIterator* class_itr = jsonNewIterator( order_hash );
3385 while ( (snode = jsonIteratorNext( class_itr )) ) {
3387 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
3390 if ( snode->type == JSON_HASH ) {
3392 jsonIterator* order_itr = jsonNewIterator( snode );
3393 while ( (onode = jsonIteratorNext( order_itr )) ) {
3395 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ))
3398 const char* direction = NULL;
3399 if ( onode->type == JSON_HASH ) {
3400 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3401 string = searchFieldTransform(
3403 oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ),
3407 osrfAppSessionStatus(
3409 OSRF_STATUS_INTERNALSERVERERROR,
3410 "osrfMethodException",
3412 "Severe query error in ORDER BY clause -- see error log for more details"
3414 jsonIteratorFree( order_itr );
3415 jsonIteratorFree( class_itr );
3417 buffer_free(having_buf);
3418 buffer_free(group_buf);
3419 buffer_free(order_buf);
3420 buffer_free(sql_buf);
3421 if (defaultselhash) jsonObjectFree(defaultselhash);
3425 growing_buffer* field_buf = buffer_init(16);
3426 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3427 string = buffer_release(field_buf);
3430 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3431 const char* dir = jsonObjectGetString(tmp_const);
3432 if (!strncasecmp(dir, "d", 1)) {
3433 direction = " DESC";
3440 string = strdup(order_itr->key);
3441 const char* dir = jsonObjectGetString(onode);
3442 if (!strncasecmp(dir, "d", 1)) {
3443 direction = " DESC";
3452 buffer_add(order_buf, ", ");
3455 buffer_add(order_buf, string);
3459 buffer_add(order_buf, direction);
3463 // jsonIteratorFree(order_itr);
3465 } else if ( snode->type == JSON_ARRAY ) {
3467 jsonIterator* order_itr = jsonNewIterator( snode );
3468 while ( (onode = jsonIteratorNext( order_itr )) ) {
3470 const char* _f = jsonObjectGetString( onode );
3472 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, _f))
3478 buffer_add(order_buf, ", ");
3481 buffer_add(order_buf, _f);
3484 // jsonIteratorFree(order_itr);
3487 // IT'S THE OOOOOOOOOOOLD STYLE!
3489 osrfLogError(OSRF_LOG_MARK, "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3491 osrfAppSessionStatus(
3493 OSRF_STATUS_INTERNALSERVERERROR,
3494 "osrfMethodException",
3496 "Severe query error -- see error log for more details"
3501 buffer_free(having_buf);
3502 buffer_free(group_buf);
3503 buffer_free(order_buf);
3504 buffer_free(sql_buf);
3505 if (defaultselhash) jsonObjectFree(defaultselhash);
3506 jsonIteratorFree(class_itr);
3511 // jsonIteratorFree(class_itr);
3515 string = buffer_release(group_buf);
3517 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
3518 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3519 OSRF_BUFFER_ADD( sql_buf, string );
3524 string = buffer_release(having_buf);
3527 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3528 OSRF_BUFFER_ADD( sql_buf, string );
3533 string = buffer_release(order_buf);
3536 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3537 OSRF_BUFFER_ADD( sql_buf, string );
3543 const char* str = jsonObjectGetString(limit);
3544 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
3548 const char* str = jsonObjectGetString(offset);
3549 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
3552 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3555 if (defaultselhash) jsonObjectFree(defaultselhash);
3557 return buffer_release(sql_buf);
3561 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
3563 const char* locale = osrf_message_get_last_locale();
3565 osrfHash* fields = osrfHashGet(meta, "fields");
3566 char* core_class = osrfHashGet(meta, "classname");
3568 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
3570 jsonObject* node = NULL;
3571 jsonObject* snode = NULL;
3572 jsonObject* onode = NULL;
3573 const jsonObject* _tmp = NULL;
3574 jsonObject* selhash = NULL;
3575 jsonObject* defaultselhash = NULL;
3577 growing_buffer* sql_buf = buffer_init(128);
3578 growing_buffer* select_buf = buffer_init(128);
3580 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
3581 defaultselhash = jsonNewObjectType(JSON_HASH);
3582 selhash = defaultselhash;
3585 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
3586 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
3587 jsonObject* flist = jsonObjectGetKey( selhash, core_class );
3592 osrfStringArray* keys = osrfHashKeys( fields );
3593 while ( (field = osrfStringArrayGetString(keys, i++)) ) {
3594 if( ! str_is_true( osrfHashGet( osrfHashGet( fields, field ), "virtual" ) ) )
3595 jsonObjectPush( flist, jsonNewObject( field ) );
3597 osrfStringArrayFree(keys);
3601 jsonIterator* class_itr = jsonNewIterator( selhash );
3602 while ( (snode = jsonIteratorNext( class_itr )) ) {
3604 char* cname = class_itr->key;
3605 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
3606 if (!idlClass) continue;
3608 if (strcmp(core_class,class_itr->key)) {
3609 if (!join_hash) continue;
3611 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
3613 jsonObjectFree(found);
3617 jsonObjectFree(found);
3620 jsonIterator* select_itr = jsonNewIterator( snode );
3621 while ( (node = jsonIteratorNext( select_itr )) ) {
3622 const char* item_str = jsonObjectGetString( node );
3623 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
3624 char* fname = osrfHashGet(field, "name");
3626 if (!field) continue;
3631 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
3636 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
3637 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
3640 i18n = osrfHashGet(field, "i18n");
3642 if( str_is_true( i18n ) ) {
3643 char* pkey = osrfHashGet(idlClass, "primarykey");
3644 char* tname = osrfHashGet(idlClass, "tablename");
3646 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);
3648 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3651 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3655 jsonIteratorFree(select_itr);
3658 jsonIteratorFree(class_itr);
3660 char* col_list = buffer_release(select_buf);
3661 char* table = getSourceDefinition(meta);
3663 table = strdup( "(null)" );
3665 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
3670 char* join_clause = searchJOIN( join_hash, meta );
3671 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
3672 OSRF_BUFFER_ADD(sql_buf, join_clause);
3676 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
3677 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
3679 buffer_add(sql_buf, " WHERE ");
3681 char* pred = searchWHERE( search_hash, meta, AND_OP_JOIN, ctx );
3683 osrfAppSessionStatus(
3685 OSRF_STATUS_INTERNALSERVERERROR,
3686 "osrfMethodException",
3688 "Severe query error -- see error log for more details"
3690 buffer_free(sql_buf);
3691 if(defaultselhash) jsonObjectFree(defaultselhash);
3694 buffer_add(sql_buf, pred);
3699 char* string = NULL;
3700 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
3702 growing_buffer* order_buf = buffer_init(128);
3705 jsonIterator* class_itr = jsonNewIterator( _tmp );
3706 while ( (snode = jsonIteratorNext( class_itr )) ) {
3708 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
3711 if ( snode->type == JSON_HASH ) {
3713 jsonIterator* order_itr = jsonNewIterator( snode );
3714 while ( (onode = jsonIteratorNext( order_itr )) ) {
3716 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ))
3719 char* direction = NULL;
3720 if ( onode->type == JSON_HASH ) {
3721 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3722 string = searchFieldTransform(
3724 oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ),
3728 osrfAppSessionStatus(
3730 OSRF_STATUS_INTERNALSERVERERROR,
3731 "osrfMethodException",
3733 "Severe query error in ORDER BY clause -- see error log for more details"
3735 jsonIteratorFree( order_itr );
3736 jsonIteratorFree( class_itr );
3737 buffer_free( order_buf );
3738 buffer_free( sql_buf );
3739 if( defaultselhash ) jsonObjectFree( defaultselhash );
3743 growing_buffer* field_buf = buffer_init(16);
3744 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3745 string = buffer_release(field_buf);
3748 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
3749 const char* dir = jsonObjectGetString(_tmp);
3750 if (!strncasecmp(dir, "d", 1)) {
3751 direction = " DESC";
3758 string = strdup(order_itr->key);
3759 const char* dir = jsonObjectGetString(onode);
3760 if (!strncasecmp(dir, "d", 1)) {
3761 direction = " DESC";
3770 buffer_add(order_buf, ", ");
3773 buffer_add(order_buf, string);
3777 buffer_add(order_buf, direction);
3782 jsonIteratorFree(order_itr);
3785 const char* str = jsonObjectGetString(snode);
3786 buffer_add(order_buf, str);
3792 jsonIteratorFree(class_itr);
3794 string = buffer_release(order_buf);
3797 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3798 OSRF_BUFFER_ADD( sql_buf, string );
3804 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
3805 const char* str = jsonObjectGetString(_tmp);
3813 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
3815 const char* str = jsonObjectGetString(_tmp);
3824 if (defaultselhash) jsonObjectFree(defaultselhash);
3826 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3827 return buffer_release(sql_buf);
3830 int doJSONSearch ( osrfMethodContext* ctx ) {
3831 if(osrfMethodVerifyContext( ctx )) {
3832 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
3836 osrfLogDebug(OSRF_LOG_MARK, "Recieved query request");
3841 dbhandle = writehandle;
3843 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
3847 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
3848 flags |= SELECT_DISTINCT;
3850 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
3851 flags |= DISABLE_I18N;
3853 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
3856 jsonObjectGetKey( hash, "select" ),
3857 jsonObjectGetKey( hash, "from" ),
3858 jsonObjectGetKey( hash, "where" ),
3859 jsonObjectGetKey( hash, "having" ),
3860 jsonObjectGetKey( hash, "order_by" ),
3861 jsonObjectGetKey( hash, "limit" ),
3862 jsonObjectGetKey( hash, "offset" ),
3871 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
3872 dbi_result result = dbi_conn_query(dbhandle, sql);
3875 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
3877 if (dbi_result_first_row(result)) {
3878 /* JSONify the result */
3879 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
3882 jsonObject* return_val = oilsMakeJSONFromResult( result );
3883 osrfAppRespond( ctx, return_val );
3884 jsonObjectFree( return_val );
3885 } while (dbi_result_next_row(result));
3888 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
3891 osrfAppRespondComplete( ctx, NULL );
3893 /* clean up the query */
3894 dbi_result_free(result);
3898 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
3899 osrfAppSessionStatus(
3901 OSRF_STATUS_INTERNALSERVERERROR,
3902 "osrfMethodException",
3904 "Severe query error -- see error log for more details"
3912 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
3913 const jsonObject* params, int* err ) {
3916 dbhandle = writehandle;
3918 osrfHash* links = osrfHashGet(meta, "links");
3919 osrfHash* fields = osrfHashGet(meta, "fields");
3920 char* core_class = osrfHashGet(meta, "classname");
3921 char* pkey = osrfHashGet(meta, "primarykey");
3923 const jsonObject* _tmp;
3925 jsonObject* search_hash = jsonObjectGetIndex(params, 0);
3926 jsonObject* order_hash = jsonObjectGetIndex(params, 1);
3928 char* sql = buildSELECT( search_hash, order_hash, meta, ctx );
3930 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
3935 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
3937 dbi_result result = dbi_conn_query(dbhandle, sql);
3938 if( NULL == result ) {
3939 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
3940 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
3941 osrfAppSessionStatus(
3943 OSRF_STATUS_INTERNALSERVERERROR,
3944 "osrfMethodException",
3946 "Severe query error -- see error log for more details"
3953 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
3956 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
3957 osrfHash* dedup = osrfNewHash();
3959 if (dbi_result_first_row(result)) {
3960 /* JSONify the result */
3961 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
3963 obj = oilsMakeFieldmapperFromResult( result, meta );
3964 char* pkey_val = oilsFMGetString( obj, pkey );
3965 if ( osrfHashGet( dedup, pkey_val ) ) {
3966 jsonObjectFree(obj);
3969 osrfHashSet( dedup, pkey_val, pkey_val );
3970 jsonObjectPush(res_list, obj);
3972 } while (dbi_result_next_row(result));
3974 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
3978 osrfHashFree(dedup);
3979 /* clean up the query */
3980 dbi_result_free(result);
3983 if (res_list->size && order_hash) {
3984 _tmp = jsonObjectGetKeyConst( order_hash, "flesh" );
3986 int x = (int)jsonObjectGetNumber(_tmp);
3987 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
3989 const jsonObject* temp_blob;
3990 if ((temp_blob = jsonObjectGetKeyConst( order_hash, "flesh_fields" )) && x > 0) {
3992 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
3993 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
3995 osrfStringArray* link_fields = NULL;
3998 if (flesh_fields->size == 1) {
3999 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
4000 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
4005 link_fields = osrfNewStringArray(1);
4006 jsonIterator* _i = jsonNewIterator( flesh_fields );
4007 while ((_f = jsonIteratorNext( _i ))) {
4008 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4010 jsonIteratorFree(_i);
4015 jsonIterator* itr = jsonNewIterator( res_list );
4016 while ((cur = jsonIteratorNext( itr ))) {
4021 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4023 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4025 osrfHash* kid_link = osrfHashGet(links, link_field);
4026 if (!kid_link) continue;
4028 osrfHash* field = osrfHashGet(fields, link_field);
4029 if (!field) continue;
4031 osrfHash* value_field = field;
4033 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4034 if (!kid_idl) continue;
4036 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4037 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4040 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4041 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4044 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4046 if (link_map->size > 0) {
4047 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4050 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4055 osrfHashGet(kid_link, "class"),
4062 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4063 osrfHashGet(kid_link, "field"),
4064 osrfHashGet(kid_link, "class"),
4065 osrfHashGet(kid_link, "key"),
4066 osrfHashGet(kid_link, "reltype")
4069 jsonObject* fake_params = jsonNewObjectType(JSON_ARRAY);
4070 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH)); // search hash
4071 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH)); // order/flesh hash
4073 osrfLogDebug(OSRF_LOG_MARK, "Creating dummy params object...");
4075 const char* search_key = jsonObjectGetString(
4078 atoi( osrfHashGet(value_field, "array_position") )
4083 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4088 jsonObjectGetIndex(fake_params, 0),
4089 osrfHashGet(kid_link, "key"),
4090 jsonNewObject( search_key )
4094 jsonObjectGetIndex(fake_params, 1),
4096 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4100 jsonObjectSetKey( jsonObjectGetIndex(fake_params, 1), "flesh_fields", jsonObjectClone(flesh_blob) );
4102 if (jsonObjectGetKeyConst(order_hash, "order_by")) {
4104 jsonObjectGetIndex(fake_params, 1),
4106 jsonObjectClone(jsonObjectGetKeyConst(order_hash, "order_by"))
4110 if (jsonObjectGetKeyConst(order_hash, "select")) {
4112 jsonObjectGetIndex(fake_params, 1),
4114 jsonObjectClone(jsonObjectGetKeyConst(order_hash, "select"))
4118 jsonObject* kids = doFieldmapperSearch(ctx, kid_idl, fake_params, err);
4121 jsonObjectFree( fake_params );
4122 osrfStringArrayFree(link_fields);
4123 jsonIteratorFree(itr);
4124 jsonObjectFree(res_list);
4125 jsonObjectFree(flesh_blob);
4129 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4131 jsonObject* X = NULL;
4132 if ( link_map->size > 0 && kids->size > 0 ) {
4134 kids = jsonNewObjectType(JSON_ARRAY);
4136 jsonObject* _k_node;
4137 jsonIterator* _k = jsonNewIterator( X );
4138 while ((_k_node = jsonIteratorNext( _k ))) {
4144 (unsigned long)atoi(
4150 osrfHashGet(kid_link, "class")
4154 osrfStringArrayGetString( link_map, 0 )
4163 jsonIteratorFree(_k);
4166 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4167 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4170 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4171 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4175 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4176 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4179 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4180 jsonObjectClone( kids )
4185 jsonObjectFree(kids);
4189 jsonObjectFree( kids );
4190 jsonObjectFree( fake_params );
4192 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4193 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4197 jsonObjectFree( flesh_blob );
4198 osrfStringArrayFree(link_fields);
4199 jsonIteratorFree(itr);
4208 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
4210 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4212 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
4214 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
4217 if (!verifyObjectClass(ctx, target)) {
4222 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4223 osrfAppSessionStatus(
4225 OSRF_STATUS_BADREQUEST,
4226 "osrfMethodException",
4228 "No active transaction -- required for UPDATE"
4234 // The following test is harmless but redundant. If a class is
4235 // readonly, we don't register an update method for it.
4236 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4237 osrfAppSessionStatus(
4239 OSRF_STATUS_BADREQUEST,
4240 "osrfMethodException",
4242 "Cannot UPDATE readonly class"
4248 dbhandle = writehandle;
4250 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4252 // Set the last_xact_id
4253 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4255 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
4256 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4259 char* pkey = osrfHashGet(meta, "primarykey");
4260 osrfHash* fields = osrfHashGet(meta, "fields");
4262 char* id = oilsFMGetString( target, pkey );
4266 "%s updating %s object with %s = %s",
4268 osrfHashGet(meta, "fieldmapper"),
4273 growing_buffer* sql = buffer_init(128);
4274 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4279 osrfStringArray* field_list = osrfHashKeys( fields );
4280 while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
4282 osrfHash* field = osrfHashGet( fields, field_name );
4284 if(!( strcmp( field_name, pkey ) )) continue;
4285 if( str_is_true( osrfHashGet(osrfHashGet(fields,field_name), "virtual") ) )
4288 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4290 int value_is_numeric = 0; // boolean
4292 if (field_object && field_object->classname) {
4293 value = oilsFMGetString(
4295 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4298 value = jsonObjectToSimpleString( field_object );
4299 if( field_object && JSON_NUMBER == field_object->type )
4300 value_is_numeric = 1;
4303 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s", osrfHashGet(meta, "fieldmapper"), field_name, value);
4305 if (!field_object || field_object->type == JSON_NULL) {
4306 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) ) && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
4307 if (first) first = 0;
4308 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4309 buffer_fadd( sql, " %s = NULL", field_name );
4312 } else if ( value_is_numeric || !strcmp( get_primitive( field ), "number") ) {
4313 if (first) first = 0;
4314 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4316 const char* numtype = get_datatype( field );
4317 if ( !strncmp( numtype, "INT", 3 ) ) {
4318 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
4319 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
4320 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
4322 // Must really be intended as a string, so quote it
4323 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4324 buffer_fadd( sql, " %s = %s", field_name, value );
4326 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4327 osrfAppSessionStatus(
4329 OSRF_STATUS_INTERNALSERVERERROR,
4330 "osrfMethodException",
4332 "Error quoting string -- please see the error log for more details"
4342 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
4345 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4346 if (first) first = 0;
4347 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4348 buffer_fadd( sql, " %s = %s", field_name, value );
4351 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4352 osrfAppSessionStatus(
4354 OSRF_STATUS_INTERNALSERVERERROR,
4355 "osrfMethodException",
4357 "Error quoting string -- please see the error log for more details"
4371 jsonObject* obj = jsonNewObject(id);
4373 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4374 dbi_conn_quote_string(dbhandle, &id);
4376 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
4378 char* query = buffer_release(sql);
4379 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
4381 dbi_result result = dbi_conn_query(dbhandle, query);
4385 jsonObjectFree(obj);
4386 obj = jsonNewObject(NULL);
4389 "%s ERROR updating %s object with %s = %s",
4391 osrfHashGet(meta, "fieldmapper"),
4402 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4404 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4406 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4407 osrfAppSessionStatus(
4409 OSRF_STATUS_BADREQUEST,
4410 "osrfMethodException",
4412 "No active transaction -- required for DELETE"
4418 // The following test is harmless but redundant. If a class is
4419 // readonly, we don't register a delete method for it.
4420 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4421 osrfAppSessionStatus(
4423 OSRF_STATUS_BADREQUEST,
4424 "osrfMethodException",
4426 "Cannot DELETE readonly class"
4432 dbhandle = writehandle;
4436 char* pkey = osrfHashGet(meta, "primarykey");
4444 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4445 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4450 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4453 if (!verifyObjectPCRUD( ctx, NULL )) {
4458 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4463 "%s deleting %s object with %s = %s",
4465 osrfHashGet(meta, "fieldmapper"),
4470 obj = jsonNewObject(id);
4472 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4473 dbi_conn_quote_string(writehandle, &id);
4475 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4478 jsonObjectFree(obj);
4479 obj = jsonNewObject(NULL);
4482 "%s ERROR deleting %s object with %s = %s",
4484 osrfHashGet(meta, "fieldmapper"),
4497 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4498 if(!(result && meta)) return jsonNULL;
4500 jsonObject* object = jsonNewObject(NULL);
4501 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4503 osrfHash* fields = osrfHashGet(meta, "fields");
4505 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4509 char dt_string[256];
4513 int columnIndex = 1;
4515 unsigned short type;
4516 const char* columnName;
4518 /* cycle through the column list */
4519 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4521 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4523 fmIndex = -1; // reset the position
4525 /* determine the field type and storage attributes */
4526 type = dbi_result_get_field_type(result, columnName);
4527 attr = dbi_result_get_field_attribs(result, columnName);
4529 /* fetch the fieldmapper index */
4530 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4532 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4535 const char* pos = (char*)osrfHashGet(_f, "array_position");
4536 if ( !pos ) continue;
4538 fmIndex = atoi( pos );
4539 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
4544 if (dbi_result_field_is_null(result, columnName)) {
4545 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
4550 case DBI_TYPE_INTEGER :
4552 if( attr & DBI_INTEGER_SIZE8 )
4553 jsonObjectSetIndex( object, fmIndex,
4554 jsonNewNumberObject(dbi_result_get_longlong(result, columnName)));
4556 jsonObjectSetIndex( object, fmIndex,
4557 jsonNewNumberObject(dbi_result_get_int(result, columnName)));
4561 case DBI_TYPE_DECIMAL :
4562 jsonObjectSetIndex( object, fmIndex,
4563 jsonNewNumberObject(dbi_result_get_double(result, columnName)));
4566 case DBI_TYPE_STRING :
4572 jsonNewObject( dbi_result_get_string(result, columnName) )
4577 case DBI_TYPE_DATETIME :
4579 memset(dt_string, '\0', sizeof(dt_string));
4580 memset(&gmdt, '\0', sizeof(gmdt));
4582 _tmp_dt = dbi_result_get_datetime(result, columnName);
4585 if (!(attr & DBI_DATETIME_DATE)) {
4586 gmtime_r( &_tmp_dt, &gmdt );
4587 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4588 } else if (!(attr & DBI_DATETIME_TIME)) {
4589 localtime_r( &_tmp_dt, &gmdt );
4590 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4592 localtime_r( &_tmp_dt, &gmdt );
4593 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4596 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
4600 case DBI_TYPE_BINARY :
4601 osrfLogError( OSRF_LOG_MARK,
4602 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4610 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
4611 if(!result) return jsonNULL;
4613 jsonObject* object = jsonNewObject(NULL);
4616 char dt_string[256];
4620 int columnIndex = 1;
4622 unsigned short type;
4623 const char* columnName;
4625 /* cycle through the column list */
4626 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4628 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4630 fmIndex = -1; // reset the position
4632 /* determine the field type and storage attributes */
4633 type = dbi_result_get_field_type(result, columnName);
4634 attr = dbi_result_get_field_attribs(result, columnName);
4636 if (dbi_result_field_is_null(result, columnName)) {
4637 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
4642 case DBI_TYPE_INTEGER :
4644 if( attr & DBI_INTEGER_SIZE8 )
4645 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_longlong(result, columnName)) );
4647 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_int(result, columnName)) );
4650 case DBI_TYPE_DECIMAL :
4651 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_double(result, columnName)) );
4654 case DBI_TYPE_STRING :
4655 jsonObjectSetKey( object, columnName, jsonNewObject(dbi_result_get_string(result, columnName)) );
4658 case DBI_TYPE_DATETIME :
4660 memset(dt_string, '\0', sizeof(dt_string));
4661 memset(&gmdt, '\0', sizeof(gmdt));
4663 _tmp_dt = dbi_result_get_datetime(result, columnName);
4666 if (!(attr & DBI_DATETIME_DATE)) {
4667 gmtime_r( &_tmp_dt, &gmdt );
4668 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4669 } else if (!(attr & DBI_DATETIME_TIME)) {
4670 localtime_r( &_tmp_dt, &gmdt );
4671 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4673 localtime_r( &_tmp_dt, &gmdt );
4674 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4677 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
4680 case DBI_TYPE_BINARY :
4681 osrfLogError( OSRF_LOG_MARK,
4682 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4690 // Interpret a string as true or false
4691 static int str_is_true( const char* str ) {
4692 if( NULL == str || strcasecmp( str, "true" ) )
4698 // Interpret a jsonObject as true or false
4699 static int obj_is_true( const jsonObject* obj ) {
4702 else switch( obj->type )
4710 if( strcasecmp( obj->value.s, "true" ) )
4714 case JSON_NUMBER : // Support 1/0 for perl's sake
4715 if( jsonObjectGetNumber( obj ) == 1.0 )
4724 // Translate a numeric code into a text string identifying a type of
4725 // jsonObject. To be used for building error messages.
4726 static const char* json_type( int code ) {
4732 return "JSON_ARRAY";
4734 return "JSON_STRING";
4736 return "JSON_NUMBER";
4742 return "(unrecognized)";
4746 // Extract the "primitive" attribute from an IDL field definition.
4747 // If we haven't initialized the app, then we must be running in
4748 // some kind of testbed. In that case, default to "string".
4749 static const char* get_primitive( osrfHash* field ) {
4750 const char* s = osrfHashGet( field, "primitive" );
4752 if( child_initialized )
4755 "%s ERROR No \"datatype\" attribute for field \"%s\"",
4757 osrfHashGet( field, "name" )
4765 // Extract the "datatype" attribute from an IDL field definition.
4766 // If we haven't initialized the app, then we must be running in
4767 // some kind of testbed. In that case, default to to NUMERIC,
4768 // since we look at the datatype only for numbers.
4769 static const char* get_datatype( osrfHash* field ) {
4770 const char* s = osrfHashGet( field, "datatype" );
4772 if( child_initialized )
4775 "%s ERROR No \"datatype\" attribute for field \"%s\"",
4777 osrfHashGet( field, "name" )
4786 If the input string is potentially a valid SQL identifier, return 1.
4789 Purpose: to prevent certain kinds of SQL injection. To that end we
4790 don't necessarily need to follow all the rules exactly, such as requiring
4791 that the first character not be a digit.
4793 We allow leading and trailing white space. In between, we do not allow
4794 punctuation (except for underscores and dollar signs), control
4795 characters, or embedded white space.
4797 More pedantically we should allow quoted identifiers containing arbitrary
4798 characters, but for the foreseeable future such quoted identifiers are not
4799 likely to be an issue.
4801 static int is_identifier( const char* s) {
4805 // Skip leading white space
4806 while( isspace( (unsigned char) *s ) )
4810 return 0; // Nothing but white space? Not okay.
4812 // Check each character until we reach white space or
4813 // end-of-string. Letters, digits, underscores, and
4814 // dollar signs are okay. With the exception of periods
4815 // (as in schema.identifier), control characters and other
4816 // punctuation characters are not okay. Anything else
4817 // is okay -- it could for example be part of a multibyte
4818 // UTF8 character such as a letter with diacritical marks,
4819 // and those are allowed.
4821 if( isalnum( (unsigned char) *s )
4825 ; // Fine; keep going
4826 else if( ispunct( (unsigned char) *s )
4827 || iscntrl( (unsigned char) *s ) )
4830 } while( *s && ! isspace( (unsigned char) *s ) );
4832 // If we found any white space in the above loop,
4833 // the rest had better be all white space.
4835 while( isspace( (unsigned char) *s ) )
4839 return 0; // White space was embedded within non-white space
4845 Determine whether to accept a character string as a comparison operator.
4846 Return 1 if it's good, or 0 if it's bad.
4848 We don't validate it for real. We just make sure that it doesn't contain
4849 any semicolons or white space (with a special exception for the
4850 "SIMILAR TO" operator). The idea is to block certain kinds of SQL
4851 injection. If it has no semicolons or white space but it's still not a
4852 valid operator, then the database will complain.
4854 Another approach would be to compare the string against a short list of
4855 approved operators. We don't do that because we want to allow custom
4856 operators like ">100*", which would be difficult or impossible to
4857 express otherwise in a JSON query.
4859 static int is_good_operator( const char* op ) {
4860 if( !op ) return 0; // Sanity check
4864 if( isspace( (unsigned char) *s ) ) {
4865 // Special exception for SIMILAR TO. Someday we might make
4866 // exceptions for IS DISTINCT FROM and IS NOT DISTINCT FROM.
4867 if( !strcasecmp( op, "similar to" ) )
4872 else if( ';' == *s )