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);
2252 } else if (!field && !fkey) {
2253 osrfHash* _links = oilsIDL_links( leftclass );
2255 // For each link defined for the left class:
2256 // see if the link references the joined class
2257 osrfHashIterator* itr = osrfNewHashIterator( _links );
2258 osrfHash* curr_link = NULL;
2259 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2260 const char* other_class = osrfHashGet( curr_link, "class" );
2261 if( other_class && !strcmp( other_class, class ) ) {
2263 // Found a link between the classes
2264 fkey = osrfHashIteratorKey( itr );
2265 field = osrfHashGet( curr_link, "key" );
2269 osrfHashIteratorFree( itr );
2271 if (!field || !fkey) {
2272 // Do another such search, with the classes reversed
2273 _links = oilsIDL_links( class );
2275 // For each link defined for the joined class:
2276 // see if the link references the left class
2277 osrfHashIterator* itr = osrfNewHashIterator( _links );
2278 osrfHash* curr_link = NULL;
2279 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2280 const char* other_class = osrfHashGet( curr_link, "class" );
2281 if( other_class && !strcmp( other_class, leftclass ) ) {
2283 // Found a link between the classes
2284 fkey = osrfHashIteratorKey( itr );
2285 field = osrfHashGet( curr_link, "key" );
2289 osrfHashIteratorFree( itr );
2292 if (!field || !fkey) {
2295 "%s: JOIN failed. No link defined between %s and %s",
2300 buffer_free(join_buf);
2302 jsonObjectFree(freeable_hash);
2303 jsonIteratorFree(search_itr);
2309 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2311 if ( !strcasecmp(type,"left") ) {
2312 buffer_add(join_buf, " LEFT JOIN");
2313 } else if ( !strcasecmp(type,"right") ) {
2314 buffer_add(join_buf, " RIGHT JOIN");
2315 } else if ( !strcasecmp(type,"full") ) {
2316 buffer_add(join_buf, " FULL JOIN");
2318 buffer_add(join_buf, " INNER JOIN");
2321 buffer_add(join_buf, " INNER JOIN");
2324 char* table = getSourceDefinition(idlClass);
2326 jsonIteratorFree( search_itr );
2327 buffer_free( join_buf );
2329 jsonObjectFree( freeable_hash );
2333 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2334 table, class, class, field, leftclass, fkey);
2337 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2339 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2340 if ( filter_op && !strcasecmp("or",filter_op) ) {
2341 buffer_add( join_buf, " OR " );
2343 buffer_add( join_buf, " AND " );
2346 char* jpred = searchWHERE( filter, idlClass, AND_OP_JOIN, NULL );
2348 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2349 OSRF_BUFFER_ADD( join_buf, jpred );
2354 "%s: JOIN failed. Invalid conditional expression.",
2357 jsonIteratorFree( search_itr );
2358 buffer_free( join_buf );
2360 jsonObjectFree( freeable_hash );
2365 buffer_add(join_buf, " ) ");
2367 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2369 char* jpred = searchJOIN( join_filter, idlClass );
2370 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2371 OSRF_BUFFER_ADD( join_buf, jpred );
2377 jsonObjectFree(freeable_hash);
2378 jsonIteratorFree(search_itr);
2380 return buffer_release(join_buf);
2385 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2386 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2387 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2389 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2391 search_hash is the JSON expression of the conditions.
2392 meta is the class definition from the IDL, for the relevant table.
2393 opjoin_type indicates whether multiple conditions, if present, should be
2394 connected by AND or OR.
2395 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2396 to pass it to other functions -- and all they do with it is to use the session
2397 and request members to send error messages back to the client.
2401 static char* searchWHERE ( const jsonObject* search_hash, osrfHash* meta, int opjoin_type, osrfMethodContext* ctx ) {
2405 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2413 growing_buffer* sql_buf = buffer_init(128);
2415 jsonObject* node = NULL;
2418 if ( search_hash->type == JSON_ARRAY ) {
2419 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2420 jsonIterator* search_itr = jsonNewIterator( search_hash );
2421 if( !jsonIteratorHasNext( search_itr ) ) {
2424 "%s: Invalid predicate structure: empty JSON array",
2427 jsonIteratorFree( search_itr );
2428 buffer_free( sql_buf );
2432 while ( (node = jsonIteratorNext( search_itr )) ) {
2436 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2437 else buffer_add(sql_buf, " AND ");
2440 char* subpred = searchWHERE( node, meta, opjoin_type, ctx );
2442 buffer_fadd(sql_buf, "( %s )", subpred);
2445 jsonIteratorFree( search_itr );
2446 buffer_free( sql_buf );
2450 jsonIteratorFree(search_itr);
2452 } else if ( search_hash->type == JSON_HASH ) {
2453 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2454 jsonIterator* search_itr = jsonNewIterator( search_hash );
2455 if( !jsonIteratorHasNext( search_itr ) ) {
2458 "%s: Invalid predicate structure: empty JSON object",
2461 jsonIteratorFree( search_itr );
2462 buffer_free( sql_buf );
2466 while ( (node = jsonIteratorNext( search_itr )) ) {
2471 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2472 else buffer_add(sql_buf, " AND ");
2475 if ( '+' == search_itr->key[ 0 ] ) {
2476 if ( node->type == JSON_STRING ) {
2477 // Intended purpose; to allow reference to a Boolean column
2479 // Verify that the class alias is not empty
2480 if( '\0' == search_itr->key[ 1 ] ) {
2483 "%s: Table alias is empty",
2486 jsonIteratorFree( search_itr );
2487 buffer_free( sql_buf );
2491 // Verify that the string looks like an identifier.
2492 const char* subpred = jsonObjectGetString( node );
2493 if( ! is_identifier( subpred ) ) {
2496 "%s: Invalid boolean identifier in WHERE clause: \"%s\"",
2500 jsonIteratorFree( search_itr );
2501 buffer_free( sql_buf );
2505 buffer_fadd(sql_buf, " \"%s\".%s ", search_itr->key + 1, subpred);
2507 char* subpred = searchWHERE( node, osrfHashGet( oilsIDL(), search_itr->key + 1 ), AND_OP_JOIN, ctx );
2509 buffer_fadd(sql_buf, "( %s )", subpred);
2512 jsonIteratorFree( search_itr );
2513 buffer_free( sql_buf );
2517 } else if ( !strcasecmp("-or",search_itr->key) ) {
2518 char* subpred = searchWHERE( node, meta, OR_OP_JOIN, ctx );
2520 buffer_fadd(sql_buf, "( %s )", subpred);
2523 buffer_free( sql_buf );
2526 } else if ( !strcasecmp("-and",search_itr->key) ) {
2527 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2529 buffer_fadd(sql_buf, "( %s )", subpred);
2532 buffer_free( sql_buf );
2535 } else if ( !strcasecmp("-not",search_itr->key) ) {
2536 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2538 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2541 buffer_free( sql_buf );
2544 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2545 char* subpred = SELECT(
2547 jsonObjectGetKey( node, "select" ),
2548 jsonObjectGetKey( node, "from" ),
2549 jsonObjectGetKey( node, "where" ),
2550 jsonObjectGetKey( node, "having" ),
2551 jsonObjectGetKey( node, "order_by" ),
2552 jsonObjectGetKey( node, "limit" ),
2553 jsonObjectGetKey( node, "offset" ),
2558 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2561 buffer_free( sql_buf );
2564 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2565 char* subpred = SELECT(
2567 jsonObjectGetKey( node, "select" ),
2568 jsonObjectGetKey( node, "from" ),
2569 jsonObjectGetKey( node, "where" ),
2570 jsonObjectGetKey( node, "having" ),
2571 jsonObjectGetKey( node, "order_by" ),
2572 jsonObjectGetKey( node, "limit" ),
2573 jsonObjectGetKey( node, "offset" ),
2578 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2581 buffer_free( sql_buf );
2587 char* class = osrfHashGet(meta, "classname");
2588 osrfHash* fields = osrfHashGet(meta, "fields");
2589 osrfHash* field = osrfHashGet( fields, search_itr->key );
2593 char* table = getSourceDefinition(meta);
2595 table = strdup( "(?)" );
2598 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2604 buffer_free(sql_buf);
2606 jsonIteratorFree(search_itr);
2610 char* subpred = searchPredicate( class, field, node, ctx );
2612 buffer_add( sql_buf, subpred );
2615 buffer_free(sql_buf);
2616 jsonIteratorFree(search_itr);
2621 jsonIteratorFree(search_itr);
2624 // ERROR ... only hash and array allowed at this level
2625 char* predicate_string = jsonObjectToJSON( search_hash );
2628 "%s: Invalid predicate structure: %s",
2632 buffer_free(sql_buf);
2633 free(predicate_string);
2637 return buffer_release(sql_buf);
2641 /* method context */ osrfMethodContext* ctx,
2643 /* SELECT */ jsonObject* selhash,
2644 /* FROM */ jsonObject* join_hash,
2645 /* WHERE */ jsonObject* search_hash,
2646 /* HAVING */ jsonObject* having_hash,
2647 /* ORDER BY */ jsonObject* order_hash,
2648 /* LIMIT */ jsonObject* limit,
2649 /* OFFSET */ jsonObject* offset,
2650 /* flags */ int flags
2652 const char* locale = osrf_message_get_last_locale();
2654 // in case we don't get a select list
2655 jsonObject* defaultselhash = NULL;
2657 // general tmp objects
2658 const jsonObject* tmp_const;
2659 jsonObject* selclass = NULL;
2660 jsonObject* selfield = NULL;
2661 jsonObject* snode = NULL;
2662 jsonObject* onode = NULL;
2664 char* string = NULL;
2665 int from_function = 0;
2670 // the core search class
2671 char* core_class = NULL;
2673 // metadata about the core search class
2674 osrfHash* core_meta = NULL;
2676 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale);
2678 // punt if there's no core class
2679 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2682 "%s: FROM clause is missing or empty",
2686 osrfAppSessionStatus(
2688 OSRF_STATUS_INTERNALSERVERERROR,
2689 "osrfMethodException",
2691 "FROM clause is missing or empty in JSON query"
2696 // get the core class -- the only key of the top level FROM clause, or a string
2697 if (join_hash->type == JSON_HASH) {
2698 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2699 snode = jsonIteratorNext( tmp_itr );
2701 core_class = strdup( tmp_itr->key );
2704 jsonObject* extra = jsonIteratorNext( tmp_itr );
2706 jsonIteratorFree( tmp_itr );
2709 // There shouldn't be more than one entry in join_hash
2713 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2717 osrfAppSessionStatus(
2719 OSRF_STATUS_INTERNALSERVERERROR,
2720 "osrfMethodException",
2722 "Malformed FROM clause in JSON query"
2725 return NULL; // Malformed join_hash; extra entry
2727 } else if (join_hash->type == JSON_ARRAY) {
2729 core_class = jsonObjectToSimpleString( jsonObjectGetIndex(join_hash, 0) );
2732 } else if (join_hash->type == JSON_STRING) {
2733 core_class = jsonObjectToSimpleString( join_hash );
2739 "%s: FROM clause is unexpected JSON type: %s",
2741 json_type( join_hash->type )
2744 osrfAppSessionStatus(
2746 OSRF_STATUS_INTERNALSERVERERROR,
2747 "osrfMethodException",
2749 "Ill-formed FROM clause in JSON query"
2755 if (!from_function) {
2756 // Get the IDL class definition for the core class
2757 core_meta = osrfHashGet( oilsIDL(), core_class );
2758 if( !core_meta ) { // Didn't find it?
2761 "%s: SELECT clause references undefined class: \"%s\"",
2766 osrfAppSessionStatus(
2768 OSRF_STATUS_INTERNALSERVERERROR,
2769 "osrfMethodException",
2771 "SELECT clause references undefined class in JSON query"
2777 // Make sure the class isn't virtual
2778 if( str_is_true( osrfHashGet( core_meta, "virtual" ) ) ) {
2781 "%s: Core class is virtual: \"%s\"",
2786 osrfAppSessionStatus(
2788 OSRF_STATUS_INTERNALSERVERERROR,
2789 "osrfMethodException",
2791 "FROM clause references virtual class in JSON query"
2798 // if the select list is empty, or the core class field list is '*',
2799 // build the default select list ...
2801 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
2802 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2803 } else if( selhash->type != JSON_HASH ) {
2806 "%s: Expected JSON_HASH for SELECT clause; found %s",
2808 json_type( selhash->type )
2812 osrfAppSessionStatus(
2814 OSRF_STATUS_INTERNALSERVERERROR,
2815 "osrfMethodException",
2817 "Malformed SELECT clause in JSON query"
2821 } else if ( (tmp_const = jsonObjectGetKeyConst( selhash, core_class )) && tmp_const->type == JSON_STRING ) {
2822 const char* _x = jsonObjectGetString( tmp_const );
2823 if (!strncmp( "*", _x, 1 )) {
2824 jsonObjectRemoveKey( selhash, core_class );
2825 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2830 growing_buffer* sql_buf = buffer_init(128);
2832 // temp buffer for the SELECT list
2833 growing_buffer* select_buf = buffer_init(128);
2834 growing_buffer* order_buf = buffer_init(128);
2835 growing_buffer* group_buf = buffer_init(128);
2836 growing_buffer* having_buf = buffer_init(128);
2838 int aggregate_found = 0; // boolean
2840 // Build a select list
2841 if(from_function) // From a function we select everything
2842 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
2845 // If we need to build a default list, prepare to do so
2846 jsonObject* _tmp = jsonObjectGetKey( selhash, core_class );
2847 if ( _tmp && !_tmp->size ) {
2849 osrfHash* core_fields = osrfHashGet( core_meta, "fields" );
2851 osrfHashIterator* field_itr = osrfNewHashIterator( core_fields );
2852 osrfHash* field_def;
2853 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2854 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2855 // This field is not virtual, so add it to the list
2856 jsonObjectPush( _tmp, jsonNewObject( osrfHashIteratorKey( field_itr ) ) );
2859 osrfHashIteratorFree( field_itr );
2862 // Now build the actual select list
2866 jsonIterator* selclass_itr = jsonNewIterator( selhash );
2867 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
2869 // Make sure the class is defined in the IDL
2870 const char* cname = selclass_itr->key;
2871 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
2875 "%s: Selected class \"%s\" not defined in IDL",
2881 osrfAppSessionStatus(
2883 OSRF_STATUS_INTERNALSERVERERROR,
2884 "osrfMethodException",
2886 "Selected class is not defined"
2888 jsonIteratorFree( selclass_itr );
2889 buffer_free( sql_buf );
2890 buffer_free( select_buf );
2891 buffer_free( order_buf );
2892 buffer_free( group_buf );
2893 buffer_free( having_buf );
2894 if( defaultselhash ) jsonObjectFree( defaultselhash );
2899 // Make sure the target relation is in the join tree.
2901 // At this point join_hash is a step down from the join_hash we
2902 // received as a parameter. If the original was a JSON_STRING,
2903 // then json_hash is now NULL. If the original was a JSON_HASH,
2904 // then json_hash is now the first (and only) entry in it,
2905 // denoting the core class. We've already excluded the
2906 // possibility that the original was a JSON_ARRAY, because in
2907 // that case from_function would be non-NULL, and we wouldn't
2910 int class_in_from_clause; // boolean
2912 if ( ! strcmp( core_class, cname ))
2913 // This is the core class -- no problem
2914 class_in_from_clause = 1;
2917 // There's only one class in the FROM clause, and this isn't it
2918 class_in_from_clause = 0;
2919 else if (join_hash->type == JSON_STRING) {
2920 // There's only one class in the FROM clause
2921 const char* str = jsonObjectGetString(join_hash);
2922 if ( strcmp( str, cname ) )
2923 class_in_from_clause = 0; // This isn't it
2925 class_in_from_clause = 1; // This is it
2927 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", cname);
2928 if ( 0 == found->size )
2929 class_in_from_clause = 0; // Nowhere in the join tree
2931 class_in_from_clause = 1; // Found it
2932 jsonObjectFree( found );
2936 // If the class isn't in the FROM clause, bail out
2937 if( ! class_in_from_clause ) {
2940 "%s: SELECT clause references class not in FROM clause: \"%s\"",
2945 osrfAppSessionStatus(
2947 OSRF_STATUS_INTERNALSERVERERROR,
2948 "osrfMethodException",
2950 "Selected class not in FROM clause in JSON query"
2952 jsonIteratorFree( selclass_itr );
2953 buffer_free( sql_buf );
2954 buffer_free( select_buf );
2955 buffer_free( order_buf );
2956 buffer_free( group_buf );
2957 buffer_free( having_buf );
2958 if( defaultselhash ) jsonObjectFree( defaultselhash );
2963 // Look up some attributes of the current class, so that we
2964 // don't have to look them up again for each field
2965 osrfHash* class_field_set = osrfHashGet( idlClass, "fields" );
2966 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
2967 const char* class_tname = osrfHashGet( idlClass, "tablename" );
2969 // stitch together the column list ...
2970 jsonIterator* select_itr = jsonNewIterator( selclass );
2971 while ( (selfield = jsonIteratorNext( select_itr )) ) { // for each SELECT column
2973 // If we need a separator comma, add one
2977 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
2980 // ... if it's a string, just toss it on the pile
2981 if (selfield->type == JSON_STRING) {
2983 // Look up the field in the IDL
2984 const char* col_name = jsonObjectGetString( selfield );
2985 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
2987 // No such field in current class
2990 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
2996 osrfAppSessionStatus(
2998 OSRF_STATUS_INTERNALSERVERERROR,
2999 "osrfMethodException",
3001 "Selected column not defined in JSON query"
3003 jsonIteratorFree( select_itr );
3004 jsonIteratorFree( selclass_itr );
3005 buffer_free( sql_buf );
3006 buffer_free( select_buf );
3007 buffer_free( order_buf );
3008 buffer_free( group_buf );
3009 buffer_free( having_buf );
3010 if( defaultselhash ) jsonObjectFree( defaultselhash );
3013 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3014 // Virtual field not allowed
3017 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3023 osrfAppSessionStatus(
3025 OSRF_STATUS_INTERNALSERVERERROR,
3026 "osrfMethodException",
3028 "Selected column may not be virtual in JSON query"
3030 jsonIteratorFree( select_itr );
3031 jsonIteratorFree( selclass_itr );
3032 buffer_free( sql_buf );
3033 buffer_free( select_buf );
3034 buffer_free( order_buf );
3035 buffer_free( group_buf );
3036 buffer_free( having_buf );
3037 if( defaultselhash ) jsonObjectFree( defaultselhash );
3044 if (flags & DISABLE_I18N)
3047 i18n = osrfHashGet(field_def, "i18n");
3049 if( str_is_true( i18n ) ) {
3050 buffer_fadd( select_buf,
3051 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3052 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3054 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3057 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3060 // ... but it could be an object, in which case we check for a Field Transform
3061 } else if (selfield->type == JSON_HASH) {
3063 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3065 // Get the field definition from the IDL
3066 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3068 // No such field in current class
3071 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3077 osrfAppSessionStatus(
3079 OSRF_STATUS_INTERNALSERVERERROR,
3080 "osrfMethodException",
3082 "Selected column is not defined in JSON query"
3084 jsonIteratorFree( select_itr );
3085 jsonIteratorFree( selclass_itr );
3086 buffer_free( sql_buf );
3087 buffer_free( select_buf );
3088 buffer_free( order_buf );
3089 buffer_free( group_buf );
3090 buffer_free( having_buf );
3091 if( defaultselhash ) jsonObjectFree( defaultselhash );
3094 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3095 // No such field in current class
3098 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3104 osrfAppSessionStatus(
3106 OSRF_STATUS_INTERNALSERVERERROR,
3107 "osrfMethodException",
3109 "Selected column is virtual in JSON query"
3111 jsonIteratorFree( select_itr );
3112 jsonIteratorFree( selclass_itr );
3113 buffer_free( sql_buf );
3114 buffer_free( select_buf );
3115 buffer_free( order_buf );
3116 buffer_free( group_buf );
3117 buffer_free( having_buf );
3118 if( defaultselhash ) jsonObjectFree( defaultselhash );
3123 // Decide what to use as a column alias
3125 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3126 _alias = jsonObjectGetString( tmp_const );
3127 } else { // Use field name as the alias
3131 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3132 char* transform_str = searchFieldTransform(cname, field_def, selfield);
3133 if( transform_str ) {
3134 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3135 free(transform_str);
3138 osrfAppSessionStatus(
3140 OSRF_STATUS_INTERNALSERVERERROR,
3141 "osrfMethodException",
3143 "Unable to generate transform function in JSON query"
3145 jsonIteratorFree( select_itr );
3146 jsonIteratorFree( selclass_itr );
3147 buffer_free( sql_buf );
3148 buffer_free( select_buf );
3149 buffer_free( order_buf );
3150 buffer_free( group_buf );
3151 buffer_free( having_buf );
3152 if( defaultselhash ) jsonObjectFree( defaultselhash );
3160 if (flags & DISABLE_I18N)
3163 i18n = osrfHashGet(field_def, "i18n");
3165 if( str_is_true( i18n ) ) {
3166 buffer_fadd( select_buf,
3167 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3168 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3170 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3173 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3180 "%s: Selected item is unexpected JSON type: %s",
3182 json_type( selfield->type )
3185 osrfAppSessionStatus(
3187 OSRF_STATUS_INTERNALSERVERERROR,
3188 "osrfMethodException",
3190 "Ill-formed SELECT item in JSON query"
3192 jsonIteratorFree( select_itr );
3193 jsonIteratorFree( selclass_itr );
3194 buffer_free( sql_buf );
3195 buffer_free( select_buf );
3196 buffer_free( order_buf );
3197 buffer_free( group_buf );
3198 buffer_free( having_buf );
3199 if( defaultselhash ) jsonObjectFree( defaultselhash );
3204 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3205 if( obj_is_true( agg_obj ) )
3206 aggregate_found = 1;
3208 // Append a comma (except for the first one)
3209 // and add the column to a GROUP BY clause
3213 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3215 buffer_fadd(group_buf, " %d", sel_pos);
3219 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3221 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3222 if ( ! obj_is_true( aggregate_obj ) ) {
3226 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3229 buffer_fadd(group_buf, " %d", sel_pos);
3232 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3236 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3239 _column = searchFieldTransform(cname, field, selfield);
3240 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3241 OSRF_BUFFER_ADD(group_buf, _column);
3242 _column = searchFieldTransform(cname, field, selfield);
3249 } // end while -- iterating across SELECT columns
3251 jsonIteratorFree(select_itr);
3252 } // end while -- iterating across classes
3254 jsonIteratorFree(selclass_itr);
3258 char* col_list = buffer_release(select_buf);
3260 if (from_function) table = searchValueTransform(join_hash);
3261 else table = getSourceDefinition(core_meta);
3265 osrfAppSessionStatus(
3267 OSRF_STATUS_INTERNALSERVERERROR,
3268 "osrfMethodException",
3270 "Unable to identify table for core class"
3273 buffer_free( sql_buf );
3274 buffer_free( order_buf );
3275 buffer_free( group_buf );
3276 buffer_free( having_buf );
3277 if( defaultselhash ) jsonObjectFree( defaultselhash );
3282 // Put it all together
3283 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3287 if (!from_function) {
3288 // Now, walk the join tree and add that clause
3290 char* join_clause = searchJOIN( join_hash, core_meta );
3292 buffer_add(sql_buf, join_clause);
3296 osrfAppSessionStatus(
3298 OSRF_STATUS_INTERNALSERVERERROR,
3299 "osrfMethodException",
3301 "Unable to construct JOIN clause(s)"
3303 buffer_free( sql_buf );
3304 buffer_free( order_buf );
3305 buffer_free( group_buf );
3306 buffer_free( having_buf );
3307 if( defaultselhash ) jsonObjectFree( defaultselhash );
3313 // Build a WHERE clause, if there is one
3314 if ( search_hash ) {
3315 buffer_add(sql_buf, " WHERE ");
3317 // and it's on the WHERE clause
3318 char* pred = searchWHERE( search_hash, core_meta, AND_OP_JOIN, ctx );
3321 buffer_add(sql_buf, pred);
3325 osrfAppSessionStatus(
3327 OSRF_STATUS_INTERNALSERVERERROR,
3328 "osrfMethodException",
3330 "Severe query error in WHERE predicate -- see error log for more details"
3334 buffer_free(having_buf);
3335 buffer_free(group_buf);
3336 buffer_free(order_buf);
3337 buffer_free(sql_buf);
3338 if (defaultselhash) jsonObjectFree(defaultselhash);
3343 // Build a HAVING clause, if there is one
3344 if ( having_hash ) {
3345 buffer_add(sql_buf, " HAVING ");
3347 // and it's on the the WHERE clause
3348 char* pred = searchWHERE( having_hash, core_meta, AND_OP_JOIN, ctx );
3351 buffer_add(sql_buf, pred);
3355 osrfAppSessionStatus(
3357 OSRF_STATUS_INTERNALSERVERERROR,
3358 "osrfMethodException",
3360 "Severe query error in HAVING predicate -- see error log for more details"
3364 buffer_free(having_buf);
3365 buffer_free(group_buf);
3366 buffer_free(order_buf);
3367 buffer_free(sql_buf);
3368 if (defaultselhash) jsonObjectFree(defaultselhash);
3373 // Build an ORDER BY clause, if there is one
3375 jsonIterator* class_itr = jsonNewIterator( order_hash );
3376 while ( (snode = jsonIteratorNext( class_itr )) ) {
3378 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
3381 if ( snode->type == JSON_HASH ) {
3383 jsonIterator* order_itr = jsonNewIterator( snode );
3384 while ( (onode = jsonIteratorNext( order_itr )) ) {
3386 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ))
3389 const char* direction = NULL;
3390 if ( onode->type == JSON_HASH ) {
3391 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3392 string = searchFieldTransform(
3394 oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ),
3398 osrfAppSessionStatus(
3400 OSRF_STATUS_INTERNALSERVERERROR,
3401 "osrfMethodException",
3403 "Severe query error in ORDER BY clause -- see error log for more details"
3405 jsonIteratorFree( order_itr );
3406 jsonIteratorFree( class_itr );
3408 buffer_free(having_buf);
3409 buffer_free(group_buf);
3410 buffer_free(order_buf);
3411 buffer_free(sql_buf);
3412 if (defaultselhash) jsonObjectFree(defaultselhash);
3416 growing_buffer* field_buf = buffer_init(16);
3417 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3418 string = buffer_release(field_buf);
3421 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3422 const char* dir = jsonObjectGetString(tmp_const);
3423 if (!strncasecmp(dir, "d", 1)) {
3424 direction = " DESC";
3431 string = strdup(order_itr->key);
3432 const char* dir = jsonObjectGetString(onode);
3433 if (!strncasecmp(dir, "d", 1)) {
3434 direction = " DESC";
3443 buffer_add(order_buf, ", ");
3446 buffer_add(order_buf, string);
3450 buffer_add(order_buf, direction);
3454 // jsonIteratorFree(order_itr);
3456 } else if ( snode->type == JSON_ARRAY ) {
3458 jsonIterator* order_itr = jsonNewIterator( snode );
3459 while ( (onode = jsonIteratorNext( order_itr )) ) {
3461 const char* _f = jsonObjectGetString( onode );
3463 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, _f))
3469 buffer_add(order_buf, ", ");
3472 buffer_add(order_buf, _f);
3475 // jsonIteratorFree(order_itr);
3478 // IT'S THE OOOOOOOOOOOLD STYLE!
3480 osrfLogError(OSRF_LOG_MARK, "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3482 osrfAppSessionStatus(
3484 OSRF_STATUS_INTERNALSERVERERROR,
3485 "osrfMethodException",
3487 "Severe query error -- see error log for more details"
3492 buffer_free(having_buf);
3493 buffer_free(group_buf);
3494 buffer_free(order_buf);
3495 buffer_free(sql_buf);
3496 if (defaultselhash) jsonObjectFree(defaultselhash);
3497 jsonIteratorFree(class_itr);
3502 // jsonIteratorFree(class_itr);
3506 string = buffer_release(group_buf);
3508 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
3509 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3510 OSRF_BUFFER_ADD( sql_buf, string );
3515 string = buffer_release(having_buf);
3518 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3519 OSRF_BUFFER_ADD( sql_buf, string );
3524 string = buffer_release(order_buf);
3527 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3528 OSRF_BUFFER_ADD( sql_buf, string );
3534 const char* str = jsonObjectGetString(limit);
3535 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
3539 const char* str = jsonObjectGetString(offset);
3540 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
3543 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3546 if (defaultselhash) jsonObjectFree(defaultselhash);
3548 return buffer_release(sql_buf);
3552 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
3554 const char* locale = osrf_message_get_last_locale();
3556 osrfHash* fields = osrfHashGet(meta, "fields");
3557 char* core_class = osrfHashGet(meta, "classname");
3559 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
3561 jsonObject* node = NULL;
3562 jsonObject* snode = NULL;
3563 jsonObject* onode = NULL;
3564 const jsonObject* _tmp = NULL;
3565 jsonObject* selhash = NULL;
3566 jsonObject* defaultselhash = NULL;
3568 growing_buffer* sql_buf = buffer_init(128);
3569 growing_buffer* select_buf = buffer_init(128);
3571 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
3572 defaultselhash = jsonNewObjectType(JSON_HASH);
3573 selhash = defaultselhash;
3576 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
3577 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
3578 jsonObject* flist = jsonObjectGetKey( selhash, core_class );
3583 osrfStringArray* keys = osrfHashKeys( fields );
3584 while ( (field = osrfStringArrayGetString(keys, i++)) ) {
3585 if( ! str_is_true( osrfHashGet( osrfHashGet( fields, field ), "virtual" ) ) )
3586 jsonObjectPush( flist, jsonNewObject( field ) );
3588 osrfStringArrayFree(keys);
3592 jsonIterator* class_itr = jsonNewIterator( selhash );
3593 while ( (snode = jsonIteratorNext( class_itr )) ) {
3595 char* cname = class_itr->key;
3596 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
3597 if (!idlClass) continue;
3599 if (strcmp(core_class,class_itr->key)) {
3600 if (!join_hash) continue;
3602 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
3604 jsonObjectFree(found);
3608 jsonObjectFree(found);
3611 jsonIterator* select_itr = jsonNewIterator( snode );
3612 while ( (node = jsonIteratorNext( select_itr )) ) {
3613 const char* item_str = jsonObjectGetString( node );
3614 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
3615 char* fname = osrfHashGet(field, "name");
3617 if (!field) continue;
3622 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
3627 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
3628 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
3631 i18n = osrfHashGet(field, "i18n");
3633 if( str_is_true( i18n ) ) {
3634 char* pkey = osrfHashGet(idlClass, "primarykey");
3635 char* tname = osrfHashGet(idlClass, "tablename");
3637 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);
3639 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3642 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3646 jsonIteratorFree(select_itr);
3649 jsonIteratorFree(class_itr);
3651 char* col_list = buffer_release(select_buf);
3652 char* table = getSourceDefinition(meta);
3654 table = strdup( "(null)" );
3656 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
3661 char* join_clause = searchJOIN( join_hash, meta );
3662 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
3663 OSRF_BUFFER_ADD(sql_buf, join_clause);
3667 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
3668 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
3670 buffer_add(sql_buf, " WHERE ");
3672 char* pred = searchWHERE( search_hash, meta, AND_OP_JOIN, ctx );
3674 osrfAppSessionStatus(
3676 OSRF_STATUS_INTERNALSERVERERROR,
3677 "osrfMethodException",
3679 "Severe query error -- see error log for more details"
3681 buffer_free(sql_buf);
3682 if(defaultselhash) jsonObjectFree(defaultselhash);
3685 buffer_add(sql_buf, pred);
3690 char* string = NULL;
3691 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
3693 growing_buffer* order_buf = buffer_init(128);
3696 jsonIterator* class_itr = jsonNewIterator( _tmp );
3697 while ( (snode = jsonIteratorNext( class_itr )) ) {
3699 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
3702 if ( snode->type == JSON_HASH ) {
3704 jsonIterator* order_itr = jsonNewIterator( snode );
3705 while ( (onode = jsonIteratorNext( order_itr )) ) {
3707 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ))
3710 char* direction = NULL;
3711 if ( onode->type == JSON_HASH ) {
3712 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3713 string = searchFieldTransform(
3715 oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ),
3719 osrfAppSessionStatus(
3721 OSRF_STATUS_INTERNALSERVERERROR,
3722 "osrfMethodException",
3724 "Severe query error in ORDER BY clause -- see error log for more details"
3726 jsonIteratorFree( order_itr );
3727 jsonIteratorFree( class_itr );
3728 buffer_free( order_buf );
3729 buffer_free( sql_buf );
3730 if( defaultselhash ) jsonObjectFree( defaultselhash );
3734 growing_buffer* field_buf = buffer_init(16);
3735 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3736 string = buffer_release(field_buf);
3739 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
3740 const char* dir = jsonObjectGetString(_tmp);
3741 if (!strncasecmp(dir, "d", 1)) {
3742 direction = " DESC";
3749 string = strdup(order_itr->key);
3750 const char* dir = jsonObjectGetString(onode);
3751 if (!strncasecmp(dir, "d", 1)) {
3752 direction = " DESC";
3761 buffer_add(order_buf, ", ");
3764 buffer_add(order_buf, string);
3768 buffer_add(order_buf, direction);
3773 jsonIteratorFree(order_itr);
3776 const char* str = jsonObjectGetString(snode);
3777 buffer_add(order_buf, str);
3783 jsonIteratorFree(class_itr);
3785 string = buffer_release(order_buf);
3788 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3789 OSRF_BUFFER_ADD( sql_buf, string );
3795 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
3796 const char* str = jsonObjectGetString(_tmp);
3804 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
3806 const char* str = jsonObjectGetString(_tmp);
3815 if (defaultselhash) jsonObjectFree(defaultselhash);
3817 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3818 return buffer_release(sql_buf);
3821 int doJSONSearch ( osrfMethodContext* ctx ) {
3822 if(osrfMethodVerifyContext( ctx )) {
3823 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
3827 osrfLogDebug(OSRF_LOG_MARK, "Recieved query request");
3832 dbhandle = writehandle;
3834 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
3838 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
3839 flags |= SELECT_DISTINCT;
3841 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
3842 flags |= DISABLE_I18N;
3844 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
3847 jsonObjectGetKey( hash, "select" ),
3848 jsonObjectGetKey( hash, "from" ),
3849 jsonObjectGetKey( hash, "where" ),
3850 jsonObjectGetKey( hash, "having" ),
3851 jsonObjectGetKey( hash, "order_by" ),
3852 jsonObjectGetKey( hash, "limit" ),
3853 jsonObjectGetKey( hash, "offset" ),
3862 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
3863 dbi_result result = dbi_conn_query(dbhandle, sql);
3866 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
3868 if (dbi_result_first_row(result)) {
3869 /* JSONify the result */
3870 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
3873 jsonObject* return_val = oilsMakeJSONFromResult( result );
3874 osrfAppRespond( ctx, return_val );
3875 jsonObjectFree( return_val );
3876 } while (dbi_result_next_row(result));
3879 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
3882 osrfAppRespondComplete( ctx, NULL );
3884 /* clean up the query */
3885 dbi_result_free(result);
3889 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
3890 osrfAppSessionStatus(
3892 OSRF_STATUS_INTERNALSERVERERROR,
3893 "osrfMethodException",
3895 "Severe query error -- see error log for more details"
3903 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
3904 const jsonObject* params, int* err ) {
3907 dbhandle = writehandle;
3909 osrfHash* links = osrfHashGet(meta, "links");
3910 osrfHash* fields = osrfHashGet(meta, "fields");
3911 char* core_class = osrfHashGet(meta, "classname");
3912 char* pkey = osrfHashGet(meta, "primarykey");
3914 const jsonObject* _tmp;
3916 jsonObject* search_hash = jsonObjectGetIndex(params, 0);
3917 jsonObject* order_hash = jsonObjectGetIndex(params, 1);
3919 char* sql = buildSELECT( search_hash, order_hash, meta, ctx );
3921 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
3926 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
3928 dbi_result result = dbi_conn_query(dbhandle, sql);
3929 if( NULL == result ) {
3930 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
3931 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
3932 osrfAppSessionStatus(
3934 OSRF_STATUS_INTERNALSERVERERROR,
3935 "osrfMethodException",
3937 "Severe query error -- see error log for more details"
3944 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
3947 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
3948 osrfHash* dedup = osrfNewHash();
3950 if (dbi_result_first_row(result)) {
3951 /* JSONify the result */
3952 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
3954 obj = oilsMakeFieldmapperFromResult( result, meta );
3955 char* pkey_val = oilsFMGetString( obj, pkey );
3956 if ( osrfHashGet( dedup, pkey_val ) ) {
3957 jsonObjectFree(obj);
3960 osrfHashSet( dedup, pkey_val, pkey_val );
3961 jsonObjectPush(res_list, obj);
3963 } while (dbi_result_next_row(result));
3965 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
3969 osrfHashFree(dedup);
3970 /* clean up the query */
3971 dbi_result_free(result);
3974 if (res_list->size && order_hash) {
3975 _tmp = jsonObjectGetKeyConst( order_hash, "flesh" );
3977 int x = (int)jsonObjectGetNumber(_tmp);
3978 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
3980 const jsonObject* temp_blob;
3981 if ((temp_blob = jsonObjectGetKeyConst( order_hash, "flesh_fields" )) && x > 0) {
3983 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
3984 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
3986 osrfStringArray* link_fields = NULL;
3989 if (flesh_fields->size == 1) {
3990 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
3991 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
3996 link_fields = osrfNewStringArray(1);
3997 jsonIterator* _i = jsonNewIterator( flesh_fields );
3998 while ((_f = jsonIteratorNext( _i ))) {
3999 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4001 jsonIteratorFree(_i);
4006 jsonIterator* itr = jsonNewIterator( res_list );
4007 while ((cur = jsonIteratorNext( itr ))) {
4012 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4014 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4016 osrfHash* kid_link = osrfHashGet(links, link_field);
4017 if (!kid_link) continue;
4019 osrfHash* field = osrfHashGet(fields, link_field);
4020 if (!field) continue;
4022 osrfHash* value_field = field;
4024 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4025 if (!kid_idl) continue;
4027 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4028 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4031 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4032 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4035 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4037 if (link_map->size > 0) {
4038 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4041 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4046 osrfHashGet(kid_link, "class"),
4053 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4054 osrfHashGet(kid_link, "field"),
4055 osrfHashGet(kid_link, "class"),
4056 osrfHashGet(kid_link, "key"),
4057 osrfHashGet(kid_link, "reltype")
4060 jsonObject* fake_params = jsonNewObjectType(JSON_ARRAY);
4061 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH)); // search hash
4062 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH)); // order/flesh hash
4064 osrfLogDebug(OSRF_LOG_MARK, "Creating dummy params object...");
4066 const char* search_key = jsonObjectGetString(
4069 atoi( osrfHashGet(value_field, "array_position") )
4074 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4079 jsonObjectGetIndex(fake_params, 0),
4080 osrfHashGet(kid_link, "key"),
4081 jsonNewObject( search_key )
4085 jsonObjectGetIndex(fake_params, 1),
4087 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4091 jsonObjectSetKey( jsonObjectGetIndex(fake_params, 1), "flesh_fields", jsonObjectClone(flesh_blob) );
4093 if (jsonObjectGetKeyConst(order_hash, "order_by")) {
4095 jsonObjectGetIndex(fake_params, 1),
4097 jsonObjectClone(jsonObjectGetKeyConst(order_hash, "order_by"))
4101 if (jsonObjectGetKeyConst(order_hash, "select")) {
4103 jsonObjectGetIndex(fake_params, 1),
4105 jsonObjectClone(jsonObjectGetKeyConst(order_hash, "select"))
4109 jsonObject* kids = doFieldmapperSearch(ctx, kid_idl, fake_params, err);
4112 jsonObjectFree( fake_params );
4113 osrfStringArrayFree(link_fields);
4114 jsonIteratorFree(itr);
4115 jsonObjectFree(res_list);
4116 jsonObjectFree(flesh_blob);
4120 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4122 jsonObject* X = NULL;
4123 if ( link_map->size > 0 && kids->size > 0 ) {
4125 kids = jsonNewObjectType(JSON_ARRAY);
4127 jsonObject* _k_node;
4128 jsonIterator* _k = jsonNewIterator( X );
4129 while ((_k_node = jsonIteratorNext( _k ))) {
4135 (unsigned long)atoi(
4141 osrfHashGet(kid_link, "class")
4145 osrfStringArrayGetString( link_map, 0 )
4154 jsonIteratorFree(_k);
4157 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4158 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4161 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4162 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4166 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4167 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4170 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4171 jsonObjectClone( kids )
4176 jsonObjectFree(kids);
4180 jsonObjectFree( kids );
4181 jsonObjectFree( fake_params );
4183 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4184 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4188 jsonObjectFree( flesh_blob );
4189 osrfStringArrayFree(link_fields);
4190 jsonIteratorFree(itr);
4199 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
4201 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4203 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
4205 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
4208 if (!verifyObjectClass(ctx, target)) {
4213 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4214 osrfAppSessionStatus(
4216 OSRF_STATUS_BADREQUEST,
4217 "osrfMethodException",
4219 "No active transaction -- required for UPDATE"
4225 // The following test is harmless but redundant. If a class is
4226 // readonly, we don't register an update method for it.
4227 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4228 osrfAppSessionStatus(
4230 OSRF_STATUS_BADREQUEST,
4231 "osrfMethodException",
4233 "Cannot UPDATE readonly class"
4239 dbhandle = writehandle;
4241 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4243 // Set the last_xact_id
4244 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4246 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
4247 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4250 char* pkey = osrfHashGet(meta, "primarykey");
4251 osrfHash* fields = osrfHashGet(meta, "fields");
4253 char* id = oilsFMGetString( target, pkey );
4257 "%s updating %s object with %s = %s",
4259 osrfHashGet(meta, "fieldmapper"),
4264 growing_buffer* sql = buffer_init(128);
4265 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4270 osrfStringArray* field_list = osrfHashKeys( fields );
4271 while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
4273 osrfHash* field = osrfHashGet( fields, field_name );
4275 if(!( strcmp( field_name, pkey ) )) continue;
4276 if( str_is_true( osrfHashGet(osrfHashGet(fields,field_name), "virtual") ) )
4279 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4281 int value_is_numeric = 0; // boolean
4283 if (field_object && field_object->classname) {
4284 value = oilsFMGetString(
4286 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4289 value = jsonObjectToSimpleString( field_object );
4290 if( field_object && JSON_NUMBER == field_object->type )
4291 value_is_numeric = 1;
4294 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s", osrfHashGet(meta, "fieldmapper"), field_name, value);
4296 if (!field_object || field_object->type == JSON_NULL) {
4297 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) ) && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
4298 if (first) first = 0;
4299 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4300 buffer_fadd( sql, " %s = NULL", field_name );
4303 } else if ( value_is_numeric || !strcmp( get_primitive( field ), "number") ) {
4304 if (first) first = 0;
4305 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4307 const char* numtype = get_datatype( field );
4308 if ( !strncmp( numtype, "INT", 3 ) ) {
4309 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
4310 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
4311 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
4313 // Must really be intended as a string, so quote it
4314 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4315 buffer_fadd( sql, " %s = %s", field_name, value );
4317 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4318 osrfAppSessionStatus(
4320 OSRF_STATUS_INTERNALSERVERERROR,
4321 "osrfMethodException",
4323 "Error quoting string -- please see the error log for more details"
4333 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
4336 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4337 if (first) first = 0;
4338 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4339 buffer_fadd( sql, " %s = %s", field_name, value );
4342 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4343 osrfAppSessionStatus(
4345 OSRF_STATUS_INTERNALSERVERERROR,
4346 "osrfMethodException",
4348 "Error quoting string -- please see the error log for more details"
4362 jsonObject* obj = jsonNewObject(id);
4364 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4365 dbi_conn_quote_string(dbhandle, &id);
4367 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
4369 char* query = buffer_release(sql);
4370 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
4372 dbi_result result = dbi_conn_query(dbhandle, query);
4376 jsonObjectFree(obj);
4377 obj = jsonNewObject(NULL);
4380 "%s ERROR updating %s object with %s = %s",
4382 osrfHashGet(meta, "fieldmapper"),
4393 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4395 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4397 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4398 osrfAppSessionStatus(
4400 OSRF_STATUS_BADREQUEST,
4401 "osrfMethodException",
4403 "No active transaction -- required for DELETE"
4409 // The following test is harmless but redundant. If a class is
4410 // readonly, we don't register a delete method for it.
4411 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4412 osrfAppSessionStatus(
4414 OSRF_STATUS_BADREQUEST,
4415 "osrfMethodException",
4417 "Cannot DELETE readonly class"
4423 dbhandle = writehandle;
4427 char* pkey = osrfHashGet(meta, "primarykey");
4435 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4436 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4441 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4444 if (!verifyObjectPCRUD( ctx, NULL )) {
4449 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4454 "%s deleting %s object with %s = %s",
4456 osrfHashGet(meta, "fieldmapper"),
4461 obj = jsonNewObject(id);
4463 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4464 dbi_conn_quote_string(writehandle, &id);
4466 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4469 jsonObjectFree(obj);
4470 obj = jsonNewObject(NULL);
4473 "%s ERROR deleting %s object with %s = %s",
4475 osrfHashGet(meta, "fieldmapper"),
4488 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4489 if(!(result && meta)) return jsonNULL;
4491 jsonObject* object = jsonNewObject(NULL);
4492 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4494 osrfHash* fields = osrfHashGet(meta, "fields");
4496 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4500 char dt_string[256];
4504 int columnIndex = 1;
4506 unsigned short type;
4507 const char* columnName;
4509 /* cycle through the column list */
4510 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4512 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4514 fmIndex = -1; // reset the position
4516 /* determine the field type and storage attributes */
4517 type = dbi_result_get_field_type(result, columnName);
4518 attr = dbi_result_get_field_attribs(result, columnName);
4520 /* fetch the fieldmapper index */
4521 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4523 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4526 const char* pos = (char*)osrfHashGet(_f, "array_position");
4527 if ( !pos ) continue;
4529 fmIndex = atoi( pos );
4530 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
4535 if (dbi_result_field_is_null(result, columnName)) {
4536 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
4541 case DBI_TYPE_INTEGER :
4543 if( attr & DBI_INTEGER_SIZE8 )
4544 jsonObjectSetIndex( object, fmIndex,
4545 jsonNewNumberObject(dbi_result_get_longlong(result, columnName)));
4547 jsonObjectSetIndex( object, fmIndex,
4548 jsonNewNumberObject(dbi_result_get_int(result, columnName)));
4552 case DBI_TYPE_DECIMAL :
4553 jsonObjectSetIndex( object, fmIndex,
4554 jsonNewNumberObject(dbi_result_get_double(result, columnName)));
4557 case DBI_TYPE_STRING :
4563 jsonNewObject( dbi_result_get_string(result, columnName) )
4568 case DBI_TYPE_DATETIME :
4570 memset(dt_string, '\0', sizeof(dt_string));
4571 memset(&gmdt, '\0', sizeof(gmdt));
4573 _tmp_dt = dbi_result_get_datetime(result, columnName);
4576 if (!(attr & DBI_DATETIME_DATE)) {
4577 gmtime_r( &_tmp_dt, &gmdt );
4578 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4579 } else if (!(attr & DBI_DATETIME_TIME)) {
4580 localtime_r( &_tmp_dt, &gmdt );
4581 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4583 localtime_r( &_tmp_dt, &gmdt );
4584 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4587 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
4591 case DBI_TYPE_BINARY :
4592 osrfLogError( OSRF_LOG_MARK,
4593 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4601 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
4602 if(!result) return jsonNULL;
4604 jsonObject* object = jsonNewObject(NULL);
4607 char dt_string[256];
4611 int columnIndex = 1;
4613 unsigned short type;
4614 const char* columnName;
4616 /* cycle through the column list */
4617 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4619 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4621 fmIndex = -1; // reset the position
4623 /* determine the field type and storage attributes */
4624 type = dbi_result_get_field_type(result, columnName);
4625 attr = dbi_result_get_field_attribs(result, columnName);
4627 if (dbi_result_field_is_null(result, columnName)) {
4628 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
4633 case DBI_TYPE_INTEGER :
4635 if( attr & DBI_INTEGER_SIZE8 )
4636 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_longlong(result, columnName)) );
4638 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_int(result, columnName)) );
4641 case DBI_TYPE_DECIMAL :
4642 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_double(result, columnName)) );
4645 case DBI_TYPE_STRING :
4646 jsonObjectSetKey( object, columnName, jsonNewObject(dbi_result_get_string(result, columnName)) );
4649 case DBI_TYPE_DATETIME :
4651 memset(dt_string, '\0', sizeof(dt_string));
4652 memset(&gmdt, '\0', sizeof(gmdt));
4654 _tmp_dt = dbi_result_get_datetime(result, columnName);
4657 if (!(attr & DBI_DATETIME_DATE)) {
4658 gmtime_r( &_tmp_dt, &gmdt );
4659 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4660 } else if (!(attr & DBI_DATETIME_TIME)) {
4661 localtime_r( &_tmp_dt, &gmdt );
4662 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4664 localtime_r( &_tmp_dt, &gmdt );
4665 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4668 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
4671 case DBI_TYPE_BINARY :
4672 osrfLogError( OSRF_LOG_MARK,
4673 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4681 // Interpret a string as true or false
4682 static int str_is_true( const char* str ) {
4683 if( NULL == str || strcasecmp( str, "true" ) )
4689 // Interpret a jsonObject as true or false
4690 static int obj_is_true( const jsonObject* obj ) {
4693 else switch( obj->type )
4701 if( strcasecmp( obj->value.s, "true" ) )
4705 case JSON_NUMBER : // Support 1/0 for perl's sake
4706 if( jsonObjectGetNumber( obj ) == 1.0 )
4715 // Translate a numeric code into a text string identifying a type of
4716 // jsonObject. To be used for building error messages.
4717 static const char* json_type( int code ) {
4723 return "JSON_ARRAY";
4725 return "JSON_STRING";
4727 return "JSON_NUMBER";
4733 return "(unrecognized)";
4737 // Extract the "primitive" attribute from an IDL field definition.
4738 // If we haven't initialized the app, then we must be running in
4739 // some kind of testbed. In that case, default to "string".
4740 static const char* get_primitive( osrfHash* field ) {
4741 const char* s = osrfHashGet( field, "primitive" );
4743 if( child_initialized )
4746 "%s ERROR No \"datatype\" attribute for field \"%s\"",
4748 osrfHashGet( field, "name" )
4756 // Extract the "datatype" attribute from an IDL field definition.
4757 // If we haven't initialized the app, then we must be running in
4758 // some kind of testbed. In that case, default to to NUMERIC,
4759 // since we look at the datatype only for numbers.
4760 static const char* get_datatype( osrfHash* field ) {
4761 const char* s = osrfHashGet( field, "datatype" );
4763 if( child_initialized )
4766 "%s ERROR No \"datatype\" attribute for field \"%s\"",
4768 osrfHashGet( field, "name" )
4777 If the input string is potentially a valid SQL identifier, return 1.
4780 Purpose: to prevent certain kinds of SQL injection. To that end we
4781 don't necessarily need to follow all the rules exactly, such as requiring
4782 that the first character not be a digit.
4784 We allow leading and trailing white space. In between, we do not allow
4785 punctuation (except for underscores and dollar signs), control
4786 characters, or embedded white space.
4788 More pedantically we should allow quoted identifiers containing arbitrary
4789 characters, but for the foreseeable future such quoted identifiers are not
4790 likely to be an issue.
4792 static int is_identifier( const char* s) {
4796 // Skip leading white space
4797 while( isspace( (unsigned char) *s ) )
4801 return 0; // Nothing but white space? Not okay.
4803 // Check each character until we reach white space or
4804 // end-of-string. Letters, digits, underscores, and
4805 // dollar signs are okay. With the exception of periods
4806 // (as in schema.identifier), control characters and other
4807 // punctuation characters are not okay. Anything else
4808 // is okay -- it could for example be part of a multibyte
4809 // UTF8 character such as a letter with diacritical marks,
4810 // and those are allowed.
4812 if( isalnum( (unsigned char) *s )
4816 ; // Fine; keep going
4817 else if( ispunct( (unsigned char) *s )
4818 || iscntrl( (unsigned char) *s ) )
4821 } while( *s && ! isspace( (unsigned char) *s ) );
4823 // If we found any white space in the above loop,
4824 // the rest had better be all white space.
4826 while( isspace( (unsigned char) *s ) )
4830 return 0; // White space was embedded within non-white space
4836 Determine whether to accept a character string as a comparison operator.
4837 Return 1 if it's good, or 0 if it's bad.
4839 We don't validate it for real. We just make sure that it doesn't contain
4840 any semicolons or white space (with a special exception for the
4841 "SIMILAR TO" operator). The idea is to block certain kinds of SQL
4842 injection. If it has no semicolons or white space but it's still not a
4843 valid operator, then the database will complain.
4845 Another approach would be to compare the string against a short list of
4846 approved operators. We don't do that because we want to allow custom
4847 operators like ">100*", which would be difficult or impossible to
4848 express otherwise in a JSON query.
4850 static int is_good_operator( const char* op ) {
4851 if( !op ) return 0; // Sanity check
4855 if( isspace( (unsigned char) *s ) ) {
4856 // Special exception for SIMILAR TO. Someday we might make
4857 // exceptions for IS DISTINCT FROM and IS NOT DISTINCT FROM.
4858 if( !strcasecmp( op, "similar to" ) )
4863 else if( ';' == *s )