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