]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/c-apps/oils_cstore.c
For the id_list methods: build the SELECT clause directly
[Evergreen.git] / Open-ILS / src / c-apps / oils_cstore.c
1 #include <ctype.h>
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"
9 #include <dbi/dbi.h>
10
11 #include <time.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <unistd.h>
15
16 #ifdef RSTORE
17 #  define MODULENAME "open-ils.reporter-store"
18 #else
19 #  ifdef PCRUD
20 #    define MODULENAME "open-ils.pcrud"
21 #  else
22 #    define MODULENAME "open-ils.cstore"
23 #  endif
24 #endif
25
26 #define SUBSELECT       4
27 #define DISABLE_I18N    2
28 #define SELECT_DISTINCT 1
29 #define AND_OP_JOIN     0
30 #define OR_OP_JOIN      1
31
32 int osrfAppChildInit();
33 int osrfAppInitialize();
34 void osrfAppChildExit();
35
36 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
37
38 int beginTransaction ( osrfMethodContext* );
39 int commitTransaction ( osrfMethodContext* );
40 int rollbackTransaction ( osrfMethodContext* );
41
42 int setSavepoint ( osrfMethodContext* );
43 int releaseSavepoint ( osrfMethodContext* );
44 int rollbackSavepoint ( osrfMethodContext* );
45
46 int doJSONSearch ( osrfMethodContext* );
47
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* ctx, osrfHash* meta,
54                 jsonObject* where_hash, jsonObject* query_hash, int* err );
55 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
56 static jsonObject* oilsMakeJSONFromResult( dbi_result );
57
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* );
70
71 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
72
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 );
83
84 #ifdef PCRUD
85 static jsonObject* verifyUserPCRUD( osrfMethodContext* );
86 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
87 static jsonObject* single_hash( const char* key, const char* value );
88 #endif
89
90 static int child_initialized = 0;   /* boolean */
91
92 static dbi_conn writehandle; /* our MASTER db connection */
93 static dbi_conn dbhandle; /* our CURRENT db connection */
94 //static osrfHash * readHandles;
95 static jsonObject* const jsonNULL = NULL; // 
96 static int max_flesh_depth = 100;
97
98 /* called when this process is about to exit */
99 void osrfAppChildExit() {
100     osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
101
102     int same = 0;
103     if (writehandle == dbhandle) same = 1;
104     if (writehandle) {
105         dbi_conn_query(writehandle, "ROLLBACK;");
106         dbi_conn_close(writehandle);
107         writehandle = NULL;
108     }
109     if (dbhandle && !same)
110         dbi_conn_close(dbhandle);
111
112     // XXX add cleanup of readHandles whenever that gets used
113
114     return;
115 }
116
117 int osrfAppInitialize() {
118
119     osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
120     osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
121
122     if (!oilsIDLInit( osrf_settings_host_value("/IDL") )) return 1; /* return non-zero to indicate error */
123
124     growing_buffer* method_name = buffer_init(64);
125 #ifndef PCRUD
126     // Generic search thingy
127     buffer_add(method_name, MODULENAME);
128         buffer_add(method_name, ".json_query");
129         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
130                                                    "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
131 #endif
132
133     // first we register all the transaction and savepoint methods
134     buffer_reset(method_name);
135         OSRF_BUFFER_ADD(method_name, MODULENAME);
136         OSRF_BUFFER_ADD(method_name, ".transaction.begin");
137         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
138                                                    "beginTransaction", "", 0, 0 );
139
140     buffer_reset(method_name);
141         OSRF_BUFFER_ADD(method_name, MODULENAME);
142         OSRF_BUFFER_ADD(method_name, ".transaction.commit");
143         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
144                                                    "commitTransaction", "", 0, 0 );
145
146     buffer_reset(method_name);
147         OSRF_BUFFER_ADD(method_name, MODULENAME);
148         OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
149         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
150                                                    "rollbackTransaction", "", 0, 0 );
151
152     buffer_reset(method_name);
153         OSRF_BUFFER_ADD(method_name, MODULENAME);
154         OSRF_BUFFER_ADD(method_name, ".savepoint.set");
155         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
156                                                    "setSavepoint", "", 1, 0 );
157
158     buffer_reset(method_name);
159         OSRF_BUFFER_ADD(method_name, MODULENAME);
160         OSRF_BUFFER_ADD(method_name, ".savepoint.release");
161         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
162                                                    "releaseSavepoint", "", 1, 0 );
163
164     buffer_reset(method_name);
165         OSRF_BUFFER_ADD(method_name, MODULENAME);
166         OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
167         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
168                                                    "rollbackSavepoint", "", 1, 0 );
169
170         static const char* global_method[] = {
171                 "create",
172                 "retrieve",
173                 "update",
174                 "delete",
175                 "search",
176                 "id_list"
177         };
178         const int global_method_count
179                 = sizeof( global_method ) / sizeof ( global_method[0] );
180         
181     int c_index = 0; 
182     char* classname;
183     osrfStringArray* classes = osrfHashKeys( oilsIDL() );
184     osrfLogDebug(OSRF_LOG_MARK, "%d classes loaded", classes->size );
185     osrfLogDebug(OSRF_LOG_MARK,
186                 "At most %d methods will be generated", classes->size * global_method_count);
187
188         // For each class in IDL...
189     while ( (classname = osrfStringArrayGetString(classes, c_index++)) ) {
190         osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
191
192         osrfHash* idlClass = osrfHashGet(oilsIDL(), classname);
193
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);
196             continue;
197         }
198
199                 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
200                         osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
201                         continue;
202                 }
203
204                 // Look up some other attributes of the current class
205                 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
206                 if( !idlClass_fieldmapper ) {
207                         osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL", classname );
208                         continue;
209                 }
210
211 #ifdef PCRUD
212                 osrfHash* idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
213                 if (!idlClass_permacrud) {
214                         osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no permacrud in IDL", classname );
215                         continue;
216                 }
217 #endif
218                 const char* readonly = osrfHashGet(idlClass, "readonly");
219
220         int i;
221         for( i = 0; i < global_method_count; ++i ) {  // for each global method
222             const char* method_type = global_method[ i ];
223             osrfLogDebug(OSRF_LOG_MARK,
224                 "Using files to build %s class methods for %s", method_type, classname);
225
226 #ifdef PCRUD
227             const char* tmp_method = method_type;
228             if ( *tmp_method == 'i' || *tmp_method == 's') {
229                 tmp_method = "retrieve";
230             }
231             if (!osrfHashGet( idlClass_permacrud, tmp_method )) continue;
232 #endif
233
234             if (    str_is_true( readonly ) &&
235                     ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd')
236                ) continue;
237
238             buffer_reset( method_name );
239 #ifdef PCRUD
240             buffer_fadd(method_name, "%s.%s.%s", MODULENAME, method_type, classname);
241 #else
242             char* st_tmp = NULL;
243             char* part = NULL;
244             char* _fm = strdup( idlClass_fieldmapper );
245             part = strtok_r(_fm, ":", &st_tmp);
246
247             buffer_fadd(method_name, "%s.direct.%s", MODULENAME, part);
248
249             while ((part = strtok_r(NULL, ":", &st_tmp))) {
250                                 OSRF_BUFFER_ADD_CHAR(method_name, '.');
251                                 OSRF_BUFFER_ADD(method_name, part);
252             }
253                         OSRF_BUFFER_ADD_CHAR(method_name, '.');
254                         OSRF_BUFFER_ADD(method_name, method_type);
255             free(_fm);
256 #endif
257
258             char* method = buffer_data(method_name);
259
260             int flags = 0;
261             if (*method_type == 'i' || *method_type == 's') {
262                 flags = flags | OSRF_METHOD_STREAMING;
263             }
264
265                         osrfHash* method_meta = osrfNewHash();
266                         osrfHashSet( method_meta, idlClass, "class");
267                         osrfHashSet( method_meta, method, "methodname" );
268                         osrfHashSet( method_meta, strdup(method_type), "methodtype" );
269
270                         osrfAppRegisterExtendedMethod(
271                     MODULENAME,
272                     method,
273                     "dispatchCRUDMethod",
274                     "",
275                     1,
276                     flags,
277                     (void*)method_meta
278                     );
279
280             free(method);
281         } // end for each global method
282     } // end for each class in IDL
283
284         buffer_free( method_name );
285         osrfStringArrayFree( classes );
286         
287     return 0;
288 }
289
290 static char* getSourceDefinition( osrfHash* class ) {
291
292         char* tabledef = osrfHashGet(class, "tablename");
293
294         if (tabledef) {
295                 tabledef = strdup(tabledef);
296         } else {
297                 tabledef = osrfHashGet(class, "source_definition");
298                 if( tabledef ) {
299                         growing_buffer* tablebuf = buffer_init(128);
300                         buffer_fadd( tablebuf, "(%s)", tabledef );
301                         tabledef = buffer_release(tablebuf);
302                 } else {
303                         const char* classname = osrfHashGet( class, "classname" );
304                         if( !classname )
305                                 classname = "???";
306                         osrfLogError(
307                                 OSRF_LOG_MARK,
308                                 "%s ERROR No tablename or source_definition for class \"%s\"",
309                                 MODULENAME,
310                                 classname
311                         );
312                 }
313         }
314
315         return tabledef;
316 }
317
318 /**
319  * Connects to the database 
320  */
321 int osrfAppChildInit() {
322
323     osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
324     dbi_initialize(NULL);
325     osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
326
327     char* driver        = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
328     char* user  = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
329     char* host  = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
330     char* port  = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
331     char* db    = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
332     char* pw    = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
333     char* md    = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion", MODULENAME);
334
335     osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
336     writehandle = dbi_conn_new(driver);
337
338     if(!writehandle) {
339         osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
340         return -1;
341     }
342     osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
343
344     osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database.  host=%s, "
345             "port=%s, user=%s, pw=%s, db=%s", MODULENAME, host, port, user, pw, db );
346
347     if(host) dbi_conn_set_option(writehandle, "host", host );
348     if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
349     if(user) dbi_conn_set_option(writehandle, "username", user);
350     if(pw) dbi_conn_set_option(writehandle, "password", pw );
351     if(db) dbi_conn_set_option(writehandle, "dbname", db );
352
353     if(md) max_flesh_depth = atoi(md);
354     if(max_flesh_depth < 0) max_flesh_depth = 1;
355     if(max_flesh_depth > 1000) max_flesh_depth = 1000;
356
357     free(user);
358     free(host);
359     free(port);
360     free(db);
361     free(pw);
362
363     const char* err;
364     if (dbi_conn_connect(writehandle) < 0) {
365         sleep(1);
366         if (dbi_conn_connect(writehandle) < 0) {
367             dbi_conn_error(writehandle, &err);
368             osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
369             return -1;
370         }
371     }
372
373     osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
374
375     int i = 0; 
376     char* classname;
377     osrfStringArray* classes = osrfHashKeys( oilsIDL() );
378
379     while ( (classname = osrfStringArrayGetString(classes, i++)) ) {
380         osrfHash* class = osrfHashGet( oilsIDL(), classname );
381         osrfHash* fields = osrfHashGet( class, "fields" );
382
383                 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
384                         osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
385                         continue;
386                 }
387
388         char* tabledef = getSourceDefinition(class);
389                 if( !tabledef )
390                         tabledef = strdup( "(null)" );
391
392         growing_buffer* sql_buf = buffer_init(32);
393         buffer_fadd( sql_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
394
395         free(tabledef);
396
397         char* sql = buffer_release(sql_buf);
398         osrfLogDebug(OSRF_LOG_MARK, "%s Investigatory SQL = %s", MODULENAME, sql);
399
400         dbi_result result = dbi_conn_query(writehandle, sql);
401         free(sql);
402
403         if (result) {
404
405             int columnIndex = 1;
406             const char* columnName;
407             osrfHash* _f;
408             while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
409
410                 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
411
412                 /* fetch the fieldmapper index */
413                 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
414
415                                         osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", (char*)columnName);
416
417                                         /* determine the field type and storage attributes */
418
419                                         switch( dbi_result_get_field_type_idx(result, columnIndex) ) {
420
421                                                 case DBI_TYPE_INTEGER : {
422
423                                                         if ( !osrfHashGet(_f, "primitive") )
424                                                                 osrfHashSet(_f,"number", "primitive");
425
426                                                         int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
427                                                         if( attr & DBI_INTEGER_SIZE8 ) 
428                                                                 osrfHashSet(_f,"INT8", "datatype");
429                                                         else 
430                                                                 osrfHashSet(_f,"INT", "datatype");
431                                                         break;
432                                                 }
433                         case DBI_TYPE_DECIMAL :
434                             if ( !osrfHashGet(_f, "primitive") )
435                                 osrfHashSet(_f,"number", "primitive");
436
437                             osrfHashSet(_f,"NUMERIC", "datatype");
438                             break;
439
440                         case DBI_TYPE_STRING :
441                             if ( !osrfHashGet(_f, "primitive") )
442                                 osrfHashSet(_f,"string", "primitive");
443                             osrfHashSet(_f,"TEXT", "datatype");
444                             break;
445
446                         case DBI_TYPE_DATETIME :
447                             if ( !osrfHashGet(_f, "primitive") )
448                                 osrfHashSet(_f,"string", "primitive");
449
450                             osrfHashSet(_f,"TIMESTAMP", "datatype");
451                             break;
452
453                         case DBI_TYPE_BINARY :
454                             if ( !osrfHashGet(_f, "primitive") )
455                                 osrfHashSet(_f,"string", "primitive");
456
457                             osrfHashSet(_f,"BYTEA", "datatype");
458                     }
459
460                     osrfLogDebug(
461                             OSRF_LOG_MARK,
462                             "Setting [%s] to primitive [%s] and datatype [%s]...",
463                             (char*)columnName,
464                             osrfHashGet(_f, "primitive"),
465                             osrfHashGet(_f, "datatype")
466                             );
467                 }
468                                 ++columnIndex;
469                         } // end while loop for traversing result
470                         dbi_result_free(result);
471         } else {
472             osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", (char*)classname);
473         }
474     }
475
476     osrfStringArrayFree(classes);
477
478         child_initialized = 1;
479     return 0;
480 }
481
482 /*
483   This function is a sleazy hack intended *only* for testing and
484   debugging.  Any real server process should initialize the 
485   database connection by calling osrfAppChildInit().
486 */
487 void set_cstore_dbi_conn( dbi_conn conn ) {
488         dbhandle = writehandle = conn;
489 }
490
491 void userDataFree( void* blob ) {
492     osrfHashFree( (osrfHash*)blob );
493     return;
494 }
495
496 static void sessionDataFree( char* key, void* item ) {
497     if (!(strcmp(key,"xact_id"))) {
498         if (writehandle)
499             dbi_conn_query(writehandle, "ROLLBACK;");
500         free(item);
501     }
502
503     return;
504 }
505
506 int beginTransaction ( osrfMethodContext* ctx ) {
507         if(osrfMethodVerifyContext( ctx )) {
508                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
509                 return -1;
510         }
511
512 #ifdef PCRUD
513     jsonObject* user = verifyUserPCRUD( ctx );
514     if (!user) return -1;
515     jsonObjectFree(user);
516 #endif
517
518     dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
519     if (!result) {
520         osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
521         osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error starting transaction" );
522         return -1;
523     } else {
524         jsonObject* ret = jsonNewObject(ctx->session->session_id);
525         osrfAppRespondComplete( ctx, ret );
526         jsonObjectFree(ret);
527
528         if (!ctx->session->userData) {
529             ctx->session->userData = osrfNewHash();
530             osrfHashSetCallback((osrfHash*)ctx->session->userData, &sessionDataFree);
531         }
532
533         osrfHashSet( (osrfHash*)ctx->session->userData, strdup( ctx->session->session_id ), "xact_id" );
534         ctx->session->userDataFree = &userDataFree;
535
536     }
537     return 0;
538 }
539
540 int setSavepoint ( osrfMethodContext* ctx ) {
541         if(osrfMethodVerifyContext( ctx )) {
542                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
543                 return -1;
544         }
545
546     int spNamePos = 0;
547 #ifdef PCRUD
548     spNamePos = 1;
549     jsonObject* user = verifyUserPCRUD( ctx );
550     if (!user) return -1;
551     jsonObjectFree(user);
552 #endif
553
554     if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
555         osrfAppSessionStatus(
556                 ctx->session,
557                 OSRF_STATUS_INTERNALSERVERERROR,
558                 "osrfMethodException",
559                 ctx->request,
560                 "No active transaction -- required for savepoints"
561                 );
562         return -1;
563     }
564
565         const char* spName = jsonObjectGetString(jsonObjectGetIndex(ctx->params, spNamePos));
566
567         dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
568         if (!result) {
569                 osrfLogError(
570                         OSRF_LOG_MARK,
571                         "%s: Error creating savepoint %s in transaction %s",
572                         MODULENAME,
573                         spName,
574                         osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
575                 );
576                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, 
577                                 "osrfMethodException", ctx->request, "Error creating savepoint" );
578                 return -1;
579         } else {
580                 jsonObject* ret = jsonNewObject(spName);
581                 osrfAppRespondComplete( ctx, ret );
582                 jsonObjectFree(ret);
583         }
584         return 0;
585 }
586
587 int releaseSavepoint ( osrfMethodContext* ctx ) {
588         if(osrfMethodVerifyContext( ctx )) {
589                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
590                 return -1;
591         }
592
593         int spNamePos = 0;
594 #ifdef PCRUD
595     spNamePos = 1;
596     jsonObject* user = verifyUserPCRUD( ctx );
597     if (!user) return -1;
598     jsonObjectFree(user);
599 #endif
600
601     if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
602         osrfAppSessionStatus(
603                 ctx->session,
604                 OSRF_STATUS_INTERNALSERVERERROR,
605                 "osrfMethodException",
606                 ctx->request,
607                 "No active transaction -- required for savepoints"
608                 );
609         return -1;
610     }
611
612         const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
613
614     dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
615     if (!result) {
616         osrfLogError(
617                 OSRF_LOG_MARK,
618                 "%s: Error releasing savepoint %s in transaction %s",
619                 MODULENAME,
620                 spName,
621                 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
622                 );
623                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
624                                 "osrfMethodException", ctx->request, "Error releasing savepoint" );
625         return -1;
626     } else {
627         jsonObject* ret = jsonNewObject(spName);
628         osrfAppRespondComplete( ctx, ret );
629         jsonObjectFree(ret);
630     }
631     return 0;
632 }
633
634 int rollbackSavepoint ( osrfMethodContext* ctx ) {
635         if(osrfMethodVerifyContext( ctx )) {
636                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
637                 return -1;
638         }
639
640         int spNamePos = 0;
641 #ifdef PCRUD
642     spNamePos = 1;
643     jsonObject* user = verifyUserPCRUD( ctx );
644     if (!user) return -1;
645     jsonObjectFree(user);
646 #endif
647
648     if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
649         osrfAppSessionStatus(
650                 ctx->session,
651                 OSRF_STATUS_INTERNALSERVERERROR,
652                 "osrfMethodException",
653                 ctx->request,
654                 "No active transaction -- required for savepoints"
655                 );
656         return -1;
657     }
658
659         const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
660
661     dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
662     if (!result) {
663         osrfLogError(
664                 OSRF_LOG_MARK,
665                 "%s: Error rolling back savepoint %s in transaction %s",
666                 MODULENAME,
667                 spName,
668                 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
669                 );
670                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, 
671                                 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
672         return -1;
673     } else {
674         jsonObject* ret = jsonNewObject(spName);
675         osrfAppRespondComplete( ctx, ret );
676         jsonObjectFree(ret);
677     }
678     return 0;
679 }
680
681 int commitTransaction ( osrfMethodContext* ctx ) {
682         if(osrfMethodVerifyContext( ctx )) {
683                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
684                 return -1;
685         }
686
687 #ifdef PCRUD
688     jsonObject* user = verifyUserPCRUD( ctx );
689     if (!user) return -1;
690     jsonObjectFree(user);
691 #endif
692
693     if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
694         osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to commit" );
695         return -1;
696     }
697
698     dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
699     if (!result) {
700         osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
701         osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error committing transaction" );
702         return -1;
703     } else {
704         osrfHashRemove(ctx->session->userData, "xact_id");
705         jsonObject* ret = jsonNewObject(ctx->session->session_id);
706         osrfAppRespondComplete( ctx, ret );
707         jsonObjectFree(ret);
708     }
709     return 0;
710 }
711
712 int rollbackTransaction ( osrfMethodContext* ctx ) {
713         if(osrfMethodVerifyContext( ctx )) {
714                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
715                 return -1;
716         }
717
718 #ifdef PCRUD
719     jsonObject* user = verifyUserPCRUD( ctx );
720     if (!user) return -1;
721     jsonObjectFree(user);
722 #endif
723
724     if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
725         osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to roll back" );
726         return -1;
727     }
728
729     dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
730     if (!result) {
731         osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
732         osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error rolling back transaction" );
733         return -1;
734     } else {
735         osrfHashRemove(ctx->session->userData, "xact_id");
736         jsonObject* ret = jsonNewObject(ctx->session->session_id);
737         osrfAppRespondComplete( ctx, ret );
738         jsonObjectFree(ret);
739     }
740     return 0;
741 }
742
743 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
744         if(osrfMethodVerifyContext( ctx )) {
745                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
746                 return -1;
747         }
748
749         osrfHash* meta = (osrfHash*) ctx->method->userData;
750     osrfHash* class_obj = osrfHashGet( meta, "class" );
751
752     int err = 0;
753
754     const char* methodtype = osrfHashGet(meta, "methodtype");
755     jsonObject * obj = NULL;
756
757     if (!strcmp(methodtype, "create")) {
758         obj = doCreate(ctx, &err);
759         osrfAppRespondComplete( ctx, obj );
760     }
761     else if (!strcmp(methodtype, "retrieve")) {
762         obj = doRetrieve(ctx, &err);
763         osrfAppRespondComplete( ctx, obj );
764     }
765     else if (!strcmp(methodtype, "update")) {
766         obj = doUpdate(ctx, &err);
767         osrfAppRespondComplete( ctx, obj );
768     }
769     else if (!strcmp(methodtype, "delete")) {
770         obj = doDelete(ctx, &err);
771         osrfAppRespondComplete( ctx, obj );
772     }
773     else if (!strcmp(methodtype, "search")) {
774
775                 jsonObject* where_clause;
776                 jsonObject* rest_of_query;
777
778 #ifdef PCRUD
779                 where_clause  = jsonObjectGetIndex( ctx->params, 1 );
780                 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
781 #else
782                 where_clause  = jsonObjectGetIndex( ctx->params, 0 );
783                 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
784 #endif
785
786                 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
787
788                 if(err) return err;
789
790                 jsonObject* cur = 0;
791                 unsigned long res_idx = 0;
792                 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
793 #ifdef PCRUD
794                         if(!verifyObjectPCRUD(ctx, cur)) continue;
795 #endif
796                         osrfAppRespond( ctx, cur );
797                 }
798                 osrfAppRespondComplete( ctx, NULL );
799
800         } else if (!strcmp(methodtype, "id_list")) {
801
802                 jsonObject* where_clause;
803                 jsonObject* rest_of_query;
804
805                 // We use the where clause without change.  But we need
806                 // to massage the rest of the query, so we work with a copy
807                 // of it instead of modifying the original.
808 #ifdef PCRUD
809                 where_clause  = jsonObjectGetIndex( ctx->params, 1 );
810                 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
811 #else
812                 where_clause  = jsonObjectGetIndex( ctx->params, 0 );
813                 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
814 #endif
815
816                 if ( rest_of_query ) {
817                         jsonObjectRemoveKey( rest_of_query, "select" );
818                         jsonObjectRemoveKey( rest_of_query, "no_i18n" );
819                         jsonObjectRemoveKey( rest_of_query, "flesh" );
820                         jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
821                 } else {
822                         rest_of_query = jsonNewObjectType( JSON_HASH );
823                 }
824
825                 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
826
827                 // Build a SELECT list containing just the primary key,
828                 // i.e. like { "classname":["keyname"] }
829                 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
830                 jsonObjectPush( col_list_obj,     // Load array with name of primary key
831                         jsonNewObject( osrfHashGet( class_obj, "primarykey" ) ) );
832                 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
833                 jsonObjectSetKey( select_clause, osrfHashGet( class_obj, "classname" ), col_list_obj );
834
835                 jsonObjectSetKey( rest_of_query, "select", select_clause );
836
837                 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
838
839                 jsonObjectFree( rest_of_query );
840                 if(err) return err;
841
842                 jsonObject* cur;
843                 unsigned long res_idx = 0;
844                 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
845 #ifdef PCRUD
846                         if(!verifyObjectPCRUD(ctx, cur)) continue;
847 #endif
848                         osrfAppRespond(
849                                 ctx,
850                                 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
851                                 );
852                 }
853                 osrfAppRespondComplete( ctx, NULL );
854
855     } else {
856         osrfAppRespondComplete( ctx, obj );
857     }
858
859     jsonObjectFree(obj);
860
861     return err;
862 }
863
864 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
865
866     int ret = 1;
867     osrfHash* meta = (osrfHash*) ctx->method->userData;
868     osrfHash* class = osrfHashGet( meta, "class" );
869
870     if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
871
872                 const char* temp_classname = param->classname;
873                 if( ! temp_classname )
874                         temp_classname = "(null)";
875
876         growing_buffer* msg = buffer_init(128);
877         buffer_fadd(
878                 msg,
879                 "%s: %s method for type %s was passed a %s",
880                 MODULENAME,
881                 osrfHashGet(meta, "methodtype"),
882                 osrfHashGet(class, "classname"),
883                 temp_classname
884                 );
885
886         char* m = buffer_release(msg);
887         osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException", ctx->request, m );
888
889         free(m);
890
891         return 0;
892     }
893
894 #ifdef PCRUD
895     ret = verifyObjectPCRUD( ctx, param );
896 #endif
897
898     return ret;
899 }
900
901 #ifdef PCRUD
902
903 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
904         const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
905     jsonObject* auth_object = jsonNewObject(auth);
906     jsonObject* user = oilsUtilsQuickReq("open-ils.auth","open-ils.auth.session.retrieve", auth_object);
907     jsonObjectFree(auth_object);
908
909     if (!user->classname || strcmp(user->classname, "au")) {
910
911         growing_buffer* msg = buffer_init(128);
912         buffer_fadd(
913             msg,
914             "%s: permacrud received a bad auth token: %s",
915             MODULENAME,
916             auth
917         );
918
919         char* m = buffer_release(msg);
920         osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException", ctx->request, m );
921
922         free(m);
923         jsonObjectFree(user);
924         user = jsonNULL;
925     }
926
927     return user;
928
929 }
930
931 static int verifyObjectPCRUD (  osrfMethodContext* ctx, const jsonObject* obj ) {
932
933         dbhandle = writehandle;
934
935         osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
936         osrfHash* class = osrfHashGet( method_metadata, "class" );
937         const char* method_type = osrfHashGet( method_metadata, "methodtype" );
938         int fetch = 0;
939
940         if ( ( *method_type == 's' || *method_type == 'i' ) ) {
941                 method_type = "retrieve"; // search and id_list are equivalant to retrieve for this
942         } else if ( *method_type == 'u' || *method_type == 'd' ) {
943                 fetch = 1; // MUST go to the db for the object for update and delete
944         }
945
946         osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
947
948     if (!pcrud) {
949         // No permacrud for this method type on this class
950
951         growing_buffer* msg = buffer_init(128);
952         buffer_fadd(
953             msg,
954             "%s: %s on class %s has no permacrud IDL entry",
955             MODULENAME,
956             osrfHashGet(method_metadata, "methodtype"),
957             osrfHashGet(class, "classname")
958         );
959
960         char* m = buffer_release(msg);
961         osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN, "osrfMethodException", ctx->request, m );
962
963         free(m);
964
965         return 0;
966     }
967
968     jsonObject* user = verifyUserPCRUD( ctx );
969     if (!user) return 0;
970
971     int userid = atoi( oilsFMGetString( user, "id" ) );
972     jsonObjectFree(user);
973
974     osrfStringArray* permission = osrfHashGet(pcrud, "permission");
975     osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
976     osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
977
978     osrfStringArray* context_org_array = osrfNewStringArray(1);
979
980     int err = 0;
981     char* pkey_value = NULL;
982         if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
983                 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions required, fetching top of the org tree" );
984
985                 // check for perm at top of org tree
986                 jsonObject* _tmp_params = single_hash( "parent_ou", NULL );
987                 jsonObject* _list = doFieldmapperSearch(ctx, osrfHashGet( oilsIDL(), "aou" ),
988                                 _tmp_params, NULL, &err);
989                 jsonObjectFree(_tmp_params);
990
991                 jsonObject* _tree_top = jsonObjectGetIndex(_list, 0);
992
993                 if (!_tree_top) {
994                         jsonObjectFree(_list);
995
996                         growing_buffer* msg = buffer_init(128);
997                         OSRF_BUFFER_ADD( msg, MODULENAME );
998                         OSRF_BUFFER_ADD( msg,
999                                 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1000
1001             char* m = buffer_release(msg);
1002             osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1003             free(m);
1004
1005             return 0;
1006         }
1007
1008         osrfStringArrayAdd( context_org_array, oilsFMGetString( _tree_top, "id" ) );
1009             osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", osrfStringArrayGetString(context_org_array, 0) );
1010
1011         jsonObjectFree(_list);
1012
1013     } else {
1014             osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, fetching context org ids" );
1015             const char* pkey = osrfHashGet(class, "primarykey");
1016         jsonObject *param = NULL;
1017
1018         if (obj->classname) {
1019             pkey_value = oilsFMGetString( obj, pkey );
1020             if (!fetch) param = jsonObjectClone(obj);
1021                 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s", pkey_value );
1022         } else {
1023             pkey_value = jsonObjectToSimpleString( obj );
1024             fetch = 1;
1025                 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value of %s and retrieving from the database", pkey_value );
1026         }
1027
1028                 if (fetch) {
1029                         jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1030                         jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1031                         jsonObjectFree(_tmp_params);
1032
1033                         param = jsonObjectExtractIndex(_list, 0);
1034                         jsonObjectFree(_list);
1035                 }
1036
1037         if (!param) {
1038             osrfLogDebug( OSRF_LOG_MARK, "Object not found in the database with primary key %s of %s", pkey, pkey_value );
1039
1040             growing_buffer* msg = buffer_init(128);
1041             buffer_fadd(
1042                 msg,
1043                 "%s: no object found with primary key %s of %s",
1044                 MODULENAME,
1045                 pkey,
1046                 pkey_value
1047             );
1048         
1049             char* m = buffer_release(msg);
1050             osrfAppSessionStatus(
1051                 ctx->session,
1052                 OSRF_STATUS_INTERNALSERVERERROR,
1053                 "osrfMethodException",
1054                 ctx->request,
1055                 m
1056             );
1057         
1058             free(m);
1059             if (pkey_value) free(pkey_value);
1060
1061             return 0;
1062         }
1063
1064         if (local_context->size > 0) {
1065                 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified", local_context->size);
1066             int i = 0;
1067             char* lcontext = NULL;
1068             while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1069                 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1070                     osrfLogDebug(
1071                     OSRF_LOG_MARK,
1072                     "adding class-local field %s (value: %s) to the context org list",
1073                     lcontext,
1074                     osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1075                 );
1076             }
1077         }
1078
1079         osrfStringArray* class_list;
1080
1081         if (foreign_context) {
1082             class_list = osrfHashKeys( foreign_context );
1083                 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_list->size);
1084
1085             if (class_list->size > 0) {
1086     
1087                 int i = 0;
1088                 char* class_name = NULL;
1089                 while ( (class_name = osrfStringArrayGetString(class_list, i++)) ) {
1090                     osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1091
1092                         osrfLogDebug(
1093                         OSRF_LOG_MARK,
1094                         "%d foreign context fields(s) specified for class %s",
1095                         ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1096                         class_name
1097                     );
1098     
1099                     char* foreign_pkey = osrfHashGet(fcontext, "field");
1100                     char* foreign_pkey_value = oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1101
1102                                         jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1103
1104                                         jsonObject* _list = doFieldmapperSearch(
1105                                                 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1106
1107                     jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1108                     jsonObjectFree(_tmp_params);
1109                     jsonObjectFree(_list);
1110  
1111                     osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1112
1113                     if (_fparam && jump_list) {
1114                         char* flink = NULL;
1115                         int k = 0;
1116                         while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1117                             free(foreign_pkey_value);
1118
1119                             osrfHash* foreign_link_hash = oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1120
1121                                                         foreign_pkey_value = oilsFMGetString(_fparam, flink);
1122                                                         foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1123
1124                                                         _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1125
1126                                                         _list = doFieldmapperSearch(
1127                                                                 ctx,
1128                                                                 osrfHashGet( oilsIDL(), osrfHashGet( foreign_link_hash, "class" ) ),
1129                                                                 _tmp_params,
1130                                                                 NULL,
1131                                                                 &err
1132                                                         );
1133
1134                             _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1135                             jsonObjectFree(_tmp_params);
1136                             jsonObjectFree(_list);
1137                         }
1138                     }
1139
1140            
1141                     if (!_fparam) {
1142
1143                         growing_buffer* msg = buffer_init(128);
1144                         buffer_fadd(
1145                             msg,
1146                             "%s: no object found with primary key %s of %s",
1147                             MODULENAME,
1148                             foreign_pkey,
1149                             foreign_pkey_value
1150                         );
1151                 
1152                         char* m = buffer_release(msg);
1153                         osrfAppSessionStatus(
1154                             ctx->session,
1155                             OSRF_STATUS_INTERNALSERVERERROR,
1156                             "osrfMethodException",
1157                             ctx->request,
1158                             m
1159                         );
1160
1161                         free(m);
1162                         osrfStringArrayFree(class_list);
1163                         free(foreign_pkey_value);
1164                         jsonObjectFree(param);
1165
1166                         return 0;
1167                     }
1168         
1169                     free(foreign_pkey_value);
1170     
1171                     int j = 0;
1172                     char* foreign_field = NULL;
1173                     while ( (foreign_field = osrfStringArrayGetString(osrfHashGet(fcontext,"context"), j++)) ) {
1174                         osrfStringArrayAdd( context_org_array, oilsFMGetString( _fparam, foreign_field ) );
1175                             osrfLogDebug(
1176                             OSRF_LOG_MARK,
1177                             "adding foreign class %s field %s (value: %s) to the context org list",
1178                             class_name,
1179                             foreign_field,
1180                             osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1181                         );
1182                     }
1183
1184                     jsonObjectFree(_fparam);
1185                 }
1186     
1187                 osrfStringArrayFree(class_list);
1188             }
1189         }
1190
1191         jsonObjectFree(param);
1192     }
1193
1194     char* context_org = NULL;
1195     char* perm = NULL;
1196     int OK = 0;
1197
1198     if (permission->size == 0) {
1199             osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1200         OK = 1;
1201     }
1202     
1203     int i = 0;
1204     while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1205         int j = 0;
1206         while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1207             dbi_result result;
1208
1209             if (pkey_value) {
1210                     osrfLogDebug(
1211                     OSRF_LOG_MARK,
1212                     "Checking object permission [%s] for user %d on object %s (class %s) at org %d",
1213                     perm,
1214                     userid,
1215                     pkey_value,
1216                     osrfHashGet(class, "classname"),
1217                     atoi(context_org)
1218                 );
1219
1220                 result = dbi_conn_queryf(
1221                     writehandle,
1222                     "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1223                     userid,
1224                     perm,
1225                     osrfHashGet(class, "classname"),
1226                     pkey_value,
1227                     atoi(context_org)
1228                 );
1229
1230                 if (result) {
1231                     osrfLogDebug(
1232                         OSRF_LOG_MARK,
1233                         "Recieved a result for object permission [%s] for user %d on object %s (class %s) at org %d",
1234                         perm,
1235                         userid,
1236                         pkey_value,
1237                         osrfHashGet(class, "classname"),
1238                         atoi(context_org)
1239                     );
1240
1241                     if (dbi_result_first_row(result)) {
1242                         jsonObject* return_val = oilsMakeJSONFromResult( result );
1243                                                 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1244
1245                             osrfLogDebug(
1246                             OSRF_LOG_MARK,
1247                             "Status of object permission [%s] for user %d on object %s (class %s) at org %d is %s",
1248                             perm,
1249                             userid,
1250                             pkey_value,
1251                             osrfHashGet(class, "classname"),
1252                             atoi(context_org),
1253                             has_perm
1254                         );
1255
1256                         if ( *has_perm == 't' ) OK = 1;
1257                         jsonObjectFree(return_val);
1258                     }
1259
1260                     dbi_result_free(result); 
1261                     if (OK) break;
1262                 }
1263             }
1264
1265                 osrfLogDebug( OSRF_LOG_MARK, "Checking non-object permission [%s] for user %d at org %d", perm, userid, atoi(context_org) );
1266             result = dbi_conn_queryf(
1267                 writehandle,
1268                 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1269                 userid,
1270                 perm,
1271                 atoi(context_org)
1272             );
1273
1274                         if (result) {
1275                                 osrfLogDebug( OSRF_LOG_MARK, "Received a result for permission [%s] for user %d at org %d",
1276                                                 perm, userid, atoi(context_org) );
1277                                 if ( dbi_result_first_row(result) ) {
1278                                         jsonObject* return_val = oilsMakeJSONFromResult( result );
1279                                         const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1280                                         osrfLogDebug( OSRF_LOG_MARK, "Status of permission [%s] for user %d at org %d is [%s]",
1281                                                         perm, userid, atoi(context_org), has_perm );
1282                                         if ( *has_perm == 't' ) OK = 1;
1283                                         jsonObjectFree(return_val);
1284                                 }
1285
1286                                 dbi_result_free(result); 
1287                                 if (OK) break;
1288                         }
1289
1290         }
1291         if (OK) break;
1292     }
1293
1294     if (pkey_value) free(pkey_value);
1295     osrfStringArrayFree(context_org_array);
1296
1297     return OK;
1298 }
1299
1300 /*
1301 Utility function: create a JSON_HASH with a single key/value pair.
1302 This function is equivalent to:
1303
1304         jsonParseStringFmt( "{\"%s\":\"%s\"}", key, value )
1305
1306 or, if value is NULL:
1307
1308         jsonParseStringFmt( "{\"%s\":null}", key )
1309
1310 ...but faster because it doesn't create and parse a JSON string.
1311 */
1312 static jsonObject* single_hash( const char* key, const char* value ) {
1313         // Sanity check
1314         if( ! key ) key = "";
1315
1316         jsonObject* hash = jsonNewObjectType( JSON_HASH );
1317         jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1318         return hash;
1319 }
1320 #endif
1321
1322
1323 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1324
1325         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1326 #ifdef PCRUD
1327         jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1328         jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1329 #else
1330         jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1331         jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1332 #endif
1333
1334         if (!verifyObjectClass(ctx, target)) {
1335                 *err = -1;
1336                 return jsonNULL;
1337         }
1338
1339         osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1340
1341         char* trans_id = NULL;
1342         if( ctx->session && ctx->session->userData )
1343                 trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
1344
1345         if ( !trans_id ) {
1346                 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1347
1348                 osrfAppSessionStatus(
1349                         ctx->session,
1350                         OSRF_STATUS_BADREQUEST,
1351                         "osrfMethodException",
1352                         ctx->request,
1353                         "No active transaction -- required for CREATE"
1354                 );
1355                 *err = -1;
1356                 return jsonNULL;
1357         }
1358
1359         // The following test is harmless but redundant.  If a class is
1360         // readonly, we don't register a create method for it.
1361         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1362                 osrfAppSessionStatus(
1363                         ctx->session,
1364                         OSRF_STATUS_BADREQUEST,
1365                         "osrfMethodException",
1366                         ctx->request,
1367                         "Cannot INSERT readonly class"
1368                 );
1369                 *err = -1;
1370                 return jsonNULL;
1371         }
1372
1373         // Set the last_xact_id
1374         int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1375         if (index > -1) {
1376                 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
1377                 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1378         }       
1379
1380         osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1381
1382         dbhandle = writehandle;
1383
1384         osrfHash* fields = osrfHashGet(meta, "fields");
1385         char* pkey = osrfHashGet(meta, "primarykey");
1386         char* seq = osrfHashGet(meta, "sequence");
1387
1388         growing_buffer* table_buf = buffer_init(128);
1389         growing_buffer* col_buf = buffer_init(128);
1390         growing_buffer* val_buf = buffer_init(128);
1391
1392         OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1393         OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1394         OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1395         buffer_add(val_buf,"VALUES (");
1396
1397
1398         int i = 0;
1399         int first = 1;
1400         char* field_name;
1401         osrfStringArray* field_list = osrfHashKeys( fields );
1402         while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
1403
1404                 osrfHash* field = osrfHashGet( fields, field_name );
1405
1406                 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1407                         continue;
1408
1409                 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1410
1411                 char* value;
1412                 if (field_object && field_object->classname) {
1413                         value = oilsFMGetString(
1414                                 field_object,
1415                                 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1416                         );
1417                 } else {
1418                         value = jsonObjectToSimpleString( field_object );
1419                 }
1420
1421
1422                 if (first) {
1423                         first = 0;
1424                 } else {
1425                         OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1426                         OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1427                 }
1428
1429                 buffer_add(col_buf, field_name);
1430
1431                 if (!field_object || field_object->type == JSON_NULL) {
1432                         buffer_add( val_buf, "DEFAULT" );
1433                         
1434                 } else if ( !strcmp(get_primitive( field ), "number") ) {
1435                         const char* numtype = get_datatype( field );
1436                         if ( !strcmp( numtype, "INT8") ) {
1437                                 buffer_fadd( val_buf, "%lld", atoll(value) );
1438                                 
1439                         } else if ( !strcmp( numtype, "INT") ) {
1440                                 buffer_fadd( val_buf, "%d", atoi(value) );
1441                                 
1442                         } else if ( !strcmp( numtype, "NUMERIC") ) {
1443                                 buffer_fadd( val_buf, "%f", atof(value) );
1444                         }
1445                 } else {
1446                         if ( dbi_conn_quote_string(writehandle, &value) ) {
1447                                 OSRF_BUFFER_ADD( val_buf, value );
1448
1449                         } else {
1450                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1451                                 osrfAppSessionStatus(
1452                                         ctx->session,
1453                                         OSRF_STATUS_INTERNALSERVERERROR,
1454                                         "osrfMethodException",
1455                                         ctx->request,
1456                                         "Error quoting string -- please see the error log for more details"
1457                                 );
1458                                 free(value);
1459                                 buffer_free(table_buf);
1460                                 buffer_free(col_buf);
1461                                 buffer_free(val_buf);
1462                                 *err = -1;
1463                                 return jsonNULL;
1464                         }
1465                 }
1466
1467                 free(value);
1468                 
1469         }
1470
1471
1472         OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1473         OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1474
1475         char* table_str = buffer_release(table_buf);
1476         char* col_str   = buffer_release(col_buf);
1477         char* val_str   = buffer_release(val_buf);
1478         growing_buffer* sql = buffer_init(128);
1479         buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1480         free(table_str);
1481         free(col_str);
1482         free(val_str);
1483
1484         char* query = buffer_release(sql);
1485
1486         osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1487
1488         
1489         dbi_result result = dbi_conn_query(writehandle, query);
1490
1491         jsonObject* obj = NULL;
1492
1493         if (!result) {
1494                 obj = jsonNewObject(NULL);
1495                 osrfLogError(
1496                         OSRF_LOG_MARK,
1497                         "%s ERROR inserting %s object using query [%s]",
1498                         MODULENAME,
1499                         osrfHashGet(meta, "fieldmapper"),
1500                         query
1501                 );
1502                 osrfAppSessionStatus(
1503                         ctx->session,
1504                         OSRF_STATUS_INTERNALSERVERERROR,
1505                         "osrfMethodException",
1506                         ctx->request,
1507                         "INSERT error -- please see the error log for more details"
1508                 );
1509                 *err = -1;
1510         } else {
1511
1512                 char* id = oilsFMGetString(target, pkey);
1513                 if (!id) {
1514                         unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1515                         growing_buffer* _id = buffer_init(10);
1516                         buffer_fadd(_id, "%lld", new_id);
1517                         id = buffer_release(_id);
1518                 }
1519
1520                 // Find quietness specification, if present
1521                 const char* quiet_str = NULL;
1522                 if ( options ) {
1523                         const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1524                         if( quiet_obj )
1525                                 quiet_str = jsonObjectGetString( quiet_obj );
1526                 }
1527
1528                 if( str_is_true( quiet_str ) ) {  // if quietness is specified
1529                         obj = jsonNewObject(id);
1530                 }
1531                 else {
1532
1533                         jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1534                         jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1535
1536                         jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1537
1538                         jsonObjectFree( where_clause );
1539
1540                         if(*err) {
1541                                 obj = jsonNULL;
1542                         } else {
1543                                 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1544                         }
1545
1546                         jsonObjectFree( list );
1547                 }
1548
1549                 free(id);
1550         }
1551
1552         free(query);
1553
1554         return obj;
1555
1556 }
1557
1558 /*
1559  * Fetch one row from a specified table, using a specified value
1560  * for the primary key
1561 */
1562 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1563
1564     int id_pos = 0;
1565     int order_pos = 1;
1566
1567 #ifdef PCRUD
1568     id_pos = 1;
1569     order_pos = 2;
1570 #endif
1571
1572         osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1573
1574         const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos);  // key value
1575
1576         osrfLogDebug(
1577                 OSRF_LOG_MARK,
1578                 "%s retrieving %s object with primary key value of %s",
1579                 MODULENAME,
1580                 osrfHashGet( class_def, "fieldmapper" ),
1581                 jsonObjectGetString( id_obj )
1582         );
1583
1584         // Build a WHERE clause based on the key value
1585         jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1586         jsonObjectSetKey( 
1587                 where_clause,
1588                 osrfHashGet( class_def, "primarykey" ),
1589                 jsonObjectClone( id_obj )
1590         );
1591
1592         jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
1593
1594         jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
1595
1596         jsonObjectFree( where_clause );
1597         if(*err)
1598                 return jsonNULL;
1599
1600         jsonObject* obj = jsonObjectExtractIndex( list, 0 );
1601         jsonObjectFree( list );
1602
1603 #ifdef PCRUD
1604         if(!verifyObjectPCRUD(ctx, obj)) {
1605         jsonObjectFree(obj);
1606         *err = -1;
1607
1608         growing_buffer* msg = buffer_init(128);
1609                 OSRF_BUFFER_ADD( msg, MODULENAME );
1610                 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1611
1612         char* m = buffer_release(msg);
1613         osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1614
1615         free(m);
1616
1617                 return jsonNULL;
1618         }
1619 #endif
1620
1621         return obj;
1622 }
1623
1624 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1625         growing_buffer* val_buf = buffer_init(32);
1626         const char* numtype = get_datatype( field );
1627
1628         if ( !strncmp( numtype, "INT", 3 ) ) {
1629                 if (value->type == JSON_NUMBER)
1630                         //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1631                         buffer_fadd( val_buf, jsonObjectGetString( value ) );
1632                 else {
1633                         //const char* val_str = jsonObjectGetString( value );
1634                         //buffer_fadd( val_buf, "%ld", atol(val_str) );
1635                         buffer_fadd( val_buf, jsonObjectGetString( value ) );
1636                 }
1637
1638         } else if ( !strcmp( numtype, "NUMERIC" ) ) {
1639                 if (value->type == JSON_NUMBER)
1640                         //buffer_fadd( val_buf, "%f",  jsonObjectGetNumber(value) );
1641                         buffer_fadd( val_buf, jsonObjectGetString( value ) );
1642                 else {
1643                         //const char* val_str = jsonObjectGetString( value );
1644                         //buffer_fadd( val_buf, "%f", atof(val_str) );
1645                         buffer_fadd( val_buf, jsonObjectGetString( value ) );
1646                 }
1647
1648         } else {
1649                 // Presumably this was really intended ot be a string, so quote it
1650                 char* str = jsonObjectToSimpleString( value );
1651                 if ( dbi_conn_quote_string(dbhandle, &str) ) {
1652                         OSRF_BUFFER_ADD( val_buf, str );
1653                         free(str);
1654                 } else {
1655                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
1656                         free(str);
1657                         buffer_free(val_buf);
1658                         return NULL;
1659                 }
1660         }
1661
1662         return buffer_release(val_buf);
1663 }
1664
1665 static char* searchINPredicate (const char* class, osrfHash* field,
1666                 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1667         growing_buffer* sql_buf = buffer_init(32);
1668         
1669         buffer_fadd(
1670                 sql_buf,
1671                 "\"%s\".%s ",
1672                 class,
1673                 osrfHashGet(field, "name")
1674         );
1675
1676         if (!op) {
1677                 buffer_add(sql_buf, "IN (");
1678         } else if (!(strcasecmp(op,"not in"))) {
1679                 buffer_add(sql_buf, "NOT IN (");
1680         } else {
1681                 buffer_add(sql_buf, "IN (");
1682         }
1683
1684     if (node->type == JSON_HASH) {
1685         // subquery predicate
1686         char* subpred = SELECT(
1687             ctx,
1688             jsonObjectGetKey( node, "select" ),
1689             jsonObjectGetKey( node, "from" ),
1690             jsonObjectGetKey( node, "where" ),
1691             jsonObjectGetKey( node, "having" ),
1692             jsonObjectGetKey( node, "order_by" ),
1693             jsonObjectGetKey( node, "limit" ),
1694             jsonObjectGetKey( node, "offset" ),
1695             SUBSELECT
1696         );
1697
1698                 if( subpred ) {
1699                         buffer_add(sql_buf, subpred);
1700                         free(subpred);
1701                 } else {
1702                         buffer_free( sql_buf );
1703                         return NULL;
1704                 }
1705
1706     } else if (node->type == JSON_ARRAY) {
1707         // literal value list
1708         int in_item_index = 0;
1709         int in_item_first = 1;
1710         const jsonObject* in_item;
1711         while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1712
1713                         if (in_item_first)
1714                                 in_item_first = 0;
1715                         else
1716                                 buffer_add(sql_buf, ", ");
1717
1718                         // Sanity check
1719                         if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1720                                 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1721                                                 MODULENAME, json_type( in_item->type ) );
1722                                 buffer_free(sql_buf);
1723                                 return NULL;
1724                         }
1725                         
1726                         // Append the literal value -- quoted if not a number
1727                         if ( JSON_NUMBER == in_item->type ) {
1728                                 char* val = jsonNumberToDBString( field, in_item );
1729                                 OSRF_BUFFER_ADD( sql_buf, val );
1730                                 free(val);
1731
1732                         } else if ( !strcmp( get_primitive( field ), "number") ) {
1733                                 char* val = jsonNumberToDBString( field, in_item );
1734                                 OSRF_BUFFER_ADD( sql_buf, val );
1735                                 free(val);
1736
1737                         } else {
1738                                 char* key_string = jsonObjectToSimpleString(in_item);
1739                                 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1740                                         OSRF_BUFFER_ADD( sql_buf, key_string );
1741                                         free(key_string);
1742                                 } else {
1743                                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1744                                         free(key_string);
1745                                         buffer_free(sql_buf);
1746                                         return NULL;
1747                                 }
1748                         }
1749                 }
1750
1751                 if( in_item_first ) {
1752                         osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1753                         buffer_free( sql_buf );
1754                         return NULL;
1755                 }
1756         } else {
1757                 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1758                         MODULENAME, json_type( node->type ) );
1759                 buffer_free(sql_buf);
1760                 return NULL;
1761         }
1762
1763         OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1764
1765         return buffer_release(sql_buf);
1766 }
1767
1768 // Receive a JSON_ARRAY representing a function call.  The first
1769 // entry in the array is the function name.  The rest are parameters.
1770 static char* searchValueTransform( const jsonObject* array ) {
1771         
1772         if( array->size < 1 ) {
1773                 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
1774                 return NULL;
1775         }
1776         
1777         // Get the function name
1778         jsonObject* func_item = jsonObjectGetIndex( array, 0 );
1779         if( func_item->type != JSON_STRING ) {
1780                 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
1781                         MODULENAME, json_type( func_item->type ) );
1782                 return NULL;
1783         }
1784         
1785         growing_buffer* sql_buf = buffer_init(32);
1786
1787         OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1788         OSRF_BUFFER_ADD( sql_buf, "( " );
1789         
1790         // Get the parameters
1791         int func_item_index = 1;   // We already grabbed the zeroth entry
1792         while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1793
1794                 // Add a separator comma, if we need one
1795                 if( func_item_index > 2 )
1796                         buffer_add( sql_buf, ", " );
1797
1798                 // Add the current parameter
1799                 if (func_item->type == JSON_NULL) {
1800                         buffer_add( sql_buf, "NULL" );
1801                 } else {
1802                         char* val = jsonObjectToSimpleString(func_item);
1803                         if ( dbi_conn_quote_string(dbhandle, &val) ) {
1804                                 OSRF_BUFFER_ADD( sql_buf, val );
1805                                 free(val);
1806                         } else {
1807                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1808                                 buffer_free(sql_buf);
1809                                 free(val);
1810                                 return NULL;
1811                         }
1812                 }
1813         }
1814
1815         buffer_add( sql_buf, " )" );
1816
1817         return buffer_release(sql_buf);
1818 }
1819
1820 static char* searchFunctionPredicate (const char* class, osrfHash* field,
1821                 const jsonObject* node, const char* op) {
1822
1823         if( ! is_good_operator( op ) ) {
1824                 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
1825                 return NULL;
1826         }
1827         
1828         char* val = searchValueTransform(node);
1829         if( !val )
1830                 return NULL;
1831         
1832         growing_buffer* sql_buf = buffer_init(32);
1833         buffer_fadd(
1834                 sql_buf,
1835                 "\"%s\".%s %s %s",
1836                 class,
1837                 osrfHashGet(field, "name"),
1838                 op,
1839                 val
1840         );
1841
1842         free(val);
1843
1844         return buffer_release(sql_buf);
1845 }
1846
1847 // class is a class name
1848 // field is a field definition as stored in the IDL
1849 // node comes from the method parameter, and may represent an entry in the SELECT list
1850 static char* searchFieldTransform (const char* class, osrfHash* field, const jsonObject* node) {
1851         growing_buffer* sql_buf = buffer_init(32);
1852
1853         const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
1854         const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
1855
1856         if(transform_subcolumn) {
1857                 if( ! is_identifier( transform_subcolumn ) ) {
1858                         osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
1859                                         MODULENAME, transform_subcolumn );
1860                         buffer_free( sql_buf );
1861                         return NULL;
1862                 }
1863                 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' );    // enclose transform in parentheses
1864         }
1865
1866         if (field_transform) {
1867                 
1868                 if( ! is_identifier( field_transform ) ) {
1869                         osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
1870                                         MODULENAME, field_transform );
1871                         buffer_free( sql_buf );
1872                         return NULL;
1873                 }
1874                 
1875                 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class, osrfHashGet(field, "name"));
1876                 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
1877
1878                 if (array) {
1879                         if( array->type != JSON_ARRAY ) {
1880                                 osrfLogError( OSRF_LOG_MARK,
1881                                         "%s: Expected JSON_ARRAY for function params; found %s",
1882                                         MODULENAME, json_type( array->type ) );
1883                                 buffer_free( sql_buf );
1884                                 return NULL;
1885                         }
1886                         int func_item_index = 0;
1887                         jsonObject* func_item;
1888                         while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1889
1890                                 char* val = jsonObjectToSimpleString(func_item);
1891
1892                                 if ( !val ) {
1893                                         buffer_add( sql_buf, ",NULL" );
1894                                 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
1895                                         OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
1896                                         OSRF_BUFFER_ADD( sql_buf, val );
1897                                 } else {
1898                                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1899                                         free(val);
1900                                         buffer_free(sql_buf);
1901                                         return NULL;
1902                         }
1903                                 free(val);
1904                         }
1905                 }
1906
1907                 buffer_add( sql_buf, " )" );
1908
1909         } else {
1910                 buffer_fadd( sql_buf, "\"%s\".%s", class, osrfHashGet(field, "name"));
1911         }
1912
1913         if (transform_subcolumn)
1914                 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
1915
1916         return buffer_release(sql_buf);
1917 }
1918
1919 static char* searchFieldTransformPredicate (const char* class, osrfHash* field,
1920                 const jsonObject* node, const char* op ) {
1921
1922         if( ! is_good_operator( op ) ) {
1923                 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
1924                 return NULL;
1925         }
1926
1927         char* field_transform = searchFieldTransform( class, field, node );
1928         if( ! field_transform )
1929                 return NULL;
1930         char* value = NULL;
1931         int extra_parens = 0;   // boolean
1932
1933         const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
1934         if ( ! value_obj ) {
1935                 value = searchWHERE( node, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1936                 if( !value ) {
1937                         osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
1938                         free(field_transform);
1939                         return NULL;
1940                 }
1941                 extra_parens = 1;
1942         } else if ( value_obj->type == JSON_ARRAY ) {
1943                 value = searchValueTransform( value_obj );
1944                 if( !value ) {
1945                         osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
1946                         free( field_transform );
1947                         return NULL;
1948                 }
1949         } else if ( value_obj->type == JSON_HASH ) {
1950                 value = searchWHERE( value_obj, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1951                 if( !value ) {
1952                         osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
1953                         free(field_transform);
1954                         return NULL;
1955                 }
1956                 extra_parens = 1;
1957         } else if ( value_obj->type == JSON_NUMBER ) {
1958                 value = jsonNumberToDBString( field, value_obj );
1959         } else if ( value_obj->type == JSON_NULL ) {
1960                 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
1961                 free(field_transform);
1962                 return NULL;
1963         } else if ( value_obj->type == JSON_BOOL ) {
1964                 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
1965                 free(field_transform);
1966                 return NULL;
1967         } else {
1968                 if ( !strcmp( get_primitive( field ), "number") ) {
1969                         value = jsonNumberToDBString( field, value_obj );
1970                 } else {
1971                         value = jsonObjectToSimpleString( value_obj );
1972                         if ( !dbi_conn_quote_string(dbhandle, &value) ) {
1973                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
1974                                 free(value);
1975                                 free(field_transform);
1976                                 return NULL;
1977                         }
1978                 }
1979         }
1980
1981         const char* left_parens  = "";
1982         const char* right_parens = "";
1983
1984         if( extra_parens ) {
1985                 left_parens  = "(";
1986                 right_parens = ")";
1987         }
1988
1989         growing_buffer* sql_buf = buffer_init(32);
1990
1991         buffer_fadd(
1992                 sql_buf,
1993                 "%s%s %s %s %s %s%s",
1994                 left_parens,
1995                 field_transform,
1996                 op,
1997                 left_parens,
1998                 value,
1999                 right_parens,
2000                 right_parens
2001         );
2002
2003         free(value);
2004         free(field_transform);
2005
2006         return buffer_release(sql_buf);
2007 }
2008
2009 static char* searchSimplePredicate (const char* op, const char* class,
2010                 osrfHash* field, const jsonObject* node) {
2011
2012         if( ! is_good_operator( op ) ) {
2013                 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2014                 return NULL;
2015         }
2016
2017         char* val = NULL;
2018
2019         // Get the value to which we are comparing the specified column
2020         if (node->type != JSON_NULL) {
2021                 if ( node->type == JSON_NUMBER ) {
2022                         val = jsonNumberToDBString( field, node );
2023                 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2024                         val = jsonNumberToDBString( field, node );
2025                 } else {
2026                         val = jsonObjectToSimpleString(node);
2027                 }
2028         }
2029
2030         if( val ) {
2031                 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2032                         // Value is not numeric; enclose it in quotes
2033                         if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2034                                 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2035                                 free( val );
2036                                 return NULL;
2037                         }
2038                 }
2039         } else {
2040                 // Compare to a null value
2041                 val = strdup( "NULL" );
2042                 if (strcmp( op, "=" ))
2043                         op = "IS NOT";
2044                 else
2045                         op = "IS";
2046         }
2047
2048         growing_buffer* sql_buf = buffer_init(32);
2049         buffer_fadd( sql_buf, "\"%s\".%s %s %s", class, osrfHashGet(field, "name"), op, val );
2050         char* pred = buffer_release( sql_buf );
2051
2052         free(val);
2053
2054         return pred;
2055 }
2056
2057 static char* searchBETWEENPredicate (const char* class, osrfHash* field, const jsonObject* node) {
2058
2059         const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2060         const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2061         
2062         if( NULL == y_node ) {
2063                 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2064                 return NULL;
2065         }
2066         else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2067                 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2068                 return NULL;
2069         }
2070         
2071         char* x_string;
2072         char* y_string;
2073
2074         if ( !strcmp( get_primitive( field ), "number") ) {
2075                 x_string = jsonNumberToDBString(field, x_node);
2076                 y_string = jsonNumberToDBString(field, y_node);
2077
2078         } else {
2079                 x_string = jsonObjectToSimpleString(x_node);
2080                 y_string = jsonObjectToSimpleString(y_node);
2081                 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2082                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2083                                         MODULENAME, x_string, y_string);
2084                         free(x_string);
2085                         free(y_string);
2086                         return NULL;
2087                 }
2088         }
2089
2090         growing_buffer* sql_buf = buffer_init(32);
2091         buffer_fadd( sql_buf, "%s BETWEEN %s AND %s", osrfHashGet(field, "name"), x_string, y_string );
2092         free(x_string);
2093         free(y_string);
2094
2095         return buffer_release(sql_buf);
2096 }
2097
2098 static char* searchPredicate ( const char* class, osrfHash* field, 
2099                                                            jsonObject* node, osrfMethodContext* ctx ) {
2100
2101         char* pred = NULL;
2102         if (node->type == JSON_ARRAY) { // equality IN search
2103                 pred = searchINPredicate( class, field, node, NULL, ctx );
2104         } else if (node->type == JSON_HASH) { // other search
2105                 jsonIterator* pred_itr = jsonNewIterator( node );
2106                 if( !jsonIteratorHasNext( pred_itr ) ) {
2107                         osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"", 
2108                                         MODULENAME, osrfHashGet(field, "name") );
2109                 } else {
2110                         jsonObject* pred_node = jsonIteratorNext( pred_itr );
2111
2112                         // Verify that there are no additional predicates
2113                         if( jsonIteratorHasNext( pred_itr ) ) {
2114                                 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"", 
2115                                                 MODULENAME, osrfHashGet(field, "name") );
2116                         } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2117                                 pred = searchBETWEENPredicate( class, field, pred_node );
2118                         else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2119                                 pred = searchINPredicate( class, field, pred_node, pred_itr->key, ctx );
2120                         else if ( pred_node->type == JSON_ARRAY )
2121                                 pred = searchFunctionPredicate( class, field, pred_node, pred_itr->key );
2122                         else if ( pred_node->type == JSON_HASH )
2123                                 pred = searchFieldTransformPredicate( class, field, pred_node, pred_itr->key );
2124                         else
2125                                 pred = searchSimplePredicate( pred_itr->key, class, field, pred_node );
2126                 }
2127                 jsonIteratorFree(pred_itr);
2128
2129         } else if (node->type == JSON_NULL) { // IS NULL search
2130                 growing_buffer* _p = buffer_init(64);
2131                 buffer_fadd(
2132                         _p,
2133                         "\"%s\".%s IS NULL",
2134                         class,
2135                         osrfHashGet(field, "name")
2136                 );
2137                 pred = buffer_release(_p);
2138         } else { // equality search
2139                 pred = searchSimplePredicate( "=", class, field, node );
2140         }
2141
2142         return pred;
2143
2144 }
2145
2146
2147 /*
2148
2149 join : {
2150         acn : {
2151                 field : record,
2152                 fkey : id
2153                 type : left
2154                 filter_op : or
2155                 filter : { ... },
2156                 join : {
2157                         acp : {
2158                                 field : call_number,
2159                                 fkey : id,
2160                                 filter : { ... },
2161                         },
2162                 },
2163         },
2164         mrd : {
2165                 field : record,
2166                 type : inner
2167                 fkey : id,
2168                 filter : { ... },
2169         }
2170 }
2171
2172 */
2173
2174 static char* searchJOIN ( const jsonObject* join_hash, osrfHash* leftmeta ) {
2175
2176         const jsonObject* working_hash;
2177         jsonObject* freeable_hash = NULL;
2178
2179         if (join_hash->type == JSON_STRING) {
2180                 // create a wrapper around a copy of the original
2181                 const char* _tmp = jsonObjectGetString( join_hash );
2182                 freeable_hash = jsonNewObjectType(JSON_HASH);
2183                 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2184                 working_hash = freeable_hash;
2185         }
2186         else {
2187                 if( join_hash->type != JSON_HASH ) {
2188                         osrfLogError(
2189                                 OSRF_LOG_MARK,
2190                                 "%s: JOIN failed; expected JSON object type not found",
2191                                 MODULENAME
2192                         );
2193                         return NULL;
2194                 }
2195                 working_hash = join_hash;
2196         }
2197
2198         growing_buffer* join_buf = buffer_init(128);
2199         const char* leftclass = osrfHashGet(leftmeta, "classname");
2200
2201         jsonObject* snode = NULL;
2202         jsonIterator* search_itr = jsonNewIterator( working_hash );
2203
2204         while ( (snode = jsonIteratorNext( search_itr )) ) {
2205                 const char* class = search_itr->key;
2206                 osrfHash* idlClass = osrfHashGet( oilsIDL(), class );
2207                 if( !idlClass ) {
2208                         osrfLogError(
2209                                 OSRF_LOG_MARK,
2210                                 "%s: JOIN failed.  No class \"%s\" defined in IDL",
2211                                 MODULENAME,
2212                                 search_itr->key
2213                         );
2214                         jsonIteratorFree( search_itr );
2215                         buffer_free( join_buf );
2216                         if( freeable_hash )
2217                                 jsonObjectFree( freeable_hash );
2218                         return NULL;
2219                 }
2220
2221                 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2222                 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2223
2224                 if (field && !fkey) {
2225                         // Look up the corresponding join column in the IDL.
2226                         // The link must be defined in the child table,
2227                         // and point to the right parent table.
2228                         osrfHash* idl_link = (osrfHash*) oilsIDLFindPath( "/%s/links/%s", class, field );
2229                         const char* reltype = NULL;
2230                         const char* other_class = NULL;
2231                         reltype = osrfHashGet( idl_link, "reltype" );
2232                         if( reltype && strcmp( reltype, "has_many" ) )
2233                                 other_class = osrfHashGet( idl_link, "class" );
2234                         if( other_class && !strcmp( other_class, leftclass ) )
2235                                 fkey = osrfHashGet( idl_link, "key" );
2236                         if (!fkey) {
2237                                 osrfLogError(
2238                                         OSRF_LOG_MARK,
2239                                         "%s: JOIN failed.  No link defined from %s.%s to %s",
2240                                         MODULENAME,
2241                                         class,
2242                                         field,
2243                                         leftclass
2244                                 );
2245                                 buffer_free(join_buf);
2246                                 if(freeable_hash)
2247                                         jsonObjectFree(freeable_hash);
2248                                 jsonIteratorFree(search_itr);
2249                                 return NULL;
2250                         }
2251
2252                 } else if (!field && fkey) {
2253                         // Look up the corresponding join column in the IDL.
2254                         // The link must be defined in the child table,
2255                         // and point to the right parent table.
2256                         osrfHash* idl_link = (osrfHash*) oilsIDLFindPath( "/%s/links/%s", leftclass, fkey );
2257                         const char* reltype = NULL;
2258                         const char* other_class = NULL;
2259                         reltype = osrfHashGet( idl_link, "reltype" );
2260                         if( reltype && strcmp( reltype, "has_many" ) )
2261                                 other_class = osrfHashGet( idl_link, "class" );
2262                         if( other_class && !strcmp( other_class, class ) )
2263                                 field = osrfHashGet( idl_link, "key" );
2264                         if (!field) {
2265                                 osrfLogError(
2266                                         OSRF_LOG_MARK,
2267                                         "%s: JOIN failed.  No link defined from %s.%s to %s",
2268                                         MODULENAME,
2269                                         leftclass,
2270                                         fkey,
2271                                         class
2272                                 );
2273                                 buffer_free(join_buf);
2274                                 if(freeable_hash)
2275                                         jsonObjectFree(freeable_hash);
2276                                 jsonIteratorFree(search_itr);
2277                                 return NULL;
2278                         }
2279
2280                 } else if (!field && !fkey) {
2281                         osrfHash* _links = oilsIDL_links( leftclass );
2282
2283                         // For each link defined for the left class:
2284                         // see if the link references the joined class
2285                         osrfHashIterator* itr = osrfNewHashIterator( _links );
2286                         osrfHash* curr_link = NULL;
2287                         while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2288                                 const char* other_class = osrfHashGet( curr_link, "class" );
2289                                 if( other_class && !strcmp( other_class, class ) ) {
2290
2291                                         // In the IDL, the parent class doesn't know then names of the child
2292                                         // columns that are pointing to it, so don't use that end of the link
2293                                         const char* reltype = osrfHashGet( curr_link, "reltype" );
2294                                         if( reltype && strcmp( reltype, "has_many" ) ) {
2295                                                 // Found a link between the classes
2296                                                 fkey = osrfHashIteratorKey( itr );
2297                                                 field = osrfHashGet( curr_link, "key" );
2298                                                 break;
2299                                         }
2300                                 }
2301                         }
2302                         osrfHashIteratorFree( itr );
2303
2304                         if (!field || !fkey) {
2305                                 // Do another such search, with the classes reversed
2306                                 _links = oilsIDL_links( class );
2307
2308                                 // For each link defined for the joined class:
2309                                 // see if the link references the left class
2310                                 osrfHashIterator* itr = osrfNewHashIterator( _links );
2311                                 osrfHash* curr_link = NULL;
2312                                 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2313                                         const char* other_class = osrfHashGet( curr_link, "class" );
2314                                         if( other_class && !strcmp( other_class, leftclass ) ) {
2315
2316                                                 // In the IDL, the parent class doesn't know then names of the child
2317                                                 // columns that are pointing to it, so don't use that end of the link
2318                                                 const char* reltype = osrfHashGet( curr_link, "reltype" );
2319                                                 if( reltype && strcmp( reltype, "has_many" ) ) {
2320                                                         // Found a link between the classes
2321                                                         field = osrfHashIteratorKey( itr );
2322                                                         fkey = osrfHashGet( curr_link, "key" );
2323                                                         break;
2324                                                 }
2325                                         }
2326                                 }
2327                                 osrfHashIteratorFree( itr );
2328                         }
2329
2330                         if (!field || !fkey) {
2331                                 osrfLogError(
2332                                         OSRF_LOG_MARK,
2333                                         "%s: JOIN failed.  No link defined between %s and %s",
2334                                         MODULENAME,
2335                                         leftclass,
2336                                         class
2337                                 );
2338                                 buffer_free(join_buf);
2339                                 if(freeable_hash)
2340                                         jsonObjectFree(freeable_hash);
2341                                 jsonIteratorFree(search_itr);
2342                                 return NULL;
2343                         }
2344
2345                 }
2346
2347                 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2348                 if (type) {
2349                         if ( !strcasecmp(type,"left") ) {
2350                                 buffer_add(join_buf, " LEFT JOIN");
2351                         } else if ( !strcasecmp(type,"right") ) {
2352                                 buffer_add(join_buf, " RIGHT JOIN");
2353                         } else if ( !strcasecmp(type,"full") ) {
2354                                 buffer_add(join_buf, " FULL JOIN");
2355                         } else {
2356                                 buffer_add(join_buf, " INNER JOIN");
2357                         }
2358                 } else {
2359                         buffer_add(join_buf, " INNER JOIN");
2360                 }
2361
2362                 char* table = getSourceDefinition(idlClass);
2363                 if( !table ) {
2364                         jsonIteratorFree( search_itr );
2365                         buffer_free( join_buf );
2366                         if( freeable_hash )
2367                                 jsonObjectFree( freeable_hash );
2368                         return NULL;
2369                 }
2370
2371                 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2372                                         table, class, class, field, leftclass, fkey);
2373                 free(table);
2374
2375                 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2376                 if (filter) {
2377                         const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2378                         if ( filter_op && !strcasecmp("or",filter_op) ) {
2379                                 buffer_add( join_buf, " OR " );
2380                         } else {
2381                                 buffer_add( join_buf, " AND " );
2382                         }
2383
2384                         char* jpred = searchWHERE( filter, idlClass, AND_OP_JOIN, NULL );
2385                         if( jpred ) {
2386                                 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2387                                 OSRF_BUFFER_ADD( join_buf, jpred );
2388                                 free(jpred);
2389                         } else {
2390                                 osrfLogError(
2391                                         OSRF_LOG_MARK,
2392                                         "%s: JOIN failed.  Invalid conditional expression.",
2393                                         MODULENAME
2394                                 );
2395                                 jsonIteratorFree( search_itr );
2396                                 buffer_free( join_buf );
2397                                 if( freeable_hash )
2398                                         jsonObjectFree( freeable_hash );
2399                                 return NULL;
2400                         }
2401                 }
2402
2403                 buffer_add(join_buf, " ) ");
2404                 
2405                 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2406                 if (join_filter) {
2407                         char* jpred = searchJOIN( join_filter, idlClass );
2408                         if( jpred ) {
2409                                 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2410                                 OSRF_BUFFER_ADD( join_buf, jpred );
2411                                 free(jpred);
2412                         } else {
2413                                 osrfLogError(  OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2414                                 jsonIteratorFree( search_itr );
2415                                 buffer_free( join_buf );
2416                                 if( freeable_hash )
2417                                         jsonObjectFree( freeable_hash );
2418                                 return NULL;
2419                         }
2420                 }
2421         }
2422
2423         if(freeable_hash)
2424                 jsonObjectFree(freeable_hash);
2425         jsonIteratorFree(search_itr);
2426
2427         return buffer_release(join_buf);
2428 }
2429
2430 /*
2431
2432 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2433 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2434 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2435
2436 Generate code to express a set of conditions, as for a WHERE clause.  Parameters:
2437
2438 search_hash is the JSON expression of the conditions.
2439 meta is the class definition from the IDL, for the relevant table.
2440 opjoin_type indicates whether multiple conditions, if present, should be
2441         connected by AND or OR.
2442 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2443         to pass it to other functions -- and all they do with it is to use the session
2444         and request members to send error messages back to the client.
2445
2446 */
2447
2448 static char* searchWHERE ( const jsonObject* search_hash, osrfHash* meta, int opjoin_type, osrfMethodContext* ctx ) {
2449
2450         osrfLogDebug(
2451         OSRF_LOG_MARK,
2452         "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2453         MODULENAME,
2454         search_hash,
2455         meta,
2456         opjoin_type,
2457         ctx
2458     );
2459
2460         growing_buffer* sql_buf = buffer_init(128);
2461
2462         jsonObject* node = NULL;
2463
2464         int first = 1;
2465         if ( search_hash->type == JSON_ARRAY ) {
2466                 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2467                 jsonIterator* search_itr = jsonNewIterator( search_hash );
2468                 if( !jsonIteratorHasNext( search_itr ) ) {
2469                         osrfLogError(
2470                                 OSRF_LOG_MARK,
2471                                 "%s: Invalid predicate structure: empty JSON array",
2472                                 MODULENAME
2473                         );
2474                         jsonIteratorFree( search_itr );
2475                         buffer_free( sql_buf );
2476                         return NULL;
2477                 }
2478
2479                 while ( (node = jsonIteratorNext( search_itr )) ) {
2480             if (first) {
2481                 first = 0;
2482             } else {
2483                 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2484                 else buffer_add(sql_buf, " AND ");
2485             }
2486
2487             char* subpred = searchWHERE( node, meta, opjoin_type, ctx );
2488                         if( subpred ) {
2489                 buffer_fadd(sql_buf, "( %s )", subpred);
2490                 free(subpred);
2491                         } else {
2492                                 jsonIteratorFree( search_itr );
2493                                 buffer_free( sql_buf );
2494                                 return NULL;
2495                         }
2496         }
2497         jsonIteratorFree(search_itr);
2498
2499         } else if ( search_hash->type == JSON_HASH ) {
2500                 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2501                 jsonIterator* search_itr = jsonNewIterator( search_hash );
2502                 if( !jsonIteratorHasNext( search_itr ) ) {
2503                         osrfLogError(
2504                                 OSRF_LOG_MARK,
2505                                 "%s: Invalid predicate structure: empty JSON object",
2506                                 MODULENAME
2507                         );
2508                         jsonIteratorFree( search_itr );
2509                         buffer_free( sql_buf );
2510                         return NULL;
2511                 }
2512
2513                 while ( (node = jsonIteratorNext( search_itr )) ) {
2514
2515             if (first) {
2516                 first = 0;
2517             } else {
2518                 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2519                 else buffer_add(sql_buf, " AND ");
2520             }
2521
2522                         if ( '+' == search_itr->key[ 0 ] ) {
2523                                 if ( node->type == JSON_STRING ) {
2524                                         // Intended purpose; to allow reference to a Boolean column
2525
2526                                         // Verify that the class alias is not empty
2527                                         if( '\0' == search_itr->key[ 1 ] ) {
2528                                                 osrfLogError(
2529                                                         OSRF_LOG_MARK,
2530                                                         "%s: Table alias is empty",
2531                                                         MODULENAME
2532                                                 );
2533                                                 jsonIteratorFree( search_itr );
2534                                                 buffer_free( sql_buf );
2535                                                 return NULL;
2536                                         }
2537
2538                                         // Verify that the string looks like an identifier.
2539                                         const char* subpred = jsonObjectGetString( node );
2540                                         if( ! is_identifier( subpred ) ) {
2541                                                 osrfLogError(
2542                                                          OSRF_LOG_MARK,
2543                                                         "%s: Invalid boolean identifier in WHERE clause: \"%s\"",
2544                                                         MODULENAME,
2545                                                         subpred
2546                                                 );
2547                                                 jsonIteratorFree( search_itr );
2548                                                 buffer_free( sql_buf );
2549                                                 return NULL;
2550                                         }
2551
2552                                         buffer_fadd(sql_buf, " \"%s\".%s ", search_itr->key + 1, subpred);
2553                                 } else {
2554                                         char* subpred = searchWHERE( node, osrfHashGet( oilsIDL(), search_itr->key + 1 ), AND_OP_JOIN, ctx );
2555                                         if( subpred ) {
2556                                                 buffer_fadd(sql_buf, "( %s )", subpred);
2557                                                 free(subpred);
2558                                         } else {
2559                                                 jsonIteratorFree( search_itr );
2560                                                 buffer_free( sql_buf );
2561                                                 return NULL;
2562                                         }
2563                                 }
2564                         } else if ( !strcasecmp("-or",search_itr->key) ) {
2565                                 char* subpred = searchWHERE( node, meta, OR_OP_JOIN, ctx );
2566                                 if( subpred ) {
2567                                         buffer_fadd(sql_buf, "( %s )", subpred);
2568                                         free( subpred );
2569                                 } else {
2570                                         buffer_free( sql_buf );
2571                                         return NULL;
2572                                 }
2573                         } else if ( !strcasecmp("-and",search_itr->key) ) {
2574                                 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2575                                 if( subpred ) {
2576                                         buffer_fadd(sql_buf, "( %s )", subpred);
2577                                         free( subpred );
2578                                 } else {
2579                                         buffer_free( sql_buf );
2580                                         return NULL;
2581                                 }
2582                         } else if ( !strcasecmp("-not",search_itr->key) ) {
2583                                 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2584                                 if( subpred ) {
2585                                         buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2586                                         free( subpred );
2587                                 } else {
2588                                         buffer_free( sql_buf );
2589                                         return NULL;
2590                                 }
2591                         } else if ( !strcasecmp("-exists",search_itr->key) ) {
2592                 char* subpred = SELECT(
2593                     ctx,
2594                     jsonObjectGetKey( node, "select" ),
2595                     jsonObjectGetKey( node, "from" ),
2596                     jsonObjectGetKey( node, "where" ),
2597                     jsonObjectGetKey( node, "having" ),
2598                     jsonObjectGetKey( node, "order_by" ),
2599                     jsonObjectGetKey( node, "limit" ),
2600                     jsonObjectGetKey( node, "offset" ),
2601                     SUBSELECT
2602                 );
2603
2604                                 if( subpred ) {
2605                                         buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2606                                         free(subpred);
2607                                 } else {
2608                                         buffer_free( sql_buf );
2609                                         return NULL;
2610                                 }
2611             } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2612                 char* subpred = SELECT(
2613                     ctx,
2614                     jsonObjectGetKey( node, "select" ),
2615                     jsonObjectGetKey( node, "from" ),
2616                     jsonObjectGetKey( node, "where" ),
2617                     jsonObjectGetKey( node, "having" ),
2618                     jsonObjectGetKey( node, "order_by" ),
2619                     jsonObjectGetKey( node, "limit" ),
2620                     jsonObjectGetKey( node, "offset" ),
2621                     SUBSELECT
2622                 );
2623
2624                                 if( subpred ) {
2625                                         buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2626                                         free(subpred);
2627                                 } else {
2628                                         buffer_free( sql_buf );
2629                                         return NULL;
2630                                 }
2631
2632             } else {
2633
2634                 char* class = osrfHashGet(meta, "classname");
2635                 osrfHash* fields = osrfHashGet(meta, "fields");
2636                 osrfHash* field = osrfHashGet( fields, search_itr->key );
2637
2638
2639                 if (!field) {
2640                     char* table = getSourceDefinition(meta);
2641                                         if( !table )
2642                                                 table = strdup( "(?)" );
2643                     osrfLogError(
2644                         OSRF_LOG_MARK,
2645                         "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2646                         MODULENAME,
2647                         search_itr->key,
2648                         table,
2649                         class ? class : "?"
2650                     );
2651                     buffer_free(sql_buf);
2652                     free(table);
2653                                         jsonIteratorFree(search_itr);
2654                                         return NULL;
2655                                 }
2656
2657                                 char* subpred = searchPredicate( class, field, node, ctx );
2658                                 if( subpred ) {
2659                                         buffer_add( sql_buf, subpred );
2660                                         free(subpred);
2661                                 } else {
2662                                         buffer_free(sql_buf);
2663                                         jsonIteratorFree(search_itr);
2664                                         return NULL;
2665                                 }
2666                         }
2667                 }
2668                 jsonIteratorFree(search_itr);
2669
2670     } else {
2671         // ERROR ... only hash and array allowed at this level
2672         char* predicate_string = jsonObjectToJSON( search_hash );
2673         osrfLogError(
2674             OSRF_LOG_MARK,
2675             "%s: Invalid predicate structure: %s",
2676             MODULENAME,
2677             predicate_string
2678         );
2679         buffer_free(sql_buf);
2680         free(predicate_string);
2681         return NULL;
2682     }
2683
2684         return buffer_release(sql_buf);
2685 }
2686
2687 // Return 1 if the class is in the FROM clause, or 0 if not
2688 static int is_joined( jsonObject* from_clause, const char* class ) {
2689
2690         if( ! from_clause )
2691                 return 0;
2692         else if( from_clause->type == JSON_STRING ) {
2693                 if( strcmp( class, jsonObjectGetString( from_clause ) ) )
2694                         return 0;
2695                 else
2696                         return 1;
2697         } else if( from_clause->type != JSON_HASH ) {
2698                 return 0;
2699         } else {      // Look at any subjoins
2700                 jsonIterator* class_itr = jsonNewIterator( from_clause );
2701                 jsonObject* curr_class;
2702                 int rc = 0;    // return code
2703                 while ( ( curr_class = jsonIteratorNext( class_itr ) ) ) {
2704                         if( ! strcmp( class_itr->key, class ) ) {
2705                                 rc = 1;
2706                                 break;
2707                         } else {
2708                                 jsonObject* subjoin = jsonObjectGetKey( curr_class, "join" );
2709                                 if( subjoin && is_joined( subjoin, class ) ) {
2710                                         rc = 1;
2711                                         break;
2712                                 }
2713                         }
2714                 }
2715                 jsonIteratorFree( class_itr );
2716
2717                 return rc;
2718         }
2719 }
2720
2721 char* SELECT (
2722                 /* method context */ osrfMethodContext* ctx,
2723                 
2724                 /* SELECT   */ jsonObject* selhash,
2725                 /* FROM     */ jsonObject* join_hash,
2726                 /* WHERE    */ jsonObject* search_hash,
2727                 /* HAVING   */ jsonObject* having_hash,
2728                 /* ORDER BY */ jsonObject* order_hash,
2729                 /* LIMIT    */ jsonObject* limit,
2730                 /* OFFSET   */ jsonObject* offset,
2731                 /* flags    */ int flags
2732 ) {
2733         const char* locale = osrf_message_get_last_locale();
2734
2735         // in case we don't get a select list
2736         jsonObject* defaultselhash = NULL;
2737
2738         // general tmp objects
2739         const jsonObject* tmp_const;
2740         jsonObject* selclass = NULL;
2741         jsonObject* selfield = NULL;
2742         jsonObject* snode = NULL;
2743         jsonObject* onode = NULL;
2744
2745         char* string = NULL;
2746         int from_function = 0;
2747         int first = 1;
2748         int gfirst = 1;
2749         //int hfirst = 1;
2750
2751         // the core search class
2752         char* core_class = NULL;
2753
2754         // metadata about the core search class
2755         osrfHash* core_meta = NULL;
2756
2757         osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale);
2758
2759         // punt if there's no core class
2760         if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2761                 osrfLogError(
2762                         OSRF_LOG_MARK,
2763                         "%s: FROM clause is missing or empty",
2764                         MODULENAME
2765                 );
2766                 if( ctx )
2767                         osrfAppSessionStatus(
2768                                 ctx->session,
2769                                 OSRF_STATUS_INTERNALSERVERERROR,
2770                                 "osrfMethodException",
2771                                 ctx->request,
2772                                 "FROM clause is missing or empty in JSON query"
2773                         );
2774                 return NULL;
2775         }
2776
2777         // get the core class -- the only key of the top level FROM clause, or a string
2778         if (join_hash->type == JSON_HASH) {
2779                 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2780                 snode = jsonIteratorNext( tmp_itr );
2781                 
2782                 core_class = strdup( tmp_itr->key );
2783                 join_hash = snode;
2784                 
2785                 jsonObject* extra = jsonIteratorNext( tmp_itr );
2786
2787                 jsonIteratorFree( tmp_itr );
2788                 snode = NULL;
2789
2790                 // There shouldn't be more than one entry in join_hash
2791                 if( extra ) {
2792                         osrfLogError(
2793                                 OSRF_LOG_MARK,
2794                                 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2795                                 MODULENAME
2796                         );
2797                         if( ctx )
2798                                 osrfAppSessionStatus(
2799                                         ctx->session,
2800                                         OSRF_STATUS_INTERNALSERVERERROR,
2801                                         "osrfMethodException",
2802                                         ctx->request,
2803                                         "Malformed FROM clause in JSON query"
2804                                 );
2805                         free( core_class );
2806                         return NULL;    // Malformed join_hash; extra entry
2807                 }
2808         } else if (join_hash->type == JSON_ARRAY) {
2809                 from_function = 1;
2810                 core_class = jsonObjectToSimpleString( jsonObjectGetIndex(join_hash, 0) );
2811                 selhash = NULL;
2812
2813         } else if (join_hash->type == JSON_STRING) {
2814                 core_class = jsonObjectToSimpleString( join_hash );
2815                 join_hash = NULL;
2816         }
2817         else {
2818                 osrfLogError(
2819                         OSRF_LOG_MARK,
2820                         "%s: FROM clause is unexpected JSON type: %s",
2821                         MODULENAME,
2822                         json_type( join_hash->type )
2823                 );
2824                 if( ctx )
2825                         osrfAppSessionStatus(
2826                                 ctx->session,
2827                                 OSRF_STATUS_INTERNALSERVERERROR,
2828                                 "osrfMethodException",
2829                                 ctx->request,
2830                                 "Ill-formed FROM clause in JSON query"
2831                         );
2832                 free( core_class );
2833                 return NULL;
2834         }
2835
2836         if (!from_function) {
2837                 // Get the IDL class definition for the core class
2838                 core_meta = osrfHashGet( oilsIDL(), core_class );
2839                 if( !core_meta ) {    // Didn't find it?
2840                         osrfLogError(
2841                                 OSRF_LOG_MARK,
2842                                 "%s: SELECT clause references undefined class: \"%s\"",
2843                                 MODULENAME,
2844                                 core_class
2845                         );
2846                         if( ctx )
2847                                 osrfAppSessionStatus(
2848                                         ctx->session,
2849                                         OSRF_STATUS_INTERNALSERVERERROR,
2850                                         "osrfMethodException",
2851                                         ctx->request,
2852                                         "SELECT clause references undefined class in JSON query"
2853                                 );
2854                         free( core_class );
2855                         return NULL;
2856                 }
2857
2858                 // Make sure the class isn't virtual
2859                 if( str_is_true( osrfHashGet( core_meta, "virtual" ) ) ) {
2860                         osrfLogError(
2861                                 OSRF_LOG_MARK,
2862                                 "%s: Core class is virtual: \"%s\"",
2863                                 MODULENAME,
2864                                 core_class
2865                         );
2866                         if( ctx )
2867                                 osrfAppSessionStatus(
2868                                         ctx->session,
2869                                         OSRF_STATUS_INTERNALSERVERERROR,
2870                                         "osrfMethodException",
2871                                         ctx->request,
2872                                         "FROM clause references virtual class in JSON query"
2873                                 );
2874                         free( core_class );
2875                         return NULL;
2876                 }
2877         }
2878
2879         // if the select list is empty, or the core class field list is '*',
2880         // build the default select list ...
2881         if (!selhash) {
2882                 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
2883                 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2884         } else if( selhash->type != JSON_HASH ) {
2885                 osrfLogError(
2886                         OSRF_LOG_MARK,
2887                         "%s: Expected JSON_HASH for SELECT clause; found %s",
2888                         MODULENAME,
2889                         json_type( selhash->type )
2890                 );
2891
2892                 if (ctx)
2893                         osrfAppSessionStatus(
2894                                 ctx->session,
2895                                 OSRF_STATUS_INTERNALSERVERERROR,
2896                                 "osrfMethodException",
2897                                 ctx->request,
2898                                 "Malformed SELECT clause in JSON query"
2899                         );
2900                 free( core_class );
2901                 return NULL;
2902         } else if ( (tmp_const = jsonObjectGetKeyConst( selhash, core_class )) && tmp_const->type == JSON_STRING ) {
2903                 const char* _x = jsonObjectGetString( tmp_const );
2904                 if (!strncmp( "*", _x, 1 )) {
2905                         jsonObjectRemoveKey( selhash, core_class );
2906                         jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2907                 }
2908         }
2909
2910         // the query buffer
2911         growing_buffer* sql_buf = buffer_init(128);
2912
2913         // temp buffers for the SELECT list and GROUP BY clause
2914         growing_buffer* select_buf = buffer_init(128);
2915         growing_buffer* group_buf = buffer_init(128);
2916
2917         int aggregate_found = 0;     // boolean
2918
2919         // Build a select list
2920         if(from_function)   // From a function we select everything
2921                 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
2922         else {
2923
2924                 // If we need to build a default list, prepare to do so
2925                 jsonObject* _tmp = jsonObjectGetKey( selhash, core_class );
2926                 if ( _tmp && !_tmp->size ) {
2927
2928                         osrfHash* core_fields = osrfHashGet( core_meta, "fields" );
2929
2930                         osrfHashIterator* field_itr = osrfNewHashIterator( core_fields );
2931                         osrfHash* field_def;
2932                         while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2933                                 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2934                                         // This field is not virtual, so add it to the list
2935                                         jsonObjectPush( _tmp, jsonNewObject( osrfHashIteratorKey( field_itr ) ) );
2936                                 }
2937                         }
2938                         osrfHashIteratorFree( field_itr );
2939                 }
2940
2941                 // Now build the actual select list
2942             int sel_pos = 1;
2943             first = 1;
2944             gfirst = 1;
2945             jsonIterator* selclass_itr = jsonNewIterator( selhash );
2946             while ( (selclass = jsonIteratorNext( selclass_itr )) ) {    // For each class
2947
2948                         const char* cname = selclass_itr->key;
2949
2950                         // Make sure the target relation is in the FROM clause.
2951                         
2952                         // At this point join_hash is a step down from the join_hash we
2953                         // received as a parameter.  If the original was a JSON_STRING,
2954                         // then json_hash is now NULL.  If the original was a JSON_HASH,
2955                         // then json_hash is now the first (and only) entry in it,
2956                         // denoting the core class.  We've already excluded the
2957                         // possibility that the original was a JSON_ARRAY, because in
2958                         // that case from_function would be non-NULL, and we wouldn't
2959                         // be here.
2960
2961                         // If the current class isn't the core class
2962                         // and it isn't in the join tree, bail out
2963                         if ( strcmp( core_class, cname ) && ! is_joined( join_hash, cname ) ) {
2964                                 osrfLogError(
2965                                         OSRF_LOG_MARK,
2966                                         "%s: SELECT clause references class not in FROM clause: \"%s\"",
2967                                         MODULENAME,
2968                                         cname
2969                                 );
2970                                 if( ctx )
2971                                         osrfAppSessionStatus(
2972                                                 ctx->session,
2973                                                 OSRF_STATUS_INTERNALSERVERERROR,
2974                                                 "osrfMethodException",
2975                                                 ctx->request,
2976                                                 "Selected class not in FROM clause in JSON query"
2977                                         );
2978                                 jsonIteratorFree( selclass_itr );
2979                                 buffer_free( sql_buf );
2980                                 buffer_free( select_buf );
2981                                 buffer_free( group_buf );
2982                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
2983                                 free( core_class );
2984                                 return NULL;
2985                         }
2986
2987                         // Look up some attributes of the current class, so that we 
2988                         // don't have to look them up again for each field
2989                         osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
2990                         osrfHash* class_field_set = osrfHashGet( idlClass, "fields" );
2991                         const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
2992                         const char* class_tname = osrfHashGet( idlClass, "tablename" );
2993                         
2994                     // stitch together the column list ...
2995                     jsonIterator* select_itr = jsonNewIterator( selclass );
2996                     while ( (selfield = jsonIteratorNext( select_itr )) ) {   // for each SELECT column
2997
2998                                 // If we need a separator comma, add one
2999                                 if (first) {
3000                                         first = 0;
3001                                 } else {
3002                                         OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3003                                 }
3004
3005                                 // ... if it's a string, just toss it on the pile
3006                                 if (selfield->type == JSON_STRING) {
3007
3008                                         // Look up the field in the IDL
3009                                         const char* col_name = jsonObjectGetString( selfield );
3010                                         osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3011                                         if ( !field_def ) {
3012                                                 // No such field in current class
3013                                                 osrfLogError(
3014                                                         OSRF_LOG_MARK,
3015                                                         "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3016                                                         MODULENAME,
3017                                                         col_name,
3018                                                         cname
3019                                                 );
3020                                                 if( ctx )
3021                                                         osrfAppSessionStatus(
3022                                                                 ctx->session,
3023                                                                 OSRF_STATUS_INTERNALSERVERERROR,
3024                                                                 "osrfMethodException",
3025                                                                 ctx->request,
3026                                                                 "Selected column not defined in JSON query"
3027                                                         );
3028                                                 jsonIteratorFree( select_itr );
3029                                                 jsonIteratorFree( selclass_itr );
3030                                                 buffer_free( sql_buf );
3031                                                 buffer_free( select_buf );
3032                                                 buffer_free( group_buf );
3033                                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3034                                                 free( core_class );
3035                                                 return NULL;
3036                                         } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3037                                                 // Virtual field not allowed
3038                                                 osrfLogError(
3039                                                         OSRF_LOG_MARK,
3040                                                         "%s: Selected column \"%s\" for class \"%s\" is virtual",
3041                                                         MODULENAME,
3042                                                         col_name,
3043                                                         cname
3044                                                 );
3045                                                 if( ctx )
3046                                                         osrfAppSessionStatus(
3047                                                                 ctx->session,
3048                                                                 OSRF_STATUS_INTERNALSERVERERROR,
3049                                                                 "osrfMethodException",
3050                                                                 ctx->request,
3051                                                                 "Selected column may not be virtual in JSON query"
3052                                                         );
3053                                                 jsonIteratorFree( select_itr );
3054                                                 jsonIteratorFree( selclass_itr );
3055                                                 buffer_free( sql_buf );
3056                                                 buffer_free( select_buf );
3057                                                 buffer_free( group_buf );
3058                                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3059                                                 free( core_class );
3060                                                 return NULL;
3061                                         }
3062
3063                                         if (locale) {
3064                                                 const char* i18n;
3065                                                 if (flags & DISABLE_I18N)
3066                                                         i18n = NULL;
3067                                                 else
3068                                                         i18n = osrfHashGet(field_def, "i18n");
3069
3070                                                 if( str_is_true( i18n ) ) {
3071                             buffer_fadd( select_buf,
3072                                                                 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3073                                                                 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3074                         } else {
3075                                             buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3076                         }
3077                     } else {
3078                                         buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3079                     }
3080                                         
3081                                 // ... but it could be an object, in which case we check for a Field Transform
3082                                 } else if (selfield->type == JSON_HASH) {
3083
3084                                         const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3085
3086                                         // Get the field definition from the IDL
3087                                         osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3088                                         if ( !field_def ) {
3089                                                 // No such field in current class
3090                                                 osrfLogError(
3091                                                         OSRF_LOG_MARK,
3092                                                         "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3093                                                         MODULENAME,
3094                                                         col_name,
3095                                                         cname
3096                                                 );
3097                                                 if( ctx )
3098                                                         osrfAppSessionStatus(
3099                                                                 ctx->session,
3100                                                                 OSRF_STATUS_INTERNALSERVERERROR,
3101                                                                 "osrfMethodException",
3102                                                                 ctx->request,
3103                                                                 "Selected column is not defined in JSON query"
3104                                                         );
3105                                                 jsonIteratorFree( select_itr );
3106                                                 jsonIteratorFree( selclass_itr );
3107                                                 buffer_free( sql_buf );
3108                                                 buffer_free( select_buf );
3109                                                 buffer_free( group_buf );
3110                                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3111                                                 free( core_class );
3112                                                 return NULL;
3113                                         } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3114                                                 // No such field in current class
3115                                                 osrfLogError(
3116                                                         OSRF_LOG_MARK,
3117                                                         "%s: Selected column \"%s\" is virtual for class \"%s\"",
3118                                                         MODULENAME,
3119                                                         col_name,
3120                                                         cname
3121                                                 );
3122                                                 if( ctx )
3123                                                         osrfAppSessionStatus(
3124                                                                 ctx->session,
3125                                                                 OSRF_STATUS_INTERNALSERVERERROR,
3126                                                                 "osrfMethodException",
3127                                                                 ctx->request,
3128                                                                 "Selected column is virtual in JSON query"
3129                                                         );
3130                                                 jsonIteratorFree( select_itr );
3131                                                 jsonIteratorFree( selclass_itr );
3132                                                 buffer_free( sql_buf );
3133                                                 buffer_free( select_buf );
3134                                                 buffer_free( group_buf );
3135                                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3136                                                 free( core_class );
3137                                                 return NULL;
3138                                         }
3139
3140                                         // Decide what to use as a column alias
3141                                         const char* _alias;
3142                                         if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3143                                                 _alias = jsonObjectGetString( tmp_const );
3144                                         } else {         // Use field name as the alias
3145                                                 _alias = col_name;
3146                                         }
3147
3148                                         if (jsonObjectGetKeyConst( selfield, "transform" )) {
3149                                                 char* transform_str = searchFieldTransform(cname, field_def, selfield);
3150                                                 if( transform_str ) {
3151                                                         buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3152                                                         free(transform_str);
3153                                                 } else {
3154                                                         if( ctx )
3155                                                                 osrfAppSessionStatus(
3156                                                                         ctx->session,
3157                                                                         OSRF_STATUS_INTERNALSERVERERROR,
3158                                                                         "osrfMethodException",
3159                                                                         ctx->request,
3160                                                                         "Unable to generate transform function in JSON query"
3161                                                                 );
3162                                                         jsonIteratorFree( select_itr );
3163                                                         jsonIteratorFree( selclass_itr );
3164                                                         buffer_free( sql_buf );
3165                                                         buffer_free( select_buf );
3166                                                         buffer_free( group_buf );
3167                                                         if( defaultselhash ) jsonObjectFree( defaultselhash );
3168                                                         free( core_class );
3169                                                         return NULL;
3170                                                 }
3171                                         } else {
3172
3173                                                 if (locale) {
3174                                                         const char* i18n;
3175                                                         if (flags & DISABLE_I18N)
3176                                                                 i18n = NULL;
3177                                                         else
3178                                                                 i18n = osrfHashGet(field_def, "i18n");
3179
3180                                                         if( str_is_true( i18n ) ) {
3181                                                                 buffer_fadd( select_buf,
3182                                                                         " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3183                                                                         class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3184                                                         } else {
3185                                                                 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3186                                                         }
3187                                                 } else {
3188                                                         buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3189                                                 }
3190                                         }
3191                                 }
3192                                 else {
3193                                         osrfLogError(
3194                                                 OSRF_LOG_MARK,
3195                                                 "%s: Selected item is unexpected JSON type: %s",
3196                                                 MODULENAME,
3197                                                 json_type( selfield->type )
3198                                         );
3199                                         if( ctx )
3200                                                 osrfAppSessionStatus(
3201                                                         ctx->session,
3202                                                         OSRF_STATUS_INTERNALSERVERERROR,
3203                                                         "osrfMethodException",
3204                                                         ctx->request,
3205                                                         "Ill-formed SELECT item in JSON query"
3206                                                 );
3207                                         jsonIteratorFree( select_itr );
3208                                         jsonIteratorFree( selclass_itr );
3209                                         buffer_free( sql_buf );
3210                                         buffer_free( select_buf );
3211                                         buffer_free( group_buf );
3212                                         if( defaultselhash ) jsonObjectFree( defaultselhash );
3213                                         free( core_class );
3214                                         return NULL;
3215                                 }
3216
3217                                 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3218                                 if( obj_is_true( agg_obj ) )
3219                                         aggregate_found = 1;
3220                                 else {
3221                                         // Append a comma (except for the first one)
3222                                         // and add the column to a GROUP BY clause
3223                                         if (gfirst)
3224                                                 gfirst = 0;
3225                                         else
3226                                                 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3227
3228                                         buffer_fadd(group_buf, " %d", sel_pos);
3229                                 }
3230
3231 #if 0
3232                             if (is_agg->size || (flags & SELECT_DISTINCT)) {
3233
3234                                         const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3235                                     if ( ! obj_is_true( aggregate_obj ) ) {
3236                                             if (gfirst) {
3237                                                     gfirst = 0;
3238                                             } else {
3239                                                         OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3240                                             }
3241
3242                                             buffer_fadd(group_buf, " %d", sel_pos);
3243
3244                                         /*
3245                                     } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3246                                             if (gfirst) {
3247                                                     gfirst = 0;
3248                                             } else {
3249                                                         OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3250                                             }
3251
3252                                             _column = searchFieldTransform(cname, field, selfield);
3253                                                 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3254                                                 OSRF_BUFFER_ADD(group_buf, _column);
3255                                             _column = searchFieldTransform(cname, field, selfield);
3256                                         */
3257                                     }
3258                             }
3259 #endif
3260
3261                             sel_pos++;
3262                     } // end while -- iterating across SELECT columns
3263
3264             jsonIteratorFree(select_itr);
3265             } // end while -- iterating across classes
3266
3267         jsonIteratorFree(selclass_itr);
3268     }
3269
3270
3271         char* col_list = buffer_release(select_buf);
3272         char* table = NULL;
3273         if (from_function) table = searchValueTransform(join_hash);
3274         else table = getSourceDefinition(core_meta);
3275         
3276         if( !table ) {
3277                 if (ctx)
3278                         osrfAppSessionStatus(
3279                                 ctx->session,
3280                                 OSRF_STATUS_INTERNALSERVERERROR,
3281                                 "osrfMethodException",
3282                                 ctx->request,
3283                                 "Unable to identify table for core class"
3284                         );
3285                 free( col_list );
3286                 buffer_free( sql_buf );
3287                 buffer_free( group_buf );
3288                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3289                 free( core_class );
3290                 return NULL;    
3291         }
3292         
3293         // Put it all together
3294         buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3295         free(col_list);
3296         free(table);
3297
3298         char* order_by_list = NULL;
3299         char* having_buf = NULL;
3300
3301         if (!from_function) {
3302
3303                 // Now, walk the join tree and add that clause
3304                 if ( join_hash ) {
3305                         char* join_clause = searchJOIN( join_hash, core_meta );
3306                         if( join_clause ) {
3307                                 buffer_add(sql_buf, join_clause);
3308                                 free(join_clause);
3309                         } else {
3310                                 if (ctx)
3311                                         osrfAppSessionStatus(
3312                                                 ctx->session,
3313                                                 OSRF_STATUS_INTERNALSERVERERROR,
3314                                                 "osrfMethodException",
3315                                                 ctx->request,
3316                                                 "Unable to construct JOIN clause(s)"
3317                                         );
3318                                 buffer_free( sql_buf );
3319                                 buffer_free( group_buf );
3320                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3321                                 free( core_class );
3322                                 return NULL;
3323                         }
3324             }
3325
3326                 // Build a WHERE clause, if there is one
3327             if ( search_hash ) {
3328                     buffer_add(sql_buf, " WHERE ");
3329
3330                     // and it's on the WHERE clause
3331                     char* pred = searchWHERE( search_hash, core_meta, AND_OP_JOIN, ctx );
3332
3333                     if (pred) {
3334                                 buffer_add(sql_buf, pred);
3335                                 free(pred);
3336                         } else {
3337                                 if (ctx) {
3338                                 osrfAppSessionStatus(
3339                                         ctx->session,
3340                                         OSRF_STATUS_INTERNALSERVERERROR,
3341                                         "osrfMethodException",
3342                                         ctx->request,
3343                                         "Severe query error in WHERE predicate -- see error log for more details"
3344                                 );
3345                             }
3346                             free(core_class);
3347                             buffer_free(group_buf);
3348                             buffer_free(sql_buf);
3349                             if (defaultselhash) jsonObjectFree(defaultselhash);
3350                             return NULL;
3351                         }
3352                 }
3353
3354                 // Build a HAVING clause, if there is one
3355                 if ( having_hash ) {
3356
3357                         // and it's on the the WHERE clause
3358                         having_buf = searchWHERE( having_hash, core_meta, AND_OP_JOIN, ctx );
3359
3360                         if( ! having_buf ) {
3361                                 if (ctx) {
3362                                                 osrfAppSessionStatus(
3363                                                 ctx->session,
3364                                                 OSRF_STATUS_INTERNALSERVERERROR,
3365                                                 "osrfMethodException",
3366                                                 ctx->request,
3367                                                 "Severe query error in HAVING predicate -- see error log for more details"
3368                                         );
3369                                 }
3370                                 free(core_class);
3371                                 buffer_free(group_buf);
3372                                 buffer_free(sql_buf);
3373                                 if (defaultselhash) jsonObjectFree(defaultselhash);
3374                                 return NULL;
3375                         }
3376                 }
3377
3378                 growing_buffer* order_buf = NULL;  // to collect ORDER BY list
3379
3380                 // Build an ORDER BY clause, if there is one
3381                 if( NULL == order_hash )
3382                         ;  // No ORDER BY? do nothing
3383                 else if( JSON_ARRAY == order_hash->type ) {
3384                         // Array of field specifications, each specification being a
3385                         // hash to define the class, field, and other details
3386                         int order_idx = 0;
3387                         jsonObject* order_spec;
3388                         while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
3389
3390                                 if( JSON_HASH != order_spec->type ) {
3391                                         osrfLogError(OSRF_LOG_MARK,
3392                                                  "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
3393                                                 MODULENAME, json_type( order_spec->type ) );
3394                                         if( ctx )
3395                                                 osrfAppSessionStatus(
3396                                                          ctx->session,
3397                                                         OSRF_STATUS_INTERNALSERVERERROR,
3398                                                         "osrfMethodException",
3399                                                         ctx->request,
3400                                                         "Malformed ORDER BY clause -- see error log for more details"
3401                                                 );
3402                                         buffer_free( order_buf );
3403                                         free(core_class);
3404                                         free(having_buf);
3405                                         buffer_free(group_buf);
3406                                         buffer_free(sql_buf);
3407                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3408                                         return NULL;
3409                                 }
3410
3411                                 const char* class =
3412                                                 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
3413                                 const char* field =
3414                                                 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
3415
3416                                 if ( order_buf )
3417                                         OSRF_BUFFER_ADD(order_buf, ", ");
3418                                 else
3419                                         order_buf = buffer_init(128);
3420
3421                                 if( !field || !class ) {
3422                                         osrfLogError(OSRF_LOG_MARK,
3423                                                 "%s: Missing class or field name in field specification of ORDER BY clause",
3424                                                  MODULENAME );
3425                                         if( ctx )
3426                                                 osrfAppSessionStatus(
3427                                                         ctx->session,
3428                                                         OSRF_STATUS_INTERNALSERVERERROR,
3429                                                         "osrfMethodException",
3430                                                         ctx->request,
3431                                                         "Malformed ORDER BY clause -- see error log for more details"
3432                                                 );
3433                                         buffer_free( order_buf );
3434                                         free(core_class);
3435                                         free(having_buf);
3436                                         buffer_free(group_buf);
3437                                         buffer_free(sql_buf);
3438                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3439                                         return NULL;
3440                                 }
3441
3442                                 if (    ! jsonObjectGetKeyConst( selhash, class )
3443                                          && strcmp( core_class, class )
3444                                          && ! is_joined( join_hash, class ) ) {
3445                                         osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
3446                                                         "not in FROM clause", MODULENAME, class );
3447                                         if( ctx )
3448                                                 osrfAppSessionStatus(
3449                                                         ctx->session,
3450                                                         OSRF_STATUS_INTERNALSERVERERROR,
3451                                                         "osrfMethodException",
3452                                                         ctx->request,
3453                                                         "Invalid class referenced in ORDER BY clause -- see error log for more details"
3454                                                 );
3455                                         free(core_class);
3456                                         free(having_buf);
3457                                         buffer_free(group_buf);
3458                                         buffer_free(sql_buf);
3459                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3460                                         return NULL;
3461                                 }
3462
3463                                 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s", class, field );
3464                                 if( !field_def ) {
3465                                         osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
3466                                                  MODULENAME, class, field );
3467                                         if( ctx )
3468                                                 osrfAppSessionStatus(
3469                                                         ctx->session,
3470                                                         OSRF_STATUS_INTERNALSERVERERROR,
3471                                                         "osrfMethodException",
3472                                                         ctx->request,
3473                                                         "Invalid field referenced in ORDER BY clause -- see error log for more details"
3474                                                 );
3475                                         free(core_class);
3476                                         free(having_buf);
3477                                         buffer_free(group_buf);
3478                                         buffer_free(sql_buf);
3479                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3480                                         return NULL;
3481                                 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3482                                         osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3483                                                                  MODULENAME, field );
3484                                         if( ctx )
3485                                                 osrfAppSessionStatus(
3486                                                         ctx->session,
3487                                                         OSRF_STATUS_INTERNALSERVERERROR,
3488                                                         "osrfMethodException",
3489                                                         ctx->request,
3490                                                         "Virtual field in ORDER BY clause -- see error log for more details"
3491                                                 );
3492                                         buffer_free( order_buf );
3493                                         free(core_class);
3494                                         free(having_buf);
3495                                         buffer_free(group_buf);
3496                                         buffer_free(sql_buf);
3497                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3498                                         return NULL;
3499                                 }
3500
3501                                 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
3502                                         char* transform_str = searchFieldTransform( class, field_def, order_spec );
3503                                         if( ! transform_str ) {
3504                                                 if( ctx )
3505                                                         osrfAppSessionStatus(
3506                                                                 ctx->session,
3507                                                                 OSRF_STATUS_INTERNALSERVERERROR,
3508                                                                 "osrfMethodException",
3509                                                                 ctx->request,
3510                                                                 "Severe query error in ORDER BY clause -- see error log for more details"
3511                                                         );
3512                                                 buffer_free( order_buf );
3513                                                 free(core_class);
3514                                                 free(having_buf);
3515                                                 buffer_free(group_buf);
3516                                                 buffer_free(sql_buf);
3517                                                 if (defaultselhash) jsonObjectFree(defaultselhash);
3518                                                 return NULL;
3519                                         }
3520                                         
3521                                         OSRF_BUFFER_ADD( order_buf, transform_str );
3522                                         free( transform_str );
3523                                 }
3524                                 else
3525                                         buffer_fadd( order_buf, "\"%s\".%s", class, field );
3526
3527                                 const char* direction =
3528                                                 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
3529                                 if( direction ) {
3530                                         if( direction[ 0 ] || 'D' == direction[ 0 ] )
3531                                                 OSRF_BUFFER_ADD( order_buf, " DESC" );
3532                                         else
3533                                                 OSRF_BUFFER_ADD( order_buf, " ASC" );
3534                                 }
3535                         }
3536                 } else if( JSON_HASH == order_hash->type ) {
3537                         // This hash is keyed on class name.  Each class has either
3538                         // an array of field names or a hash keyed on field name.
3539                         jsonIterator* class_itr = jsonNewIterator( order_hash );
3540                         while ( (snode = jsonIteratorNext( class_itr )) ) {
3541
3542                                 if (   ! jsonObjectGetKeyConst( selhash,class_itr->key )
3543                                         && strcmp( core_class, class_itr->key )
3544                                         && ! is_joined( join_hash, class_itr->key ) ) {
3545                                         osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
3546                                                                  MODULENAME, class_itr->key );
3547                                         if( ctx )
3548                                                 osrfAppSessionStatus(
3549                                                         ctx->session,
3550                                                         OSRF_STATUS_INTERNALSERVERERROR,
3551                                                         "osrfMethodException",
3552                                                         ctx->request,
3553                                                         "Invalid class referenced in ORDER BY clause -- see error log for more details"
3554                                                 );
3555                                         jsonIteratorFree( class_itr );
3556                                         buffer_free( order_buf );
3557                                         free(core_class);
3558                                         free(having_buf);
3559                                         buffer_free(group_buf);
3560                                         buffer_free(sql_buf);
3561                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3562                                         return NULL;
3563                                 }
3564
3565                                 osrfHash* field_list_def = oilsIDLFindPath( "/%s/fields", class_itr->key );
3566
3567                                 if ( snode->type == JSON_HASH ) {
3568
3569                                         // Hash is keyed on field names from the current class.  For each field
3570                                         // there is another layer of hash to define the sorting details, if any,
3571                                         // or a string to indicate direction of sorting.
3572                                         jsonIterator* order_itr = jsonNewIterator( snode );
3573                                         while ( (onode = jsonIteratorNext( order_itr )) ) {
3574
3575                                                 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
3576                                                 if( !field_def ) {
3577                                                         osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3578                                                                         MODULENAME, order_itr->key );
3579                                                         if( ctx )
3580                                                                 osrfAppSessionStatus(
3581                                                                         ctx->session,
3582                                                                         OSRF_STATUS_INTERNALSERVERERROR,
3583                                                                         "osrfMethodException",
3584                                                                         ctx->request,
3585                                                                         "Invalid field in ORDER BY clause -- see error log for more details"
3586                                                                 );
3587                                                         jsonIteratorFree( order_itr );
3588                                                         jsonIteratorFree( class_itr );
3589                                                         buffer_free( order_buf );
3590                                                         free(core_class);
3591                                                         free(having_buf);
3592                                                         buffer_free(group_buf);
3593                                                         buffer_free(sql_buf);
3594                                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3595                                                         return NULL;
3596                                                 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3597                                                         osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3598                                                                  MODULENAME, order_itr->key );
3599                                                         if( ctx )
3600                                                                 osrfAppSessionStatus(
3601                                                                         ctx->session,
3602                                                                         OSRF_STATUS_INTERNALSERVERERROR,
3603                                                                         "osrfMethodException",
3604                                                                         ctx->request,
3605                                                                         "Virtual field in ORDER BY clause -- see error log for more details"
3606                                                         );
3607                                                         jsonIteratorFree( order_itr );
3608                                                         jsonIteratorFree( class_itr );
3609                                                         buffer_free( order_buf );
3610                                                         free(core_class);
3611                                                         free(having_buf);
3612                                                         buffer_free(group_buf);
3613                                                         buffer_free(sql_buf);
3614                                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3615                                                         return NULL;
3616                                                 }
3617
3618                                                 const char* direction = NULL;
3619                                                 if ( onode->type == JSON_HASH ) {
3620                                                         if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3621                                                                 string = searchFieldTransform(
3622                                                                         class_itr->key,
3623                                                                         osrfHashGet( field_list_def, order_itr->key ),
3624                                                                         onode
3625                                                                 );
3626                                                                 if( ! string ) {
3627                                                                         if( ctx ) osrfAppSessionStatus(
3628                                                                                 ctx->session,
3629                                                                                 OSRF_STATUS_INTERNALSERVERERROR,
3630                                                                                 "osrfMethodException",
3631                                                                                 ctx->request,
3632                                                                                 "Severe query error in ORDER BY clause -- see error log for more details"
3633                                                                         );
3634                                                                         jsonIteratorFree( order_itr );
3635                                                                         jsonIteratorFree( class_itr );
3636                                                                         free(core_class);
3637                                                                         free(having_buf);
3638                                                                         buffer_free(group_buf);
3639                                                                         buffer_free(order_buf);
3640                                                                         buffer_free(sql_buf);
3641                                                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3642                                                                         return NULL;
3643                                                                 }
3644                                                         } else {
3645                                                                 growing_buffer* field_buf = buffer_init(16);
3646                                                                 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3647                                                                 string = buffer_release(field_buf);
3648                                                         }
3649
3650                                                         if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3651                                                                 const char* dir = jsonObjectGetString(tmp_const);
3652                                                                 if (!strncasecmp(dir, "d", 1)) {
3653                                                                         direction = " DESC";
3654                                                                 } else {
3655                                                                         direction = " ASC";
3656                                                                 }
3657                                                         }
3658
3659                                                 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
3660                                                         osrfLogError( OSRF_LOG_MARK,
3661                                                                 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
3662                                                                 MODULENAME, json_type( onode->type ) );
3663                                                         if( ctx )
3664                                                                 osrfAppSessionStatus(
3665                                                                         ctx->session,
3666                                                                         OSRF_STATUS_INTERNALSERVERERROR,
3667                                                                         "osrfMethodException",
3668                                                                         ctx->request,
3669                                                                         "Malformed ORDER BY clause -- see error log for more details"
3670                                                                 );
3671                                                         jsonIteratorFree( order_itr );
3672                                                         jsonIteratorFree( class_itr );
3673                                                         free(core_class);
3674                                                         free(having_buf);
3675                                                         buffer_free(group_buf);
3676                                                         buffer_free(order_buf);
3677                                                         buffer_free(sql_buf);
3678                                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3679                                                         return NULL;
3680
3681                                                 } else {
3682                                                         string = strdup(order_itr->key);
3683                                                         const char* dir = jsonObjectGetString(onode);
3684                                                         if (!strncasecmp(dir, "d", 1)) {
3685                                                                 direction = " DESC";
3686                                                         } else {
3687                                                                 direction = " ASC";
3688                                                         }
3689                                                 }
3690
3691                                                 if ( order_buf )
3692                                                         OSRF_BUFFER_ADD(order_buf, ", ");
3693                                                 else
3694                                                         order_buf = buffer_init(128);
3695
3696                                                 OSRF_BUFFER_ADD(order_buf, string);
3697                                                 free(string);
3698
3699                                                 if (direction) {
3700                                                          OSRF_BUFFER_ADD(order_buf, direction);
3701                                                 }
3702
3703                                         } // end while
3704                                         jsonIteratorFree(order_itr);
3705
3706                                 } else if ( snode->type == JSON_ARRAY ) {
3707
3708                                         // Array is a list of fields from the current class
3709                                         unsigned long order_idx = 0;
3710                                         while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
3711
3712                                                 const char* _f = jsonObjectGetString( onode );
3713
3714                                                 osrfHash* field_def = osrfHashGet( field_list_def, _f );
3715                                                 if( !field_def ) {
3716                                                         osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3717                                                                         MODULENAME, _f );
3718                                                         if( ctx )
3719                                                                 osrfAppSessionStatus(
3720                                                                         ctx->session,
3721                                                                         OSRF_STATUS_INTERNALSERVERERROR,
3722                                                                         "osrfMethodException",
3723                                                                         ctx->request,
3724                                                                         "Invalid field in ORDER BY clause -- see error log for more details"
3725                                                                 );
3726                                                         jsonIteratorFree( class_itr );
3727                                                         buffer_free( order_buf );
3728                                                         free(core_class);
3729                                                         free(having_buf);
3730                                                         buffer_free(group_buf);
3731                                                         buffer_free(sql_buf);
3732                                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3733                                                         return NULL;
3734                                                 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3735                                                         osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3736                                                                         MODULENAME, _f );
3737                                                         if( ctx )
3738                                                                 osrfAppSessionStatus(
3739                                                                         ctx->session,
3740                                                                         OSRF_STATUS_INTERNALSERVERERROR,
3741                                                                         "osrfMethodException",
3742                                                                         ctx->request,
3743                                                                         "Virtual field in ORDER BY clause -- see error log for more details"
3744                                                                 );
3745                                                         jsonIteratorFree( class_itr );
3746                                                         buffer_free( order_buf );
3747                                                         free(core_class);
3748                                                         free(having_buf);
3749                                                         buffer_free(group_buf);
3750                                                         buffer_free(sql_buf);
3751                                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3752                                                         return NULL;
3753                                                 }
3754
3755                                                 if ( order_buf )
3756                                                         OSRF_BUFFER_ADD(order_buf, ", ");
3757                                                 else
3758                                                         order_buf = buffer_init(128);
3759
3760                                                 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
3761
3762                                         } // end while
3763
3764                                 // IT'S THE OOOOOOOOOOOLD STYLE!
3765                                 } else {
3766                                         osrfLogError(OSRF_LOG_MARK, 
3767                                                         "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3768                                         if (ctx) {
3769                                                 osrfAppSessionStatus(
3770                                                         ctx->session,
3771                                                         OSRF_STATUS_INTERNALSERVERERROR,
3772                                                         "osrfMethodException",
3773                                                         ctx->request,
3774                                                         "Severe query error -- see error log for more details"
3775                                                 );
3776                                         }
3777
3778                                         free(core_class);
3779                                         free(having_buf);
3780                                         buffer_free(group_buf);
3781                                         buffer_free(order_buf);
3782                                         buffer_free(sql_buf);
3783                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3784                                         jsonIteratorFree(class_itr);
3785                                         return NULL;
3786                                 }
3787                         } // end while
3788                         jsonIteratorFree( class_itr );
3789                 } else {
3790                         osrfLogError(OSRF_LOG_MARK,
3791                                 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
3792                                 MODULENAME, json_type( order_hash->type ) );
3793                         if( ctx )
3794                                 osrfAppSessionStatus(
3795                                         ctx->session,
3796                                         OSRF_STATUS_INTERNALSERVERERROR,
3797                                         "osrfMethodException",
3798                                         ctx->request,
3799                                         "Malformed ORDER BY clause -- see error log for more details"
3800                                 );
3801                         buffer_free( order_buf );
3802                         free(core_class);
3803                         free(having_buf);
3804                         buffer_free(group_buf);
3805                         buffer_free(sql_buf);
3806                         if (defaultselhash) jsonObjectFree(defaultselhash);
3807                         return NULL;
3808                 }
3809
3810                 if( order_buf )
3811                         order_by_list = buffer_release( order_buf );
3812         }
3813
3814
3815         string = buffer_release(group_buf);
3816
3817         if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
3818                 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3819                 OSRF_BUFFER_ADD( sql_buf, string );
3820         }
3821
3822         free(string);
3823
3824         if( having_buf && *having_buf ) {
3825                 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3826                 OSRF_BUFFER_ADD( sql_buf, having_buf );
3827                 free( having_buf );
3828         }
3829
3830         if( order_by_list ) {
3831
3832                 if ( *order_by_list ) {
3833                         OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3834                         OSRF_BUFFER_ADD( sql_buf, order_by_list );
3835                 }
3836
3837                 free( order_by_list );
3838         }
3839
3840         if ( limit ){
3841                 const char* str = jsonObjectGetString(limit);
3842                 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
3843         }
3844
3845         if (offset) {
3846                 const char* str = jsonObjectGetString(offset);
3847                 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
3848         }
3849
3850         if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3851
3852         free(core_class);
3853         if (defaultselhash) jsonObjectFree(defaultselhash);
3854
3855         return buffer_release(sql_buf);
3856
3857 }
3858
3859 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
3860
3861         const char* locale = osrf_message_get_last_locale();
3862
3863         osrfHash* fields = osrfHashGet(meta, "fields");
3864         char* core_class = osrfHashGet(meta, "classname");
3865
3866         const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
3867
3868         jsonObject* node = NULL;
3869         jsonObject* snode = NULL;
3870         jsonObject* onode = NULL;
3871         const jsonObject* _tmp = NULL;
3872         jsonObject* selhash = NULL;
3873         jsonObject* defaultselhash = NULL;
3874
3875         growing_buffer* sql_buf = buffer_init(128);
3876         growing_buffer* select_buf = buffer_init(128);
3877
3878         if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
3879                 defaultselhash = jsonNewObjectType(JSON_HASH);
3880                 selhash = defaultselhash;
3881         }
3882         
3883         if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
3884                 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
3885                 jsonObject* flist = jsonObjectGetKey( selhash, core_class );
3886                 
3887                 int i = 0;
3888                 char* field;
3889
3890                 osrfStringArray* keys = osrfHashKeys( fields );
3891                 while ( (field = osrfStringArrayGetString(keys, i++)) ) {
3892                         if( ! str_is_true( osrfHashGet( osrfHashGet( fields, field ), "virtual" ) ) )
3893                                 jsonObjectPush( flist, jsonNewObject( field ) );
3894                 }
3895                 osrfStringArrayFree(keys);
3896         }
3897
3898         int first = 1;
3899         jsonIterator* class_itr = jsonNewIterator( selhash );
3900         while ( (snode = jsonIteratorNext( class_itr )) ) {
3901
3902                 char* cname = class_itr->key;
3903                 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
3904                 if (!idlClass) continue;
3905
3906                 if (strcmp(core_class,class_itr->key)) {
3907                         if (!join_hash) continue;
3908
3909                         jsonObject* found =  jsonObjectFindPath(join_hash, "//%s", class_itr->key);
3910                         if (!found->size) {
3911                                 jsonObjectFree(found);
3912                                 continue;
3913                         }
3914
3915                         jsonObjectFree(found);
3916                 }
3917
3918                 jsonIterator* select_itr = jsonNewIterator( snode );
3919                 while ( (node = jsonIteratorNext( select_itr )) ) {
3920                         const char* item_str = jsonObjectGetString( node );
3921                         osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
3922                         char* fname = osrfHashGet(field, "name");
3923
3924                         if (!field) continue;
3925
3926                         if (first) {
3927                                 first = 0;
3928                         } else {
3929                                 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
3930                         }
3931
3932             if (locale) {
3933                         const char* i18n;
3934                                 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
3935                                 if ( obj_is_true( no_i18n_obj ) )    // Suppress internationalization?
3936                                         i18n = NULL;
3937                                 else
3938                                         i18n = osrfHashGet(field, "i18n");
3939
3940                                 if( str_is_true( i18n ) ) {
3941                         char* pkey = osrfHashGet(idlClass, "primarykey");
3942                         char* tname = osrfHashGet(idlClass, "tablename");
3943
3944                     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);
3945                 } else {
3946                                 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3947                 }
3948             } else {
3949                             buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3950             }
3951                 }
3952
3953         jsonIteratorFree(select_itr);
3954         }
3955
3956     jsonIteratorFree(class_itr);
3957
3958         char* col_list = buffer_release(select_buf);
3959         char* table = getSourceDefinition(meta);
3960         if( !table )
3961                 table = strdup( "(null)" );
3962
3963         buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
3964         free(col_list);
3965         free(table);
3966
3967         if ( join_hash ) {
3968                 char* join_clause = searchJOIN( join_hash, meta );
3969                 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
3970                 OSRF_BUFFER_ADD(sql_buf, join_clause);
3971                 free(join_clause);
3972         }
3973
3974         osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL =  %s",
3975                                  MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
3976
3977         OSRF_BUFFER_ADD(sql_buf, " WHERE ");
3978
3979         char* pred = searchWHERE( search_hash, meta, AND_OP_JOIN, ctx );
3980         if (!pred) {
3981                 osrfAppSessionStatus(
3982                         ctx->session,
3983                         OSRF_STATUS_INTERNALSERVERERROR,
3984                                 "osrfMethodException",
3985                                 ctx->request,
3986                                 "Severe query error -- see error log for more details"
3987                         );
3988                 buffer_free(sql_buf);
3989                 if(defaultselhash) jsonObjectFree(defaultselhash);
3990                 return NULL;
3991         } else {
3992                 buffer_add(sql_buf, pred);
3993                 free(pred);
3994         }
3995
3996         if (order_hash) {
3997                 char* string = NULL;
3998                 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
3999
4000                         growing_buffer* order_buf = buffer_init(128);
4001
4002                         first = 1;
4003                         jsonIterator* class_itr = jsonNewIterator( _tmp );
4004                         while ( (snode = jsonIteratorNext( class_itr )) ) {
4005
4006                                 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4007                                         continue;
4008
4009                                 if ( snode->type == JSON_HASH ) {
4010
4011                                         jsonIterator* order_itr = jsonNewIterator( snode );
4012                                         while ( (onode = jsonIteratorNext( order_itr )) ) {
4013
4014                                                 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4015                                                                 class_itr->key, order_itr->key );
4016                                                 if ( !field_def )
4017                                                         continue;
4018
4019                                                 char* direction = NULL;
4020                                                 if ( onode->type == JSON_HASH ) {
4021                                                         if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4022                                                                 string = searchFieldTransform( class_itr->key, field_def, onode );
4023                                                                 if( ! string ) {
4024                                                                         osrfAppSessionStatus(
4025                                                                                 ctx->session,
4026                                                                                 OSRF_STATUS_INTERNALSERVERERROR,
4027                                                                                 "osrfMethodException",
4028                                                                                 ctx->request,
4029                                                                                 "Severe query error in ORDER BY clause -- see error log for more details"
4030                                                                         );
4031                                                                         jsonIteratorFree( order_itr );
4032                                                                         jsonIteratorFree( class_itr );
4033                                                                         buffer_free( order_buf );
4034                                                                         buffer_free( sql_buf );
4035                                                                         if( defaultselhash ) jsonObjectFree( defaultselhash );
4036                                                                         return NULL;
4037                                                                 }
4038                                                         } else {
4039                                                                 growing_buffer* field_buf = buffer_init(16);
4040                                                                 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4041                                                                 string = buffer_release(field_buf);
4042                                                         }
4043
4044                                                         if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4045                                                                 const char* dir = jsonObjectGetString(_tmp);
4046                                                                 if (!strncasecmp(dir, "d", 1)) {
4047                                                                         direction = " DESC";
4048                                                                 } else {
4049                                                                         free(direction);
4050                                                                 }
4051                                                         }
4052
4053                                                 } else {
4054                                                         string = strdup(order_itr->key);
4055                                                         const char* dir = jsonObjectGetString(onode);
4056                                                         if (!strncasecmp(dir, "d", 1)) {
4057                                                                 direction = " DESC";
4058                                                         } else {
4059                                                                 direction = " ASC";
4060                                                         }
4061                                                 }
4062
4063                                                 if (first) {
4064                                                         first = 0;
4065                                                 } else {
4066                                                         buffer_add(order_buf, ", ");
4067                                                 }
4068
4069                                                 buffer_add(order_buf, string);
4070                                                 free(string);
4071
4072                                                 if (direction) {
4073                                                         buffer_add(order_buf, direction);
4074                                                 }
4075
4076                                         }
4077
4078                     jsonIteratorFree(order_itr);
4079
4080                                 } else {
4081                                         const char* str = jsonObjectGetString(snode);
4082                                         buffer_add(order_buf, str);
4083                                         break;
4084                                 }
4085
4086                         }
4087
4088                         jsonIteratorFree(class_itr);
4089
4090                         string = buffer_release(order_buf);
4091
4092                         if ( *string ) {
4093                                 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4094                                 OSRF_BUFFER_ADD( sql_buf, string );
4095                         }
4096
4097                         free(string);
4098                 }
4099
4100                 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4101                         const char* str = jsonObjectGetString(_tmp);
4102                         buffer_fadd(
4103                                 sql_buf,
4104                                 " LIMIT %d",
4105                                 atoi(str)
4106                         );
4107                 }
4108
4109                 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4110                 if (_tmp) {
4111                         const char* str = jsonObjectGetString(_tmp);
4112                         buffer_fadd(
4113                                 sql_buf,
4114                                 " OFFSET %d",
4115                                 atoi(str)
4116                         );
4117                 }
4118         }
4119
4120         if (defaultselhash) jsonObjectFree(defaultselhash);
4121
4122         OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4123         return buffer_release(sql_buf);
4124 }
4125
4126 int doJSONSearch ( osrfMethodContext* ctx ) {
4127         if(osrfMethodVerifyContext( ctx )) {
4128                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
4129                 return -1;
4130         }
4131
4132         osrfLogDebug(OSRF_LOG_MARK, "Recieved query request");
4133
4134         int err = 0;
4135
4136         // XXX for now...
4137         dbhandle = writehandle;
4138
4139         jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4140
4141         int flags = 0;
4142
4143         if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4144                 flags |= SELECT_DISTINCT;
4145
4146         if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4147                 flags |= DISABLE_I18N;
4148
4149         osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4150         char* sql = SELECT(
4151                         ctx,
4152                         jsonObjectGetKey( hash, "select" ),
4153                         jsonObjectGetKey( hash, "from" ),
4154                         jsonObjectGetKey( hash, "where" ),
4155                         jsonObjectGetKey( hash, "having" ),
4156                         jsonObjectGetKey( hash, "order_by" ),
4157                         jsonObjectGetKey( hash, "limit" ),
4158                         jsonObjectGetKey( hash, "offset" ),
4159                         flags
4160         );
4161
4162         if (!sql) {
4163                 err = -1;
4164                 return err;
4165         }
4166         
4167         osrfLogDebug(OSRF_LOG_MARK, "%s SQL =  %s", MODULENAME, sql);
4168         dbi_result result = dbi_conn_query(dbhandle, sql);
4169
4170         if(result) {
4171                 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4172
4173                 if (dbi_result_first_row(result)) {
4174                         /* JSONify the result */
4175                         osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4176
4177                         do {
4178                                 jsonObject* return_val = oilsMakeJSONFromResult( result );
4179                                 osrfAppRespond( ctx, return_val );
4180                 jsonObjectFree( return_val );
4181                         } while (dbi_result_next_row(result));
4182
4183                 } else {
4184                         osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4185                 }
4186
4187                 osrfAppRespondComplete( ctx, NULL );
4188
4189                 /* clean up the query */
4190                 dbi_result_free(result); 
4191
4192         } else {
4193                 err = -1;
4194                 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4195                 osrfAppSessionStatus(
4196                         ctx->session,
4197                         OSRF_STATUS_INTERNALSERVERERROR,
4198                         "osrfMethodException",
4199                         ctx->request,
4200                         "Severe query error -- see error log for more details"
4201                 );
4202         }
4203
4204         free(sql);
4205         return err;
4206 }
4207
4208 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4209                 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4210
4211         // XXX for now...
4212         dbhandle = writehandle;
4213
4214         osrfHash* links = osrfHashGet(meta, "links");
4215         osrfHash* fields = osrfHashGet(meta, "fields");
4216         char* core_class = osrfHashGet(meta, "classname");
4217         char* pkey = osrfHashGet(meta, "primarykey");
4218
4219         const jsonObject* _tmp;
4220         jsonObject* obj;
4221
4222         char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4223         if (!sql) {
4224                 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4225                 *err = -1;
4226                 return NULL;
4227         }
4228
4229         osrfLogDebug(OSRF_LOG_MARK, "%s SQL =  %s", MODULENAME, sql);
4230
4231         dbi_result result = dbi_conn_query(dbhandle, sql);
4232         if( NULL == result ) {
4233                 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4234                         MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4235                 osrfAppSessionStatus(
4236                         ctx->session,
4237                         OSRF_STATUS_INTERNALSERVERERROR,
4238                         "osrfMethodException",
4239                         ctx->request,
4240                         "Severe query error -- see error log for more details"
4241                 );
4242                 *err = -1;
4243                 free(sql);
4244                 return jsonNULL;
4245
4246         } else {
4247                 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4248         }
4249
4250         jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
4251         osrfHash* dedup = osrfNewHash();
4252
4253         if (dbi_result_first_row(result)) {
4254                 /* JSONify the result */
4255                 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4256                 do {
4257                         obj = oilsMakeFieldmapperFromResult( result, meta );
4258                         char* pkey_val = oilsFMGetString( obj, pkey );
4259                         if ( osrfHashGet( dedup, pkey_val ) ) {
4260                                 jsonObjectFree(obj);
4261                                 free(pkey_val);
4262                         } else {
4263                                 osrfHashSet( dedup, pkey_val, pkey_val );
4264                                 jsonObjectPush(res_list, obj);
4265                         }
4266                 } while (dbi_result_next_row(result));
4267         } else {
4268                 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
4269                         MODULENAME, sql );
4270         }
4271
4272         osrfHashFree(dedup);
4273         /* clean up the query */
4274         dbi_result_free(result);
4275         free(sql);
4276
4277         if (res_list->size && query_hash) {
4278                 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
4279                 if (_tmp) {
4280                         int x = (int)jsonObjectGetNumber(_tmp);
4281                         if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
4282
4283                         const jsonObject* temp_blob;
4284                         if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
4285
4286                                 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
4287                                 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
4288
4289                                 osrfStringArray* link_fields = NULL;
4290
4291                                 if (flesh_fields) {
4292                                         if (flesh_fields->size == 1) {
4293                                                 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
4294                                                 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
4295                                         }
4296
4297                                         if (!link_fields) {
4298                                                 jsonObject* _f;
4299                                                 link_fields = osrfNewStringArray(1);
4300                                                 jsonIterator* _i = jsonNewIterator( flesh_fields );
4301                                                 while ((_f = jsonIteratorNext( _i ))) {
4302                                                         osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4303                                                 }
4304                         jsonIteratorFree(_i);
4305                                         }
4306                                 }
4307
4308                                 jsonObject* cur;
4309                                 unsigned long res_idx = 0;
4310                                 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
4311
4312                                         int i = 0;
4313                                         char* link_field;
4314                                         
4315                                         while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4316
4317                                                 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4318
4319                                                 osrfHash* kid_link = osrfHashGet(links, link_field);
4320                                                 if (!kid_link) continue;
4321
4322                                                 osrfHash* field = osrfHashGet(fields, link_field);
4323                                                 if (!field) continue;
4324
4325                                                 osrfHash* value_field = field;
4326
4327                                                 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4328                                                 if (!kid_idl) continue;
4329
4330                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4331                                                         value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4332                                                 }
4333                                                         
4334                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4335                                                         value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4336                                                 }
4337
4338                                                 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4339
4340                                                 if (link_map->size > 0) {
4341                                                         jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4342                                                         jsonObjectPush(
4343                                                                 _kid_key,
4344                                                                 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4345                                                         );
4346
4347                                                         jsonObjectSetKey(
4348                                                                 flesh_blob,
4349                                                                 osrfHashGet(kid_link, "class"),
4350                                                                 _kid_key
4351                                                         );
4352                                                 };
4353
4354                                                 osrfLogDebug(
4355                                                         OSRF_LOG_MARK,
4356                                                         "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4357                                                         osrfHashGet(kid_link, "field"),
4358                                                         osrfHashGet(kid_link, "class"),
4359                                                         osrfHashGet(kid_link, "key"),
4360                                                         osrfHashGet(kid_link, "reltype")
4361                                                 );
4362
4363                                                 const char* search_key = jsonObjectGetString(
4364                                                         jsonObjectGetIndex(
4365                                                                 cur,
4366                                                                 atoi( osrfHashGet(value_field, "array_position") )
4367                                                         )
4368                                                 );
4369
4370                                                 if (!search_key) {
4371                                                         osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4372                                                         continue;
4373                                                 }
4374
4375                                                 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
4376
4377                                                 // construct WHERE clause
4378                                                 jsonObject* where_clause  = jsonNewObjectType(JSON_HASH);
4379                                                 jsonObjectSetKey(
4380                                                         where_clause,
4381                                                         osrfHashGet(kid_link, "key"),
4382                                                         jsonNewObject( search_key )
4383                                                 );
4384
4385                                                 // construct the rest of the query
4386                                                 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
4387                                                 jsonObjectSetKey( rest_of_query, "flesh",
4388                                                         jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4389                                                 );
4390
4391                                                 if (flesh_blob)
4392                                                         jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
4393
4394                                                 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
4395                                                         jsonObjectSetKey( rest_of_query, "order_by",
4396                                                                 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
4397                                                         );
4398                                                 }
4399
4400                                                 if (jsonObjectGetKeyConst(query_hash, "select")) {
4401                                                         jsonObjectSetKey( rest_of_query, "select",
4402                                                                 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
4403                                                         );
4404                                                 }
4405
4406                                                 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
4407                                                         where_clause, rest_of_query, err);
4408
4409                                                 jsonObjectFree( where_clause );
4410                                                 jsonObjectFree( rest_of_query );
4411
4412                                                 if(*err) {
4413                                                         osrfStringArrayFree(link_fields);
4414                                                         jsonObjectFree(res_list);
4415                                                         jsonObjectFree(flesh_blob);
4416                                                         return jsonNULL;
4417                                                 }
4418
4419                                                 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4420
4421                                                 jsonObject* X = NULL;
4422                                                 if ( link_map->size > 0 && kids->size > 0 ) {
4423                                                         X = kids;
4424                                                         kids = jsonNewObjectType(JSON_ARRAY);
4425
4426                                                         jsonObject* _k_node;
4427                                                         unsigned long res_idx = 0;
4428                                                         while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
4429                                                                 jsonObjectPush(
4430                                                                         kids,
4431                                                                         jsonObjectClone(
4432                                                                                 jsonObjectGetIndex(
4433                                                                                         _k_node,
4434                                                                                         (unsigned long)atoi(
4435                                                                                                 osrfHashGet(
4436                                                                                                         osrfHashGet(
4437                                                                                                                 osrfHashGet(
4438                                                                                                                         osrfHashGet(
4439                                                                                                                                 oilsIDL(),
4440                                                                                                                                 osrfHashGet(kid_link, "class")
4441                                                                                                                         ),
4442                                                                                                                         "fields"
4443                                                                                                                 ),
4444                                                                                                                 osrfStringArrayGetString( link_map, 0 )
4445                                                                                                         ),
4446                                                                                                         "array_position"
4447                                                                                                 )
4448                                                                                         )
4449                                                                                 )
4450                                                                         )
4451                                                                 );
4452                                                         } // end while loop traversing X
4453                                                 }
4454
4455                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4456                                                         osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4457                                                         jsonObjectSetIndex(
4458                                                                 cur,
4459                                                                 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4460                                                                 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4461                                                         );
4462                                                 }
4463
4464                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4465                                                         osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4466                                                         jsonObjectSetIndex(
4467                                                                 cur,
4468                                                                 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4469                                                                 jsonObjectClone( kids )
4470                                                         );
4471                                                 }
4472
4473                                                 if (X) {
4474                                                         jsonObjectFree(kids);
4475                                                         kids = X;
4476                                                 }
4477
4478                                                 jsonObjectFree( kids );
4479
4480                                                 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4481                                                 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4482
4483                                         }
4484                                 } // end while loop traversing res_list
4485                                 jsonObjectFree( flesh_blob );
4486                                 osrfStringArrayFree(link_fields);
4487                         }
4488                 }
4489         }
4490
4491         return res_list;
4492 }
4493
4494
4495 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
4496
4497         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4498 #ifdef PCRUD
4499         jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
4500 #else
4501         jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
4502 #endif
4503
4504         if (!verifyObjectClass(ctx, target)) {
4505                 *err = -1;
4506                 return jsonNULL;
4507         }
4508
4509         if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4510                 osrfAppSessionStatus(
4511                         ctx->session,
4512                         OSRF_STATUS_BADREQUEST,
4513                         "osrfMethodException",
4514                         ctx->request,
4515                         "No active transaction -- required for UPDATE"
4516                 );
4517                 *err = -1;
4518                 return jsonNULL;
4519         }
4520
4521         // The following test is harmless but redundant.  If a class is
4522         // readonly, we don't register an update method for it.
4523         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4524                 osrfAppSessionStatus(
4525                         ctx->session,
4526                         OSRF_STATUS_BADREQUEST,
4527                         "osrfMethodException",
4528                         ctx->request,
4529                         "Cannot UPDATE readonly class"
4530                 );
4531                 *err = -1;
4532                 return jsonNULL;
4533         }
4534
4535         dbhandle = writehandle;
4536
4537         char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4538
4539         // Set the last_xact_id
4540         int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4541         if (index > -1) {
4542                 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
4543                 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4544         }       
4545
4546         char* pkey = osrfHashGet(meta, "primarykey");
4547         osrfHash* fields = osrfHashGet(meta, "fields");
4548
4549         char* id = oilsFMGetString( target, pkey );
4550
4551         osrfLogDebug(
4552                 OSRF_LOG_MARK,
4553                 "%s updating %s object with %s = %s",
4554                 MODULENAME,
4555                 osrfHashGet(meta, "fieldmapper"),
4556                 pkey,
4557                 id
4558         );
4559
4560         growing_buffer* sql = buffer_init(128);
4561         buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4562
4563         int i = 0;
4564         int first = 1;
4565         char* field_name;
4566         osrfStringArray* field_list = osrfHashKeys( fields );
4567         while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
4568
4569                 osrfHash* field = osrfHashGet( fields, field_name );
4570
4571                 if(!( strcmp( field_name, pkey ) )) continue;
4572                 if( str_is_true( osrfHashGet(osrfHashGet(fields,field_name), "virtual") ) )
4573                         continue;
4574
4575                 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4576
4577                 int value_is_numeric = 0;    // boolean
4578                 char* value;
4579                 if (field_object && field_object->classname) {
4580                         value = oilsFMGetString(
4581                                 field_object,
4582                                 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4583             );
4584                 } else {
4585                         value = jsonObjectToSimpleString( field_object );
4586                         if( field_object && JSON_NUMBER == field_object->type )
4587                                 value_is_numeric = 1;
4588                 }
4589
4590                 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s", osrfHashGet(meta, "fieldmapper"), field_name, value);
4591
4592                 if (!field_object || field_object->type == JSON_NULL) {
4593                         if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) ) && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
4594                                 if (first) first = 0;
4595                                 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4596                                 buffer_fadd( sql, " %s = NULL", field_name );
4597                         }
4598                         
4599                 } else if ( value_is_numeric || !strcmp( get_primitive( field ), "number") ) {
4600                         if (first) first = 0;
4601                         else OSRF_BUFFER_ADD_CHAR(sql, ',');
4602
4603                         const char* numtype = get_datatype( field );
4604                         if ( !strncmp( numtype, "INT", 3 ) ) {
4605                                 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
4606                         } else if ( !strcmp( numtype, "NUMERIC" ) ) {
4607                                 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
4608                         } else {
4609                                 // Must really be intended as a string, so quote it
4610                                 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4611                                         buffer_fadd( sql, " %s = %s", field_name, value );
4612                                 } else {
4613                                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4614                                         osrfAppSessionStatus(
4615                                                 ctx->session,
4616                                                 OSRF_STATUS_INTERNALSERVERERROR,
4617                                                 "osrfMethodException",
4618                                                 ctx->request,
4619                                                 "Error quoting string -- please see the error log for more details"
4620                                         );
4621                                         free(value);
4622                                         free(id);
4623                                         buffer_free(sql);
4624                                         *err = -1;
4625                                         return jsonNULL;
4626                                 }
4627                         }
4628
4629                         osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
4630
4631                 } else {
4632                         if ( dbi_conn_quote_string(dbhandle, &value) ) {
4633                                 if (first) first = 0;
4634                                 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4635                                 buffer_fadd( sql, " %s = %s", field_name, value );
4636
4637                         } else {
4638                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4639                                 osrfAppSessionStatus(
4640                                         ctx->session,
4641                                         OSRF_STATUS_INTERNALSERVERERROR,
4642                                         "osrfMethodException",
4643                                         ctx->request,
4644                                         "Error quoting string -- please see the error log for more details"
4645                                 );
4646                                 free(value);
4647                                 free(id);
4648                                 buffer_free(sql);
4649                                 *err = -1;
4650                                 return jsonNULL;
4651                         }
4652                 }
4653
4654                 free(value);
4655                 
4656         }
4657
4658         jsonObject* obj = jsonNewObject(id);
4659
4660         if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4661                 dbi_conn_quote_string(dbhandle, &id);
4662
4663         buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
4664
4665         char* query = buffer_release(sql);
4666         osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
4667
4668         dbi_result result = dbi_conn_query(dbhandle, query);
4669         free(query);
4670
4671         if (!result) {
4672                 jsonObjectFree(obj);
4673                 obj = jsonNewObject(NULL);
4674                 osrfLogError(
4675                         OSRF_LOG_MARK,
4676                         "%s ERROR updating %s object with %s = %s",
4677                         MODULENAME,
4678                         osrfHashGet(meta, "fieldmapper"),
4679                         pkey,
4680                         id
4681                 );
4682         }
4683
4684         free(id);
4685
4686         return obj;
4687 }
4688
4689 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4690
4691         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4692
4693         if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4694                 osrfAppSessionStatus(
4695                         ctx->session,
4696                         OSRF_STATUS_BADREQUEST,
4697                         "osrfMethodException",
4698                         ctx->request,
4699                         "No active transaction -- required for DELETE"
4700                 );
4701                 *err = -1;
4702                 return jsonNULL;
4703         }
4704
4705         // The following test is harmless but redundant.  If a class is
4706         // readonly, we don't register a delete method for it.
4707         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4708                 osrfAppSessionStatus(
4709                         ctx->session,
4710                         OSRF_STATUS_BADREQUEST,
4711                         "osrfMethodException",
4712                         ctx->request,
4713                         "Cannot DELETE readonly class"
4714                 );
4715                 *err = -1;
4716                 return jsonNULL;
4717         }
4718
4719         dbhandle = writehandle;
4720
4721         jsonObject* obj;
4722
4723         char* pkey = osrfHashGet(meta, "primarykey");
4724
4725         int _obj_pos = 0;
4726 #ifdef PCRUD
4727                 _obj_pos = 1;
4728 #endif
4729
4730         char* id;
4731         if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4732                 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4733                         *err = -1;
4734                         return jsonNULL;
4735                 }
4736
4737                 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4738         } else {
4739 #ifdef PCRUD
4740         if (!verifyObjectPCRUD( ctx, NULL )) {
4741                         *err = -1;
4742                         return jsonNULL;
4743         }
4744 #endif
4745                 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4746         }
4747
4748         osrfLogDebug(
4749                 OSRF_LOG_MARK,
4750                 "%s deleting %s object with %s = %s",
4751                 MODULENAME,
4752                 osrfHashGet(meta, "fieldmapper"),
4753                 pkey,
4754                 id
4755         );
4756
4757         obj = jsonNewObject(id);
4758
4759         if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4760                 dbi_conn_quote_string(writehandle, &id);
4761
4762         dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4763
4764         if (!result) {
4765                 jsonObjectFree(obj);
4766                 obj = jsonNewObject(NULL);
4767                 osrfLogError(
4768                         OSRF_LOG_MARK,
4769                         "%s ERROR deleting %s object with %s = %s",
4770                         MODULENAME,
4771                         osrfHashGet(meta, "fieldmapper"),
4772                         pkey,
4773                         id
4774                 );
4775         }
4776
4777         free(id);
4778
4779         return obj;
4780
4781 }
4782
4783
4784 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4785         if(!(result && meta)) return jsonNULL;
4786
4787         jsonObject* object = jsonNewObject(NULL);
4788         jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4789
4790         osrfHash* fields = osrfHashGet(meta, "fields");
4791
4792         osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4793
4794         osrfHash* _f;
4795         time_t _tmp_dt;
4796         char dt_string[256];
4797         struct tm gmdt;
4798
4799         int fmIndex;
4800         int columnIndex = 1;
4801         int attr;
4802         unsigned short type;
4803         const char* columnName;
4804
4805         /* cycle through the column list */
4806         while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
4807
4808                 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4809
4810                 fmIndex = -1; // reset the position
4811                 
4812                 /* determine the field type and storage attributes */
4813                 type = dbi_result_get_field_type_idx(result, columnIndex);
4814                 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
4815
4816                 /* fetch the fieldmapper index */
4817                 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4818                         
4819                         if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4820                                 continue;
4821                         
4822                         const char* pos = (char*)osrfHashGet(_f, "array_position");
4823                         if ( !pos ) continue;
4824
4825                         fmIndex = atoi( pos );
4826                         osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
4827                 } else {
4828                         continue;
4829                 }
4830
4831                 if (dbi_result_field_is_null_idx(result, columnIndex)) {
4832                         jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
4833                 } else {
4834
4835                         switch( type ) {
4836
4837                                 case DBI_TYPE_INTEGER :
4838
4839                                         if( attr & DBI_INTEGER_SIZE8 ) 
4840                                                 jsonObjectSetIndex( object, fmIndex, 
4841                                                         jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
4842                                         else 
4843                                                 jsonObjectSetIndex( object, fmIndex, 
4844                                                         jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
4845
4846                                         break;
4847
4848                                 case DBI_TYPE_DECIMAL :
4849                                         jsonObjectSetIndex( object, fmIndex, 
4850                                                         jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
4851                                         break;
4852
4853                                 case DBI_TYPE_STRING :
4854
4855
4856                                         jsonObjectSetIndex(
4857                                                 object,
4858                                                 fmIndex,
4859                                                 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
4860                                         );
4861
4862                                         break;
4863
4864                                 case DBI_TYPE_DATETIME :
4865
4866                                         memset(dt_string, '\0', sizeof(dt_string));
4867                                         memset(&gmdt, '\0', sizeof(gmdt));
4868
4869                                         _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
4870
4871
4872                                         if (!(attr & DBI_DATETIME_DATE)) {
4873                                                 gmtime_r( &_tmp_dt, &gmdt );
4874                                                 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4875                                         } else if (!(attr & DBI_DATETIME_TIME)) {
4876                                                 localtime_r( &_tmp_dt, &gmdt );
4877                                                 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4878                                         } else {
4879                                                 localtime_r( &_tmp_dt, &gmdt );
4880                                                 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4881                                         }
4882
4883                                         jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
4884
4885                                         break;
4886
4887                                 case DBI_TYPE_BINARY :
4888                                         osrfLogError( OSRF_LOG_MARK, 
4889                                                 "Can't do binary at column %s : index %d", columnName, columnIndex);
4890                         }
4891                 }
4892                 ++columnIndex;
4893         }
4894
4895         return object;
4896 }
4897
4898 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
4899         if(!result) return jsonNULL;
4900
4901         jsonObject* object = jsonNewObject(NULL);
4902
4903         time_t _tmp_dt;
4904         char dt_string[256];
4905         struct tm gmdt;
4906
4907         int fmIndex;
4908         int columnIndex = 1;
4909         int attr;
4910         unsigned short type;
4911         const char* columnName;
4912
4913         /* cycle through the column list */
4914         while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
4915
4916                 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4917
4918                 fmIndex = -1; // reset the position
4919                 
4920                 /* determine the field type and storage attributes */
4921                 type = dbi_result_get_field_type_idx(result, columnIndex);
4922                 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
4923
4924                 if (dbi_result_field_is_null_idx(result, columnIndex)) {
4925                         jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
4926                 } else {
4927
4928                         switch( type ) {
4929
4930                                 case DBI_TYPE_INTEGER :
4931
4932                                         if( attr & DBI_INTEGER_SIZE8 ) 
4933                                                 jsonObjectSetKey( object, columnName,
4934                                                                 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)) );
4935                                         else 
4936                                                 jsonObjectSetKey( object, columnName,
4937                                                                 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
4938                                         break;
4939
4940                                 case DBI_TYPE_DECIMAL :
4941                                         jsonObjectSetKey( object, columnName,
4942                                                         jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
4943                                         break;
4944
4945                                 case DBI_TYPE_STRING :
4946                                         jsonObjectSetKey( object, columnName,
4947                                                         jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
4948                                         break;
4949
4950                                 case DBI_TYPE_DATETIME :
4951
4952                                         memset(dt_string, '\0', sizeof(dt_string));
4953                                         memset(&gmdt, '\0', sizeof(gmdt));
4954
4955                                         _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
4956
4957
4958                                         if (!(attr & DBI_DATETIME_DATE)) {
4959                                                 gmtime_r( &_tmp_dt, &gmdt );
4960                                                 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4961                                         } else if (!(attr & DBI_DATETIME_TIME)) {
4962                                                 localtime_r( &_tmp_dt, &gmdt );
4963                                                 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4964                                         } else {
4965                                                 localtime_r( &_tmp_dt, &gmdt );
4966                                                 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4967                                         }
4968
4969                                         jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
4970                                         break;
4971
4972                                 case DBI_TYPE_BINARY :
4973                                         osrfLogError( OSRF_LOG_MARK, 
4974                                                 "Can't do binary at column %s : index %d", columnName, columnIndex );
4975                         }
4976                 }
4977                 ++columnIndex;
4978         } // end while loop traversing result
4979
4980         return object;
4981 }
4982
4983 // Interpret a string as true or false
4984 static int str_is_true( const char* str ) {
4985         if( NULL == str || strcasecmp( str, "true" ) )
4986                 return 0;
4987         else
4988                 return 1;
4989 }
4990
4991 // Interpret a jsonObject as true or false
4992 static int obj_is_true( const jsonObject* obj ) {
4993         if( !obj )
4994                 return 0;
4995         else switch( obj->type )
4996         {
4997                 case JSON_BOOL :
4998                         if( obj->value.b )
4999                                 return 1;
5000                         else
5001                                 return 0;
5002                 case JSON_STRING :
5003                         if( strcasecmp( obj->value.s, "true" ) )
5004                                 return 0;
5005                         else
5006                                 return 1;
5007                 case JSON_NUMBER :          // Support 1/0 for perl's sake
5008                         if( jsonObjectGetNumber( obj ) == 1.0 )
5009                                 return 1;
5010                         else
5011                                 return 0;
5012                 default :
5013                         return 0;
5014         }
5015 }
5016
5017 // Translate a numeric code into a text string identifying a type of
5018 // jsonObject.  To be used for building error messages.
5019 static const char* json_type( int code ) {
5020         switch ( code )
5021         {
5022                 case 0 :
5023                         return "JSON_HASH";
5024                 case 1 :
5025                         return "JSON_ARRAY";
5026                 case 2 :
5027                         return "JSON_STRING";
5028                 case 3 :
5029                         return "JSON_NUMBER";
5030                 case 4 :
5031                         return "JSON_NULL";
5032                 case 5 :
5033                         return "JSON_BOOL";
5034                 default :
5035                         return "(unrecognized)";
5036         }
5037 }
5038
5039 // Extract the "primitive" attribute from an IDL field definition.
5040 // If we haven't initialized the app, then we must be running in
5041 // some kind of testbed.  In that case, default to "string".
5042 static const char* get_primitive( osrfHash* field ) {
5043         const char* s = osrfHashGet( field, "primitive" );
5044         if( !s ) {
5045                 if( child_initialized )
5046                         osrfLogError(
5047                                 OSRF_LOG_MARK,
5048                                 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5049                                 MODULENAME,
5050                                 osrfHashGet( field, "name" )
5051                         );
5052                 else
5053                         s = "string";
5054         }
5055         return s;
5056 }
5057
5058 // Extract the "datatype" attribute from an IDL field definition.
5059 // If we haven't initialized the app, then we must be running in
5060 // some kind of testbed.  In that case, default to to NUMERIC,
5061 // since we look at the datatype only for numbers.
5062 static const char* get_datatype( osrfHash* field ) {
5063         const char* s = osrfHashGet( field, "datatype" );
5064         if( !s ) {
5065                 if( child_initialized )
5066                         osrfLogError(
5067                                 OSRF_LOG_MARK,
5068                                 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5069                                 MODULENAME,
5070                                 osrfHashGet( field, "name" )
5071                         );
5072                 else
5073                         s = "NUMERIC";
5074         }
5075         return s;
5076 }
5077
5078 /*
5079 If the input string is potentially a valid SQL identifier, return 1.
5080 Otherwise return 0.
5081
5082 Purpose: to prevent certain kinds of SQL injection.  To that end we
5083 don't necessarily need to follow all the rules exactly, such as requiring
5084 that the first character not be a digit.
5085
5086 We allow leading and trailing white space.  In between, we do not allow
5087 punctuation (except for underscores and dollar signs), control 
5088 characters, or embedded white space.
5089
5090 More pedantically we should allow quoted identifiers containing arbitrary
5091 characters, but for the foreseeable future such quoted identifiers are not
5092 likely to be an issue.
5093 */
5094 static int is_identifier( const char* s) {
5095         if( !s )
5096                 return 0;
5097
5098         // Skip leading white space
5099         while( isspace( (unsigned char) *s ) )
5100                 ++s;
5101
5102         if( !s )
5103                 return 0;   // Nothing but white space?  Not okay.
5104
5105         // Check each character until we reach white space or
5106         // end-of-string.  Letters, digits, underscores, and 
5107         // dollar signs are okay. With the exception of periods
5108         // (as in schema.identifier), control characters and other
5109         // punctuation characters are not okay.  Anything else
5110         // is okay -- it could for example be part of a multibyte
5111         // UTF8 character such as a letter with diacritical marks,
5112         // and those are allowed.
5113         do {
5114                 if( isalnum( (unsigned char) *s )
5115                         || '.' == *s
5116                         || '_' == *s
5117                         || '$' == *s )
5118                         ;  // Fine; keep going
5119                 else if(   ispunct( (unsigned char) *s )
5120                                 || iscntrl( (unsigned char) *s ) )
5121                         return 0;
5122                         ++s;
5123         } while( *s && ! isspace( (unsigned char) *s ) );
5124
5125         // If we found any white space in the above loop,
5126         // the rest had better be all white space.
5127         
5128         while( isspace( (unsigned char) *s ) )
5129                 ++s;
5130
5131         if( *s )
5132                 return 0;   // White space was embedded within non-white space
5133
5134         return 1;
5135 }
5136
5137 /*
5138 Determine whether to accept a character string as a comparison operator.
5139 Return 1 if it's good, or 0 if it's bad.
5140
5141 We don't validate it for real.  We just make sure that it doesn't contain
5142 any semicolons or white space (with a special exception for the
5143 "SIMILAR TO" operator).  The idea is to block certain kinds of SQL
5144 injection.  If it has no semicolons or white space but it's still not a
5145 valid operator, then the database will complain.
5146
5147 Another approach would be to compare the string against a short list of
5148 approved operators.  We don't do that because we want to allow custom
5149 operators like ">100*", which would be difficult or impossible to
5150 express otherwise in a JSON query.
5151 */
5152 static int is_good_operator( const char* op ) {
5153         if( !op ) return 0;   // Sanity check
5154
5155         const char* s = op;
5156         while( *s ) {
5157                 if( isspace( (unsigned char) *s ) ) {
5158                         // Special exception for SIMILAR TO.  Someday we might make
5159                         // exceptions for IS DISTINCT FROM and IS NOT DISTINCT FROM.
5160                         if( !strcasecmp( op, "similar to" ) )
5161                                 return 1;
5162                         else
5163                                 return 0;
5164                 }
5165                 else if( ';' == *s )
5166                         return 0;
5167                 ++s;
5168         }
5169         return 1;
5170 }