]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/c-apps/oils_cstore.c
In searchWHERE(): plugged a security hole that invited SQL injection.
[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         while ( (node = jsonIteratorNext( search_itr )) ) {
2301             if (first) {
2302                 first = 0;
2303             } else {
2304                 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2305                 else buffer_add(sql_buf, " AND ");
2306             }
2307
2308             char* subpred = searchWHERE( node, meta, opjoin_type, ctx );
2309             buffer_fadd(sql_buf, "( %s )", subpred);
2310             free(subpred);
2311         }
2312         jsonIteratorFree(search_itr);
2313
2314     } else if ( search_hash->type == JSON_HASH ) {
2315             osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2316         jsonIterator* search_itr = jsonNewIterator( search_hash );
2317         while ( (node = jsonIteratorNext( search_itr )) ) {
2318
2319             if (first) {
2320                 first = 0;
2321             } else {
2322                 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2323                 else buffer_add(sql_buf, " AND ");
2324             }
2325
2326                         if ( '+' == search_itr->key[ 0 ] ) {
2327                                 if ( node->type == JSON_STRING ) {
2328                                         // Intended purpose; to allow reference to a Boolean column.
2329                                         // Verify that the string looks like an identifier.
2330                                         const char* subpred = jsonObjectGetString( node );
2331                                         if( ! is_identifier( subpred ) ) {
2332                                                 osrfLogError(
2333                                                          OSRF_LOG_MARK,
2334                                                         "%s: Invalid boolean identifier in WHERE clause: \"%s\"",
2335                                                         MODULENAME,
2336                                                         subpred
2337                                                 );
2338                                                 jsonIteratorFree( search_itr );
2339                                                 buffer_free( sql_buf );
2340                                                 return NULL;
2341                                         }
2342                                         buffer_fadd(sql_buf, " \"%s\".%s ", search_itr->key + 1, subpred);
2343                 } else {
2344                     char* subpred = searchWHERE( node, osrfHashGet( oilsIDL(), search_itr->key + 1 ), AND_OP_JOIN, ctx );
2345                     buffer_fadd(sql_buf, "( %s )", subpred);
2346                     free(subpred);
2347                 }
2348             } else if ( !strcasecmp("-or",search_itr->key) ) {
2349                 char* subpred = searchWHERE( node, meta, OR_OP_JOIN, ctx );
2350                 buffer_fadd(sql_buf, "( %s )", subpred);
2351                 free(subpred);
2352             } else if ( !strcasecmp("-and",search_itr->key) ) {
2353                 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2354                 buffer_fadd(sql_buf, "( %s )", subpred);
2355                 free(subpred);
2356             } else if ( !strcasecmp("-exists",search_itr->key) ) {
2357                 char* subpred = SELECT(
2358                     ctx,
2359                     jsonObjectGetKey( node, "select" ),
2360                     jsonObjectGetKey( node, "from" ),
2361                     jsonObjectGetKey( node, "where" ),
2362                     jsonObjectGetKey( node, "having" ),
2363                     jsonObjectGetKey( node, "order_by" ),
2364                     jsonObjectGetKey( node, "limit" ),
2365                     jsonObjectGetKey( node, "offset" ),
2366                     SUBSELECT
2367                 );
2368
2369                 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2370                 free(subpred);
2371             } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2372                 char* subpred = SELECT(
2373                     ctx,
2374                     jsonObjectGetKey( node, "select" ),
2375                     jsonObjectGetKey( node, "from" ),
2376                     jsonObjectGetKey( node, "where" ),
2377                     jsonObjectGetKey( node, "having" ),
2378                     jsonObjectGetKey( node, "order_by" ),
2379                     jsonObjectGetKey( node, "limit" ),
2380                     jsonObjectGetKey( node, "offset" ),
2381                     SUBSELECT
2382                 );
2383
2384                 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2385                 free(subpred);
2386             } else {
2387
2388                 char* class = osrfHashGet(meta, "classname");
2389                 osrfHash* fields = osrfHashGet(meta, "fields");
2390                 osrfHash* field = osrfHashGet( fields, search_itr->key );
2391
2392
2393                 if (!field) {
2394                     char* table = getSourceDefinition(meta);
2395                                         if( !table )
2396                                                 table = strdup( "(?)" );
2397                     osrfLogError(
2398                         OSRF_LOG_MARK,
2399                         "%s: Attempt to reference non-existent column %s on %s (%s)",
2400                         MODULENAME,
2401                         search_itr->key,
2402                         table,
2403                         class
2404                     );
2405                     buffer_free(sql_buf);
2406                     free(table);
2407                                         jsonIteratorFree(search_itr);
2408                                         return NULL;
2409                 }
2410
2411                 char* subpred = searchPredicate( class, field, node, ctx );
2412                 buffer_add( sql_buf, subpred );
2413                 free(subpred);
2414             }
2415         }
2416             jsonIteratorFree(search_itr);
2417
2418     } else {
2419         // ERROR ... only hash and array allowed at this level
2420         char* predicate_string = jsonObjectToJSON( search_hash );
2421         osrfLogError(
2422             OSRF_LOG_MARK,
2423             "%s: Invalid predicate structure: %s",
2424             MODULENAME,
2425             predicate_string
2426         );
2427         buffer_free(sql_buf);
2428         free(predicate_string);
2429         return NULL;
2430     }
2431
2432
2433         return buffer_release(sql_buf);
2434 }
2435
2436 char* SELECT (
2437                 /* method context */ osrfMethodContext* ctx,
2438                 
2439                 /* SELECT   */ jsonObject* selhash,
2440                 /* FROM     */ jsonObject* join_hash,
2441                 /* WHERE    */ jsonObject* search_hash,
2442                 /* HAVING   */ jsonObject* having_hash,
2443                 /* ORDER BY */ jsonObject* order_hash,
2444                 /* LIMIT    */ jsonObject* limit,
2445                 /* OFFSET   */ jsonObject* offset,
2446                 /* flags    */ int flags
2447 ) {
2448         const char* locale = osrf_message_get_last_locale();
2449
2450         // in case we don't get a select list
2451         jsonObject* defaultselhash = NULL;
2452
2453         // general tmp objects
2454         const jsonObject* tmp_const;
2455         jsonObject* selclass = NULL;
2456         jsonObject* selfield = NULL;
2457         jsonObject* snode = NULL;
2458         jsonObject* onode = NULL;
2459
2460         char* string = NULL;
2461         int from_function = 0;
2462         int first = 1;
2463         int gfirst = 1;
2464         //int hfirst = 1;
2465
2466         // the core search class
2467         char* core_class = NULL;
2468
2469         // metadata about the core search class
2470         osrfHash* core_meta = NULL;
2471
2472         // punt if there's no core class
2473         if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2474                 osrfLogError(
2475                         OSRF_LOG_MARK,
2476                         "%s: FROM clause is missing or empty",
2477                         MODULENAME
2478                 );
2479                 if( ctx )
2480                         osrfAppSessionStatus(
2481                                 ctx->session,
2482                                 OSRF_STATUS_INTERNALSERVERERROR,
2483                                 "osrfMethodException",
2484                                 ctx->request,
2485                                 "FROM clause is missing or empty in JSON query"
2486                         );
2487                 return NULL;
2488         }
2489
2490         // get the core class -- the only key of the top level FROM clause, or a string
2491         if (join_hash->type == JSON_HASH) {
2492                 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2493                 snode = jsonIteratorNext( tmp_itr );
2494                 
2495                 core_class = strdup( tmp_itr->key );
2496                 join_hash = snode;
2497                 
2498                 jsonObject* extra = jsonIteratorNext( tmp_itr );
2499
2500                 jsonIteratorFree( tmp_itr );
2501                 snode = NULL;
2502
2503                 // There shouldn't be more than one entry in join_hash
2504                 if( extra ) {
2505                         osrfLogError(
2506                                 OSRF_LOG_MARK,
2507                                 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2508                                 MODULENAME
2509                         );
2510                         if( ctx )
2511                                 osrfAppSessionStatus(
2512                                         ctx->session,
2513                                         OSRF_STATUS_INTERNALSERVERERROR,
2514                                         "osrfMethodException",
2515                                         ctx->request,
2516                                         "Malformed FROM clause in JSON query"
2517                                 );
2518                         free( core_class );
2519                         return NULL;    // Malformed join_hash; extra entry
2520                 }
2521         } else if (join_hash->type == JSON_ARRAY) {
2522                 from_function = 1;
2523                 core_class = jsonObjectToSimpleString( jsonObjectGetIndex(join_hash, 0) );
2524                 selhash = NULL;
2525
2526         } else if (join_hash->type == JSON_STRING) {
2527                 core_class = jsonObjectToSimpleString( join_hash );
2528                 join_hash = NULL;
2529         }
2530         else {
2531                 osrfLogError(
2532                         OSRF_LOG_MARK,
2533                         "%s: FROM clause is unexpected JSON type: %s",
2534                         MODULENAME,
2535                         json_type( join_hash->type )
2536                 );
2537                 if( ctx )
2538                         osrfAppSessionStatus(
2539                                 ctx->session,
2540                                 OSRF_STATUS_INTERNALSERVERERROR,
2541                                 "osrfMethodException",
2542                                 ctx->request,
2543                                 "Ill-formed FROM clause in JSON query"
2544                         );
2545                 free( core_class );
2546                 return NULL;
2547         }
2548
2549         if (!from_function) {
2550                 // Get the IDL class definition for the core class
2551                 core_meta = osrfHashGet( oilsIDL(), core_class );
2552                 if( !core_meta ) {    // Didn't find it?
2553                         osrfLogError(
2554                                 OSRF_LOG_MARK,
2555                                 "%s: SELECT clause references undefined class: \"%s\"",
2556                                 MODULENAME,
2557                                 core_class
2558                         );
2559                         if( ctx )
2560                                 osrfAppSessionStatus(
2561                                         ctx->session,
2562                                         OSRF_STATUS_INTERNALSERVERERROR,
2563                                         "osrfMethodException",
2564                                         ctx->request,
2565                                         "SELECT clause references undefined class in JSON query"
2566                                 );
2567                         free( core_class );
2568                         return NULL;
2569                 }
2570
2571                 // Make sure the class isn't virtual
2572                 if( str_is_true( osrfHashGet( core_meta, "virtual" ) ) ) {
2573                         osrfLogError(
2574                                 OSRF_LOG_MARK,
2575                                 "%s: Core class is virtual: \"%s\"",
2576                                 MODULENAME,
2577                                 core_class
2578                         );
2579                         if( ctx )
2580                                 osrfAppSessionStatus(
2581                                         ctx->session,
2582                                         OSRF_STATUS_INTERNALSERVERERROR,
2583                                         "osrfMethodException",
2584                                         ctx->request,
2585                                         "FROM clause references virtual class in JSON query"
2586                                 );
2587                         free( core_class );
2588                         return NULL;
2589                 }
2590         }
2591
2592         // if the select list is empty, or the core class field list is '*',
2593         // build the default select list ...
2594         if (!selhash) {
2595                 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
2596                 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2597         } else if( selhash->type != JSON_HASH ) {
2598                 osrfLogError(
2599                         OSRF_LOG_MARK,
2600                         "%s: Expected JSON_HASH for SELECT clause; found %s",
2601                         MODULENAME,
2602                         json_type( selhash->type )
2603                 );
2604
2605                 if (ctx)
2606                         osrfAppSessionStatus(
2607                                 ctx->session,
2608                                 OSRF_STATUS_INTERNALSERVERERROR,
2609                                 "osrfMethodException",
2610                                 ctx->request,
2611                                 "Malformed SELECT clause in JSON query"
2612                         );
2613                 free( core_class );
2614                 return NULL;
2615         } else if ( (tmp_const = jsonObjectGetKeyConst( selhash, core_class )) && tmp_const->type == JSON_STRING ) {
2616                 char* _x = jsonObjectToSimpleString( tmp_const );
2617                 if (!strncmp( "*", _x, 1 )) {
2618                         jsonObjectRemoveKey( selhash, core_class );
2619                         jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2620                 }
2621                 free(_x);
2622         }
2623
2624         // the query buffer
2625         growing_buffer* sql_buf = buffer_init(128);
2626
2627         // temp buffer for the SELECT list
2628         growing_buffer* select_buf = buffer_init(128);
2629         growing_buffer* order_buf = buffer_init(128);
2630         growing_buffer* group_buf = buffer_init(128);
2631         growing_buffer* having_buf = buffer_init(128);
2632
2633         int aggregate_found = 0;     // boolean
2634
2635         // Build a select list
2636         if(from_function)   // From a function we select everything
2637                 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
2638         else {
2639
2640                 // If we need to build a default list, prepare to do so
2641                 jsonObject* _tmp = jsonObjectGetKey( selhash, core_class );
2642                 if ( _tmp && !_tmp->size ) {
2643
2644                         osrfHash* core_fields = osrfHashGet( core_meta, "fields" );
2645
2646                         osrfHashIterator* field_itr = osrfNewHashIterator( core_fields );
2647                         osrfHash* field_def;
2648                         while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2649                                 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2650                                         // This field is not virtual, so add it to the list
2651                                         jsonObjectPush( _tmp, jsonNewObject( osrfHashIteratorKey( field_itr ) ) );
2652                                 }
2653                         }
2654                         osrfHashIteratorFree( field_itr );
2655                 }
2656
2657                 // Now build the actual select list
2658             int sel_pos = 1;
2659             first = 1;
2660             gfirst = 1;
2661             jsonIterator* selclass_itr = jsonNewIterator( selhash );
2662             while ( (selclass = jsonIteratorNext( selclass_itr )) ) {    // For each class
2663
2664                     // Make sure the class is defined in the IDL
2665                         const char* cname = selclass_itr->key;
2666                         osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
2667                     if (!idlClass) {
2668                                 osrfLogError(
2669                                         OSRF_LOG_MARK,
2670                                         "%s: Selected class \"%s\" not defined in IDL",
2671                                         MODULENAME,
2672                                         cname
2673                                 );
2674
2675                                 if (ctx)
2676                                         osrfAppSessionStatus(
2677                                                 ctx->session,
2678                                                 OSRF_STATUS_INTERNALSERVERERROR,
2679                                                 "osrfMethodException",
2680                                                 ctx->request,
2681                                                 "Selected class is not defined"
2682                                         );
2683                                 jsonIteratorFree( selclass_itr );
2684                                 buffer_free( sql_buf );
2685                                 buffer_free( select_buf );
2686                                 buffer_free( order_buf );
2687                                 buffer_free( group_buf );
2688                                 buffer_free( having_buf );
2689                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
2690                                 free( core_class );
2691                                 return NULL;
2692                         }
2693
2694                     // Make sure the target relation is in the join tree.
2695                         
2696                         // At this point join_hash is a step down from the join_hash we
2697                         // received as a parameter.  If the original was a JSON_STRING,
2698                         // then json_hash is now NULL.  If the original was a JSON_HASH,
2699                         // then json_hash is now the first (and only) entry in it,
2700                         // denoting the core class.  We've already excluded the
2701                         // possibility that the original was a JSON_ARRAY, because in
2702                         // that case from_function would be non-NULL, and we wouldn't
2703                         // be here.
2704
2705                         int class_in_from_clause;    // boolean
2706                         
2707                     if ( ! strcmp( core_class, cname ))
2708                                 // This is the core class -- no problem
2709                                 class_in_from_clause = 1;
2710                         else {
2711                                 if (!join_hash) 
2712                                         // There's only one class in the FROM clause, and this isn't it
2713                                         class_in_from_clause = 0;
2714                                 else if (join_hash->type == JSON_STRING) {
2715                                         // There's only one class in the FROM clause
2716                                         string = jsonObjectToSimpleString(join_hash);
2717                                         if ( strcmp( string, cname ) )
2718                                                 class_in_from_clause = 0;    // This isn't it
2719                                         else 
2720                                                 class_in_from_clause = 1;    // This is it
2721                                         free( string );
2722                                 } else {
2723                                         jsonObject* found = jsonObjectFindPath(join_hash, "//%s", cname);
2724                                         if ( 0 == found->size )
2725                                                 class_in_from_clause = 0;   // Nowhere in the join tree
2726                                         else
2727                                                 class_in_from_clause = 1;   // Found it
2728                                         jsonObjectFree( found );
2729                                 }
2730                         }
2731
2732                         // If the class isn't in the FROM clause, bail out
2733                         if( ! class_in_from_clause ) {
2734                                 osrfLogError(
2735                                         OSRF_LOG_MARK,
2736                                         "%s: SELECT clause references class not in FROM clause: \"%s\"",
2737                                         MODULENAME,
2738                                         cname
2739                                 );
2740                                 if( ctx )
2741                                         osrfAppSessionStatus(
2742                                                 ctx->session,
2743                                                 OSRF_STATUS_INTERNALSERVERERROR,
2744                                                 "osrfMethodException",
2745                                                 ctx->request,
2746                                                 "Selected class not in FROM clause in JSON query"
2747                                         );
2748                                 jsonIteratorFree( selclass_itr );
2749                                 buffer_free( sql_buf );
2750                                 buffer_free( select_buf );
2751                                 buffer_free( order_buf );
2752                                 buffer_free( group_buf );
2753                                 buffer_free( having_buf );
2754                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
2755                                 free( core_class );
2756                                 return NULL;
2757                         }
2758
2759                         // Look up some attributes of the current class, so that we 
2760                         // don't have to look them up again for each field
2761                         osrfHash* class_field_set = osrfHashGet( idlClass, "fields" );
2762                         const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
2763                         const char* class_tname = osrfHashGet( idlClass, "tablename" );
2764                         
2765                     // stitch together the column list ...
2766                     jsonIterator* select_itr = jsonNewIterator( selclass );
2767                     while ( (selfield = jsonIteratorNext( select_itr )) ) {   // for each SELECT column
2768
2769                                 // If we need a separator comma, add one
2770                                 if (first) {
2771                                         first = 0;
2772                                 } else {
2773                                         OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
2774                                 }
2775
2776                                 // ... if it's a string, just toss it on the pile
2777                                 if (selfield->type == JSON_STRING) {
2778
2779                                         // Look up the field in the IDL
2780                                         const char* col_name = selfield->value.s;
2781                                         osrfHash* field_def = osrfHashGet( class_field_set, col_name );
2782                                         if ( !field_def ) {
2783                                                 // No such field in current class
2784                                                 osrfLogError(
2785                                                         OSRF_LOG_MARK,
2786                                                         "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
2787                                                         MODULENAME,
2788                                                         col_name,
2789                                                         cname
2790                                                 );
2791                                                 if( ctx )
2792                                                         osrfAppSessionStatus(
2793                                                                 ctx->session,
2794                                                                 OSRF_STATUS_INTERNALSERVERERROR,
2795                                                                 "osrfMethodException",
2796                                                                 ctx->request,
2797                                                                 "Selected column not defined in JSON query"
2798                                                         );
2799                                                 jsonIteratorFree( select_itr );
2800                                                 jsonIteratorFree( selclass_itr );
2801                                                 buffer_free( sql_buf );
2802                                                 buffer_free( select_buf );
2803                                                 buffer_free( order_buf );
2804                                                 buffer_free( group_buf );
2805                                                 buffer_free( having_buf );
2806                                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
2807                                                 free( core_class );
2808                                                 return NULL;
2809                                         } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2810                                                 // Virtual field not allowed
2811                                                 osrfLogError(
2812                                                         OSRF_LOG_MARK,
2813                                                         "%s: Selected column \"%s\" for class \"%s\" is virtual",
2814                                                         MODULENAME,
2815                                                         col_name,
2816                                                         cname
2817                                                 );
2818                                                 if( ctx )
2819                                                         osrfAppSessionStatus(
2820                                                                 ctx->session,
2821                                                                 OSRF_STATUS_INTERNALSERVERERROR,
2822                                                                 "osrfMethodException",
2823                                                                 ctx->request,
2824                                                                 "Selected column may not be virtual in JSON query"
2825                                                         );
2826                                                 jsonIteratorFree( select_itr );
2827                                                 jsonIteratorFree( selclass_itr );
2828                                                 buffer_free( sql_buf );
2829                                                 buffer_free( select_buf );
2830                                                 buffer_free( order_buf );
2831                                                 buffer_free( group_buf );
2832                                                 buffer_free( having_buf );
2833                                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
2834                                                 free( core_class );
2835                                                 return NULL;
2836                                         }
2837
2838                                         if (locale) {
2839                                                 const char* i18n;
2840                                                 if (flags & DISABLE_I18N)
2841                                                         i18n = NULL;
2842                                                 else
2843                                                         i18n = osrfHashGet(field_def, "i18n");
2844
2845                                                 if( str_is_true( i18n ) ) {
2846                             buffer_fadd( select_buf,
2847                                                                 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
2848                                                                 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
2849                         } else {
2850                                             buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
2851                         }
2852                     } else {
2853                                         buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
2854                     }
2855                                         
2856                                 // ... but it could be an object, in which case we check for a Field Transform
2857                                 } else if (selfield->type == JSON_HASH) {
2858
2859                                         char* col_name = jsonObjectToSimpleString( jsonObjectGetKeyConst( selfield, "column" ) );
2860
2861                                         // Get the field definition from the IDL
2862                                         osrfHash* field_def = osrfHashGet( class_field_set, col_name );
2863                                         if ( !field_def ) {
2864                                                 // No such field in current class
2865                                                 osrfLogError(
2866                                                         OSRF_LOG_MARK,
2867                                                         "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
2868                                                         MODULENAME,
2869                                                         col_name,
2870                                                         cname
2871                                                 );
2872                                                 if( ctx )
2873                                                         osrfAppSessionStatus(
2874                                                                 ctx->session,
2875                                                                 OSRF_STATUS_INTERNALSERVERERROR,
2876                                                                 "osrfMethodException",
2877                                                                 ctx->request,
2878                                                                 "Selected column is not defined in JSON query"
2879                                                         );
2880                                                 jsonIteratorFree( select_itr );
2881                                                 jsonIteratorFree( selclass_itr );
2882                                                 buffer_free( sql_buf );
2883                                                 buffer_free( select_buf );
2884                                                 buffer_free( order_buf );
2885                                                 buffer_free( group_buf );
2886                                                 buffer_free( having_buf );
2887                                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
2888                                                 free( core_class );
2889                                                 return NULL;
2890                                         } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2891                                                 // No such field in current class
2892                                                 osrfLogError(
2893                                                         OSRF_LOG_MARK,
2894                                                         "%s: Selected column \"%s\" is virtual for class \"%s\"",
2895                                                         MODULENAME,
2896                                                         col_name,
2897                                                         cname
2898                                                 );
2899                                                 if( ctx )
2900                                                         osrfAppSessionStatus(
2901                                                                 ctx->session,
2902                                                                 OSRF_STATUS_INTERNALSERVERERROR,
2903                                                                 "osrfMethodException",
2904                                                                 ctx->request,
2905                                                                 "Selected column is virtual in JSON query"
2906                                                         );
2907                                                 jsonIteratorFree( select_itr );
2908                                                 jsonIteratorFree( selclass_itr );
2909                                                 buffer_free( sql_buf );
2910                                                 buffer_free( select_buf );
2911                                                 buffer_free( order_buf );
2912                                                 buffer_free( group_buf );
2913                                                 buffer_free( having_buf );
2914                                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
2915                                                 free( core_class );
2916                                                 return NULL;
2917                                         }
2918
2919                                         // Decide what to use as a column alias
2920                                         char* _alias;
2921                                         if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
2922                                                 _alias = jsonObjectToSimpleString( tmp_const );
2923                                         } else {         // Use field name as the alias
2924                                                 _alias = col_name;
2925                                         }
2926
2927                                         if (jsonObjectGetKeyConst( selfield, "transform" )) {
2928                                                 char* transform_str = searchFieldTransform(cname, field_def, selfield);
2929                                                 if( transform_str ) {
2930                                                         buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
2931                                                         free(transform_str);
2932                                                 } else {
2933                                                         if( ctx )
2934                                                                 osrfAppSessionStatus(
2935                                                                         ctx->session,
2936                                                                         OSRF_STATUS_INTERNALSERVERERROR,
2937                                                                         "osrfMethodException",
2938                                                                         ctx->request,
2939                                                                         "Unable to generate transform function in JSON query"
2940                                                                 );
2941                                                         jsonIteratorFree( select_itr );
2942                                                         jsonIteratorFree( selclass_itr );
2943                                                         buffer_free( sql_buf );
2944                                                         buffer_free( select_buf );
2945                                                         buffer_free( order_buf );
2946                                                         buffer_free( group_buf );
2947                                                         buffer_free( having_buf );
2948                                                         if( defaultselhash ) jsonObjectFree( defaultselhash );
2949                                                         free( core_class );
2950                                                         return NULL;
2951                                                 }
2952                                         } else {
2953
2954                                                 if (locale) {
2955                                                         const char* i18n;
2956                                                         if (flags & DISABLE_I18N)
2957                                                                 i18n = NULL;
2958                                                         else
2959                                                                 i18n = osrfHashGet(field_def, "i18n");
2960
2961                                                         if( str_is_true( i18n ) ) {
2962                                                                 buffer_fadd( select_buf,
2963                                                                         " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
2964                                                                         class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
2965                             } else {
2966                                                     buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
2967                             }
2968                         } else {
2969                                                 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
2970                         }
2971                                     }
2972
2973                                         if( _alias != col_name )
2974                                             free(_alias);
2975                                         free( col_name );
2976                             }
2977                                 else {
2978                                         osrfLogError(
2979                                                 OSRF_LOG_MARK,
2980                                                 "%s: Selected item is unexpected JSON type: %s",
2981                                                 MODULENAME,
2982                                                 json_type( selfield->type )
2983                                         );
2984                                         if( ctx )
2985                                                 osrfAppSessionStatus(
2986                                                         ctx->session,
2987                                                         OSRF_STATUS_INTERNALSERVERERROR,
2988                                                         "osrfMethodException",
2989                                                         ctx->request,
2990                                                         "Ill-formed SELECT item in JSON query"
2991                                                 );
2992                                         jsonIteratorFree( select_itr );
2993                                         jsonIteratorFree( selclass_itr );
2994                                         buffer_free( sql_buf );
2995                                         buffer_free( select_buf );
2996                                         buffer_free( order_buf );
2997                                         buffer_free( group_buf );
2998                                         buffer_free( having_buf );
2999                                         if( defaultselhash ) jsonObjectFree( defaultselhash );
3000                                         free( core_class );
3001                                         return NULL;
3002                                 }
3003
3004                                 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3005                                 if( obj_is_true( agg_obj ) )
3006                                         aggregate_found = 1;
3007                                 else {
3008                                         // Append a comma (except for the first one)
3009                                         // and add the column to a GROUP BY clause
3010                                         if (gfirst)
3011                                                 gfirst = 0;
3012                                         else
3013                                                 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3014
3015                                         buffer_fadd(group_buf, " %d", sel_pos);
3016                                 }
3017
3018 #if 0
3019                             if (is_agg->size || (flags & SELECT_DISTINCT)) {
3020
3021                                         const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3022                                     if ( ! obj_is_true( aggregate_obj ) ) {
3023                                             if (gfirst) {
3024                                                     gfirst = 0;
3025                                             } else {
3026                                                         OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3027                                             }
3028
3029                                             buffer_fadd(group_buf, " %d", sel_pos);
3030
3031                                         /*
3032                                     } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3033                                             if (gfirst) {
3034                                                     gfirst = 0;
3035                                             } else {
3036                                                         OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3037                                             }
3038
3039                                             _column = searchFieldTransform(cname, field, selfield);
3040                                                 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3041                                                 OSRF_BUFFER_ADD(group_buf, _column);
3042                                             _column = searchFieldTransform(cname, field, selfield);
3043                                         */
3044                                     }
3045                             }
3046 #endif
3047
3048                             sel_pos++;
3049                     } // end while -- iterating across SELECT columns
3050
3051             jsonIteratorFree(select_itr);
3052             } // end while -- iterating across classes
3053
3054         jsonIteratorFree(selclass_itr);
3055     }
3056
3057
3058         char* col_list = buffer_release(select_buf);
3059         char* table = NULL;
3060         if (from_function) table = searchValueTransform(join_hash);
3061         else table = getSourceDefinition(core_meta);
3062         
3063         if( !table ) {
3064                 if (ctx)
3065                         osrfAppSessionStatus(
3066                                 ctx->session,
3067                                 OSRF_STATUS_INTERNALSERVERERROR,
3068                                 "osrfMethodException",
3069                                 ctx->request,
3070                                 "Unable to identify table for core class"
3071                         );
3072                 free( col_list );
3073                 buffer_free( sql_buf );
3074                 buffer_free( order_buf );
3075                 buffer_free( group_buf );
3076                 buffer_free( having_buf );
3077                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3078                 free( core_class );
3079                 return NULL;    
3080         }
3081         
3082         // Put it all together
3083         buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3084         free(col_list);
3085         free(table);
3086
3087     if (!from_function) {
3088             // Now, walk the join tree and add that clause
3089             if ( join_hash ) {
3090                     char* join_clause = searchJOIN( join_hash, core_meta );
3091                         if( join_clause ) {
3092                                 buffer_add(sql_buf, join_clause);
3093                         free(join_clause);
3094                         } else {
3095                                 if (ctx)
3096                                         osrfAppSessionStatus(
3097                                                 ctx->session,
3098                                                 OSRF_STATUS_INTERNALSERVERERROR,
3099                                                 "osrfMethodException",
3100                                                 ctx->request,
3101                                                 "Unable to construct JOIN clause(s)"
3102                                         );
3103                                 buffer_free( sql_buf );
3104                                 buffer_free( order_buf );
3105                                 buffer_free( group_buf );
3106                                 buffer_free( having_buf );
3107                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3108                                 free( core_class );
3109                                 return NULL;
3110                         }
3111             }
3112
3113                 // Build a WHERE clause, if there is one
3114             if ( search_hash ) {
3115                     buffer_add(sql_buf, " WHERE ");
3116
3117                     // and it's on the WHERE clause
3118                     char* pred = searchWHERE( search_hash, core_meta, AND_OP_JOIN, ctx );
3119
3120                     if (pred) {
3121                                 buffer_add(sql_buf, pred);
3122                                 free(pred);
3123                         } else {
3124                                 if (ctx) {
3125                                 osrfAppSessionStatus(
3126                                         ctx->session,
3127                                         OSRF_STATUS_INTERNALSERVERERROR,
3128                                         "osrfMethodException",
3129                                         ctx->request,
3130                                         "Severe query error in WHERE predicate -- see error log for more details"
3131                                 );
3132                             }
3133                             free(core_class);
3134                             buffer_free(having_buf);
3135                             buffer_free(group_buf);
3136                             buffer_free(order_buf);
3137                             buffer_free(sql_buf);
3138                             if (defaultselhash) jsonObjectFree(defaultselhash);
3139                             return NULL;
3140                     }
3141         }
3142
3143                 // Build a HAVING clause, if there is one
3144             if ( having_hash ) {
3145                     buffer_add(sql_buf, " HAVING ");
3146
3147                     // and it's on the the WHERE clause
3148                     char* pred = searchWHERE( having_hash, core_meta, AND_OP_JOIN, ctx );
3149
3150                     if (pred) {
3151                                 buffer_add(sql_buf, pred);
3152                                 free(pred);
3153                         } else {
3154                                 if (ctx) {
3155                                 osrfAppSessionStatus(
3156                                         ctx->session,
3157                                         OSRF_STATUS_INTERNALSERVERERROR,
3158                                         "osrfMethodException",
3159                                         ctx->request,
3160                                         "Severe query error in HAVING predicate -- see error log for more details"
3161                                 );
3162                             }
3163                             free(core_class);
3164                             buffer_free(having_buf);
3165                             buffer_free(group_buf);
3166                             buffer_free(order_buf);
3167                             buffer_free(sql_buf);
3168                             if (defaultselhash) jsonObjectFree(defaultselhash);
3169                             return NULL;
3170                     }
3171             }
3172
3173                 // Build an ORDER BY clause, if there is one
3174             first = 1;
3175             jsonIterator* class_itr = jsonNewIterator( order_hash );
3176             while ( (snode = jsonIteratorNext( class_itr )) ) {
3177
3178                     if (!jsonObjectGetKeyConst(selhash,class_itr->key))
3179                             continue;
3180
3181                     if ( snode->type == JSON_HASH ) {
3182
3183                         jsonIterator* order_itr = jsonNewIterator( snode );
3184                             while ( (onode = jsonIteratorNext( order_itr )) ) {
3185
3186                                     if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ))
3187                                             continue;
3188
3189                                     char* direction = NULL;
3190                                     if ( onode->type == JSON_HASH ) {
3191                                             if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3192                                                     string = searchFieldTransform(
3193                                                             class_itr->key,
3194                                                             oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ),
3195                                                             onode
3196                                                     );
3197                                             } else {
3198                                                     growing_buffer* field_buf = buffer_init(16);
3199                                                     buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3200                                                     string = buffer_release(field_buf);
3201                                             }
3202
3203                                             if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3204                                                     direction = jsonObjectToSimpleString(tmp_const);
3205                                                     if (!strncasecmp(direction, "d", 1)) {
3206                                                             free(direction);
3207                                                             direction = " DESC";
3208                                                     } else {
3209                                                             free(direction);
3210                                                             direction = " ASC";
3211                                                     }
3212                                             }
3213
3214                                     } else {
3215                                             string = strdup(order_itr->key);
3216                                             direction = jsonObjectToSimpleString(onode);
3217                                             if (!strncasecmp(direction, "d", 1)) {
3218                                                     free(direction);
3219                                                     direction = " DESC";
3220                                             } else {
3221                                                     free(direction);
3222                                                     direction = " ASC";
3223                                             }
3224                                     }
3225
3226                                     if (first) {
3227                                             first = 0;
3228                                     } else {
3229                                             buffer_add(order_buf, ", ");
3230                                     }
3231
3232                                     buffer_add(order_buf, string);
3233                                     free(string);
3234
3235                                     if (direction) {
3236                                             buffer_add(order_buf, direction);
3237                                     }
3238
3239                             } // end while
3240                 // jsonIteratorFree(order_itr);
3241
3242                     } else if ( snode->type == JSON_ARRAY ) {
3243
3244                         jsonIterator* order_itr = jsonNewIterator( snode );
3245                             while ( (onode = jsonIteratorNext( order_itr )) ) {
3246
3247                                     char* _f = jsonObjectToSimpleString( onode );
3248
3249                                     if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, _f))
3250                                             continue;
3251
3252                                     if (first) {
3253                                             first = 0;
3254                                     } else {
3255                                             buffer_add(order_buf, ", ");
3256                                     }
3257
3258                                     buffer_add(order_buf, _f);
3259                                     free(_f);
3260
3261                             } // end while
3262                 // jsonIteratorFree(order_itr);
3263
3264
3265                     // IT'S THE OOOOOOOOOOOLD STYLE!
3266                     } else {
3267                             osrfLogError(OSRF_LOG_MARK, "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3268                             if (ctx) {
3269                                 osrfAppSessionStatus(
3270                                         ctx->session,
3271                                         OSRF_STATUS_INTERNALSERVERERROR,
3272                                         "osrfMethodException",
3273                                         ctx->request,
3274                                         "Severe query error -- see error log for more details"
3275                                 );
3276                             }
3277
3278                             free(core_class);
3279                             buffer_free(having_buf);
3280                             buffer_free(group_buf);
3281                             buffer_free(order_buf);
3282                             buffer_free(sql_buf);
3283                             if (defaultselhash) jsonObjectFree(defaultselhash);
3284                             jsonIteratorFree(class_itr);
3285                             return NULL;
3286                     }
3287
3288             } // end while
3289                 // jsonIteratorFree(class_itr);
3290         }
3291
3292
3293         string = buffer_release(group_buf);
3294
3295         if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
3296                 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3297                 OSRF_BUFFER_ADD( sql_buf, string );
3298         }
3299
3300         free(string);
3301
3302         string = buffer_release(having_buf);
3303  
3304         if ( *string ) {
3305                 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3306                 OSRF_BUFFER_ADD( sql_buf, string );
3307         }
3308
3309         free(string);
3310
3311         string = buffer_release(order_buf);
3312
3313         if ( *string ) {
3314                 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3315                 OSRF_BUFFER_ADD( sql_buf, string );
3316         }
3317
3318         free(string);
3319
3320         if ( limit ){
3321                 string = jsonObjectToSimpleString(limit);
3322                 buffer_fadd( sql_buf, " LIMIT %d", atoi(string) );
3323                 free(string);
3324         }
3325
3326         if (offset) {
3327                 string = jsonObjectToSimpleString(offset);
3328                 buffer_fadd( sql_buf, " OFFSET %d", atoi(string) );
3329                 free(string);
3330         }
3331
3332         if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3333
3334         free(core_class);
3335         if (defaultselhash) jsonObjectFree(defaultselhash);
3336
3337         return buffer_release(sql_buf);
3338
3339 }
3340
3341 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
3342
3343         const char* locale = osrf_message_get_last_locale();
3344
3345         osrfHash* fields = osrfHashGet(meta, "fields");
3346         char* core_class = osrfHashGet(meta, "classname");
3347
3348         const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
3349
3350         jsonObject* node = NULL;
3351         jsonObject* snode = NULL;
3352         jsonObject* onode = NULL;
3353         const jsonObject* _tmp = NULL;
3354         jsonObject* selhash = NULL;
3355         jsonObject* defaultselhash = NULL;
3356
3357         growing_buffer* sql_buf = buffer_init(128);
3358         growing_buffer* select_buf = buffer_init(128);
3359
3360         if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
3361                 defaultselhash = jsonNewObjectType(JSON_HASH);
3362                 selhash = defaultselhash;
3363         }
3364         
3365         if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
3366                 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
3367                 jsonObject* flist = jsonObjectGetKey( selhash, core_class );
3368                 
3369                 int i = 0;
3370                 char* field;
3371
3372                 osrfStringArray* keys = osrfHashKeys( fields );
3373                 while ( (field = osrfStringArrayGetString(keys, i++)) ) {
3374                         if( ! str_is_true( osrfHashGet( osrfHashGet( fields, field ), "virtual" ) ) )
3375                                 jsonObjectPush( flist, jsonNewObject( field ) );
3376                 }
3377                 osrfStringArrayFree(keys);
3378         }
3379
3380         int first = 1;
3381         jsonIterator* class_itr = jsonNewIterator( selhash );
3382         while ( (snode = jsonIteratorNext( class_itr )) ) {
3383
3384                 char* cname = class_itr->key;
3385                 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
3386                 if (!idlClass) continue;
3387
3388                 if (strcmp(core_class,class_itr->key)) {
3389                         if (!join_hash) continue;
3390
3391                         jsonObject* found =  jsonObjectFindPath(join_hash, "//%s", class_itr->key);
3392                         if (!found->size) {
3393                                 jsonObjectFree(found);
3394                                 continue;
3395                         }
3396
3397                         jsonObjectFree(found);
3398                 }
3399
3400                 jsonIterator* select_itr = jsonNewIterator( snode );
3401                 while ( (node = jsonIteratorNext( select_itr )) ) {
3402                         char* item_str = jsonObjectToSimpleString(node);
3403                         osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
3404                         free(item_str);
3405                         char* fname = osrfHashGet(field, "name");
3406
3407                         if (!field) continue;
3408
3409                         if (first) {
3410                                 first = 0;
3411                         } else {
3412                                 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
3413                         }
3414
3415             if (locale) {
3416                         const char* i18n;
3417                                 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
3418                                 if ( obj_is_true( no_i18n_obj ) )    // Suppress internationalization?
3419                                         i18n = NULL;
3420                                 else
3421                                         i18n = osrfHashGet(field, "i18n");
3422
3423                                 if( str_is_true( i18n ) ) {
3424                         char* pkey = osrfHashGet(idlClass, "primarykey");
3425                         char* tname = osrfHashGet(idlClass, "tablename");
3426
3427                     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);
3428                 } else {
3429                                 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3430                 }
3431             } else {
3432                             buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3433             }
3434                 }
3435
3436         jsonIteratorFree(select_itr);
3437         }
3438
3439     jsonIteratorFree(class_itr);
3440
3441         char* col_list = buffer_release(select_buf);
3442         char* table = getSourceDefinition(meta);
3443         if( !table )
3444                 table = strdup( "(null)" );
3445
3446         buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
3447         free(col_list);
3448         free(table);
3449
3450         if ( join_hash ) {
3451                 char* join_clause = searchJOIN( join_hash, meta );
3452                 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
3453                 OSRF_BUFFER_ADD(sql_buf, join_clause);
3454                 free(join_clause);
3455         }
3456
3457         osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL =  %s",
3458                                  MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
3459
3460         buffer_add(sql_buf, " WHERE ");
3461
3462         char* pred = searchWHERE( search_hash, meta, AND_OP_JOIN, ctx );
3463         if (!pred) {
3464                 osrfAppSessionStatus(
3465                         ctx->session,
3466                         OSRF_STATUS_INTERNALSERVERERROR,
3467                                 "osrfMethodException",
3468                                 ctx->request,
3469                                 "Severe query error -- see error log for more details"
3470                         );
3471                 buffer_free(sql_buf);
3472                 if(defaultselhash) jsonObjectFree(defaultselhash);
3473                 return NULL;
3474         } else {
3475                 buffer_add(sql_buf, pred);
3476                 free(pred);
3477         }
3478
3479         if (order_hash) {
3480                 char* string = NULL;
3481                 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
3482
3483                         growing_buffer* order_buf = buffer_init(128);
3484
3485                         first = 1;
3486                         jsonIterator* class_itr = jsonNewIterator( _tmp );
3487                         while ( (snode = jsonIteratorNext( class_itr )) ) {
3488
3489                                 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
3490                                         continue;
3491
3492                                 if ( snode->type == JSON_HASH ) {
3493
3494                                         jsonIterator* order_itr = jsonNewIterator( snode );
3495                                         while ( (onode = jsonIteratorNext( order_itr )) ) {
3496
3497                                                 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ))
3498                                                         continue;
3499
3500                                                 char* direction = NULL;
3501                                                 if ( onode->type == JSON_HASH ) {
3502                                                         if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3503                                                                 string = searchFieldTransform(
3504                                                                         class_itr->key,
3505                                                                         oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ),
3506                                                                         onode
3507                                                                 );
3508                                                         } else {
3509                                                                 growing_buffer* field_buf = buffer_init(16);
3510                                                                 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3511                                                                 string = buffer_release(field_buf);
3512                                                         }
3513
3514                                                         if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
3515                                                                 direction = jsonObjectToSimpleString(_tmp);
3516                                                                 if (!strncasecmp(direction, "d", 1)) {
3517                                                                         free(direction);
3518                                                                         direction = " DESC";
3519                                                                 } else {
3520                                                                         free(direction);
3521                                                                         direction = " ASC";
3522                                                                 }
3523                                                         }
3524
3525                                                 } else {
3526                                                         string = strdup(order_itr->key);
3527                                                         direction = jsonObjectToSimpleString(onode);
3528                                                         if (!strncasecmp(direction, "d", 1)) {
3529                                                                 free(direction);
3530                                                                 direction = " DESC";
3531                                                         } else {
3532                                                                 free(direction);
3533                                                                 direction = " ASC";
3534                                                         }
3535                                                 }
3536
3537                                                 if (first) {
3538                                                         first = 0;
3539                                                 } else {
3540                                                         buffer_add(order_buf, ", ");
3541                                                 }
3542
3543                                                 buffer_add(order_buf, string);
3544                                                 free(string);
3545
3546                                                 if (direction) {
3547                                                         buffer_add(order_buf, direction);
3548                                                 }
3549
3550                                         }
3551
3552                     jsonIteratorFree(order_itr);
3553
3554                                 } else {
3555                                         string = jsonObjectToSimpleString(snode);
3556                                         buffer_add(order_buf, string);
3557                                         free(string);
3558                                         break;
3559                                 }
3560
3561                         }
3562
3563             jsonIteratorFree(class_itr);
3564
3565                         string = buffer_release(order_buf);
3566
3567                         if ( *string ) {
3568                                 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3569                                 OSRF_BUFFER_ADD( sql_buf, string );
3570                         }
3571
3572                         free(string);
3573                 }
3574
3575                 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
3576                         string = jsonObjectToSimpleString(_tmp);
3577                         buffer_fadd(
3578                                 sql_buf,
3579                                 " LIMIT %d",
3580                                 atoi(string)
3581                         );
3582                         free(string);
3583                 }
3584
3585                 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
3586                 if (_tmp) {
3587                         string = jsonObjectToSimpleString(_tmp);
3588                         buffer_fadd(
3589                                 sql_buf,
3590                                 " OFFSET %d",
3591                                 atoi(string)
3592                         );
3593                         free(string);
3594                 }
3595         }
3596
3597         if (defaultselhash) jsonObjectFree(defaultselhash);
3598
3599         OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3600         return buffer_release(sql_buf);
3601 }
3602
3603 int doJSONSearch ( osrfMethodContext* ctx ) {
3604         if(osrfMethodVerifyContext( ctx )) {
3605                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
3606                 return -1;
3607         }
3608
3609         osrfLogDebug(OSRF_LOG_MARK, "Recieved query request");
3610
3611         int err = 0;
3612
3613         // XXX for now...
3614         dbhandle = writehandle;
3615
3616         jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
3617
3618         int flags = 0;
3619
3620         if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
3621                 flags |= SELECT_DISTINCT;
3622
3623         if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
3624                 flags |= DISABLE_I18N;
3625
3626         osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
3627         char* sql = SELECT(
3628                         ctx,
3629                         jsonObjectGetKey( hash, "select" ),
3630                         jsonObjectGetKey( hash, "from" ),
3631                         jsonObjectGetKey( hash, "where" ),
3632                         jsonObjectGetKey( hash, "having" ),
3633                         jsonObjectGetKey( hash, "order_by" ),
3634                         jsonObjectGetKey( hash, "limit" ),
3635                         jsonObjectGetKey( hash, "offset" ),
3636                         flags
3637         );
3638
3639         if (!sql) {
3640                 err = -1;
3641                 return err;
3642         }
3643         
3644         osrfLogDebug(OSRF_LOG_MARK, "%s SQL =  %s", MODULENAME, sql);
3645         dbi_result result = dbi_conn_query(dbhandle, sql);
3646
3647         if(result) {
3648                 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
3649
3650                 if (dbi_result_first_row(result)) {
3651                         /* JSONify the result */
3652                         osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
3653
3654                         do {
3655                                 jsonObject* return_val = oilsMakeJSONFromResult( result );
3656                                 osrfAppRespond( ctx, return_val );
3657                 jsonObjectFree( return_val );
3658                         } while (dbi_result_next_row(result));
3659
3660                 } else {
3661                         osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
3662                 }
3663
3664                 osrfAppRespondComplete( ctx, NULL );
3665
3666                 /* clean up the query */
3667                 dbi_result_free(result); 
3668
3669         } else {
3670                 err = -1;
3671                 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
3672                 osrfAppSessionStatus(
3673                         ctx->session,
3674                         OSRF_STATUS_INTERNALSERVERERROR,
3675                         "osrfMethodException",
3676                         ctx->request,
3677                         "Severe query error -- see error log for more details"
3678                 );
3679         }
3680
3681         free(sql);
3682         return err;
3683 }
3684
3685 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
3686                 const jsonObject* params, int* err ) {
3687
3688         // XXX for now...
3689         dbhandle = writehandle;
3690
3691         osrfHash* links = osrfHashGet(meta, "links");
3692         osrfHash* fields = osrfHashGet(meta, "fields");
3693         char* core_class = osrfHashGet(meta, "classname");
3694         char* pkey = osrfHashGet(meta, "primarykey");
3695
3696         const jsonObject* _tmp;
3697         jsonObject* obj;
3698         jsonObject* search_hash = jsonObjectGetIndex(params, 0);
3699         jsonObject* order_hash = jsonObjectGetIndex(params, 1);
3700
3701         char* sql = buildSELECT( search_hash, order_hash, meta, ctx );
3702         if (!sql) {
3703                 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
3704                 *err = -1;
3705                 return NULL;
3706         }
3707         
3708         osrfLogDebug(OSRF_LOG_MARK, "%s SQL =  %s", MODULENAME, sql);
3709
3710         dbi_result result = dbi_conn_query(dbhandle, sql);
3711         if( NULL == result ) {
3712                 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
3713                         MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
3714                 osrfAppSessionStatus(
3715                         ctx->session,
3716                         OSRF_STATUS_INTERNALSERVERERROR,
3717                         "osrfMethodException",
3718                         ctx->request,
3719                         "Severe query error -- see error log for more details"
3720                 );
3721                 *err = -1;
3722                 free(sql);
3723                 return jsonNULL;
3724
3725         } else {
3726                 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
3727         }
3728
3729         jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
3730         osrfHash* dedup = osrfNewHash();
3731
3732         if (dbi_result_first_row(result)) {
3733                 /* JSONify the result */
3734                 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
3735                 do {
3736                         obj = oilsMakeFieldmapperFromResult( result, meta );
3737                         char* pkey_val = oilsFMGetString( obj, pkey );
3738                         if ( osrfHashGet( dedup, pkey_val ) ) {
3739                                 jsonObjectFree(obj);
3740                                 free(pkey_val);
3741                         } else {
3742                                 osrfHashSet( dedup, pkey_val, pkey_val );
3743                                 jsonObjectPush(res_list, obj);
3744                         }
3745                 } while (dbi_result_next_row(result));
3746         } else {
3747                 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
3748                         MODULENAME, sql );
3749         }
3750
3751         osrfHashFree(dedup);
3752         /* clean up the query */
3753         dbi_result_free(result);
3754         free(sql);
3755
3756         if (res_list->size && order_hash) {
3757                 _tmp = jsonObjectGetKeyConst( order_hash, "flesh" );
3758                 if (_tmp) {
3759                         int x = (int)jsonObjectGetNumber(_tmp);
3760                         if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
3761
3762                         const jsonObject* temp_blob;
3763                         if ((temp_blob = jsonObjectGetKeyConst( order_hash, "flesh_fields" )) && x > 0) {
3764
3765                                 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
3766                                 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
3767
3768                                 osrfStringArray* link_fields = NULL;
3769
3770                                 if (flesh_fields) {
3771                                         if (flesh_fields->size == 1) {
3772                                                 char* _t = jsonObjectToSimpleString( jsonObjectGetIndex( flesh_fields, 0 ) );
3773                                                 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
3774                                                 free(_t);
3775                                         }
3776
3777                                         if (!link_fields) {
3778                                                 jsonObject* _f;
3779                                                 link_fields = osrfNewStringArray(1);
3780                                                 jsonIterator* _i = jsonNewIterator( flesh_fields );
3781                                                 while ((_f = jsonIteratorNext( _i ))) {
3782                                                         osrfStringArrayAdd( link_fields, jsonObjectToSimpleString( _f ) );
3783                                                 }
3784                         jsonIteratorFree(_i);
3785                                         }
3786                                 }
3787
3788                                 jsonObject* cur;
3789                                 jsonIterator* itr = jsonNewIterator( res_list );
3790                                 while ((cur = jsonIteratorNext( itr ))) {
3791
3792                                         int i = 0;
3793                                         char* link_field;
3794                                         
3795                                         while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
3796
3797                                                 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
3798
3799                                                 osrfHash* kid_link = osrfHashGet(links, link_field);
3800                                                 if (!kid_link) continue;
3801
3802                                                 osrfHash* field = osrfHashGet(fields, link_field);
3803                                                 if (!field) continue;
3804
3805                                                 osrfHash* value_field = field;
3806
3807                                                 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
3808                                                 if (!kid_idl) continue;
3809
3810                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
3811                                                         value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
3812                                                 }
3813                                                         
3814                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
3815                                                         value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
3816                                                 }
3817
3818                                                 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
3819
3820                                                 if (link_map->size > 0) {
3821                                                         jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
3822                                                         jsonObjectPush(
3823                                                                 _kid_key,
3824                                                                 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
3825                                                         );
3826
3827                                                         jsonObjectSetKey(
3828                                                                 flesh_blob,
3829                                                                 osrfHashGet(kid_link, "class"),
3830                                                                 _kid_key
3831                                                         );
3832                                                 };
3833
3834                                                 osrfLogDebug(
3835                                                         OSRF_LOG_MARK,
3836                                                         "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
3837                                                         osrfHashGet(kid_link, "field"),
3838                                                         osrfHashGet(kid_link, "class"),
3839                                                         osrfHashGet(kid_link, "key"),
3840                                                         osrfHashGet(kid_link, "reltype")
3841                                                 );
3842
3843                                                 jsonObject* fake_params = jsonNewObjectType(JSON_ARRAY);
3844                                                 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH)); // search hash
3845                                                 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH)); // order/flesh hash
3846
3847                                                 osrfLogDebug(OSRF_LOG_MARK, "Creating dummy params object...");
3848
3849                                                 char* search_key =
3850                                                 jsonObjectToSimpleString(
3851                                                         jsonObjectGetIndex(
3852                                                                 cur,
3853                                                                 atoi( osrfHashGet(value_field, "array_position") )
3854                                                         )
3855                                                 );
3856
3857                                                 if (!search_key) {
3858                                                         osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
3859                                                         continue;
3860                                                 }
3861                                                         
3862                                                 jsonObjectSetKey(
3863                                                         jsonObjectGetIndex(fake_params, 0),
3864                                                         osrfHashGet(kid_link, "key"),
3865                                                         jsonNewObject( search_key )
3866                                                 );
3867
3868                                                 free(search_key);
3869
3870
3871                                                 jsonObjectSetKey(
3872                                                         jsonObjectGetIndex(fake_params, 1),
3873                                                         "flesh",
3874                                                         jsonNewNumberObject( (double)(x - 1 + link_map->size) )
3875                                                 );
3876
3877                                                 if (flesh_blob)
3878                                                         jsonObjectSetKey( jsonObjectGetIndex(fake_params, 1), "flesh_fields", jsonObjectClone(flesh_blob) );
3879
3880                                                 if (jsonObjectGetKeyConst(order_hash, "order_by")) {
3881                                                         jsonObjectSetKey(
3882                                                                 jsonObjectGetIndex(fake_params, 1),
3883                                                                 "order_by",
3884                                                                 jsonObjectClone(jsonObjectGetKeyConst(order_hash, "order_by"))
3885                                                         );
3886                                                 }
3887
3888                                                 if (jsonObjectGetKeyConst(order_hash, "select")) {
3889                                                         jsonObjectSetKey(
3890                                                                 jsonObjectGetIndex(fake_params, 1),
3891                                                                 "select",
3892                                                                 jsonObjectClone(jsonObjectGetKeyConst(order_hash, "select"))
3893                                                         );
3894                                                 }
3895
3896                                                 jsonObject* kids = doFieldmapperSearch(ctx, kid_idl, fake_params, err);
3897
3898                                                 if(*err) {
3899                                                         jsonObjectFree( fake_params );
3900                                                         osrfStringArrayFree(link_fields);
3901                                                         jsonIteratorFree(itr);
3902                                                         jsonObjectFree(res_list);
3903                                                         jsonObjectFree(flesh_blob);
3904                                                         return jsonNULL;
3905                                                 }
3906
3907                                                 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
3908
3909                                                 jsonObject* X = NULL;
3910                                                 if ( link_map->size > 0 && kids->size > 0 ) {
3911                                                         X = kids;
3912                                                         kids = jsonNewObjectType(JSON_ARRAY);
3913
3914                                                         jsonObject* _k_node;
3915                                                         jsonIterator* _k = jsonNewIterator( X );
3916                                                         while ((_k_node = jsonIteratorNext( _k ))) {
3917                                                                 jsonObjectPush(
3918                                                                         kids,
3919                                                                         jsonObjectClone(
3920                                                                                 jsonObjectGetIndex(
3921                                                                                         _k_node,
3922                                                                                         (unsigned long)atoi(
3923                                                                                                 osrfHashGet(
3924                                                                                                         osrfHashGet(
3925                                                                                                                 osrfHashGet(
3926                                                                                                                         osrfHashGet(
3927                                                                                                                                 oilsIDL(),
3928                                                                                                                                 osrfHashGet(kid_link, "class")
3929                                                                                                                         ),
3930                                                                                                                         "fields"
3931                                                                                                                 ),
3932                                                                                                                 osrfStringArrayGetString( link_map, 0 )
3933                                                                                                         ),
3934                                                                                                         "array_position"
3935                                                                                                 )
3936                                                                                         )
3937                                                                                 )
3938                                                                         )
3939                                                                 );
3940                                                         }
3941                                                         jsonIteratorFree(_k);
3942                                                 }
3943
3944                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
3945                                                         osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
3946                                                         jsonObjectSetIndex(
3947                                                                 cur,
3948                                                                 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
3949                                                                 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
3950                                                         );
3951                                                 }
3952
3953                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
3954                                                         osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
3955                                                         jsonObjectSetIndex(
3956                                                                 cur,
3957                                                                 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
3958                                                                 jsonObjectClone( kids )
3959                                                         );
3960                                                 }
3961
3962                                                 if (X) {
3963                                                         jsonObjectFree(kids);
3964                                                         kids = X;
3965                                                 }
3966
3967                                                 jsonObjectFree( kids );
3968                                                 jsonObjectFree( fake_params );
3969
3970                                                 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
3971                                                 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
3972
3973                                         }
3974                                 }
3975                                 jsonObjectFree( flesh_blob );
3976                                 osrfStringArrayFree(link_fields);
3977                                 jsonIteratorFree(itr);
3978                         }
3979                 }
3980         }
3981
3982         return res_list;
3983 }
3984
3985
3986 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
3987
3988         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
3989 #ifdef PCRUD
3990         jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
3991 #else
3992         jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
3993 #endif
3994
3995         if (!verifyObjectClass(ctx, target)) {
3996                 *err = -1;
3997                 return jsonNULL;
3998         }
3999
4000         if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4001                 osrfAppSessionStatus(
4002                         ctx->session,
4003                         OSRF_STATUS_BADREQUEST,
4004                         "osrfMethodException",
4005                         ctx->request,
4006                         "No active transaction -- required for UPDATE"
4007                 );
4008                 *err = -1;
4009                 return jsonNULL;
4010         }
4011
4012         // The following test is harmless but redundant.  If a class is
4013         // readonly, we don't register an update method for it.
4014         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4015                 osrfAppSessionStatus(
4016                         ctx->session,
4017                         OSRF_STATUS_BADREQUEST,
4018                         "osrfMethodException",
4019                         ctx->request,
4020                         "Cannot UPDATE readonly class"
4021                 );
4022                 *err = -1;
4023                 return jsonNULL;
4024         }
4025
4026         dbhandle = writehandle;
4027
4028         char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4029
4030         // Set the last_xact_id
4031         int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4032         if (index > -1) {
4033                 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
4034                 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4035         }       
4036
4037         char* pkey = osrfHashGet(meta, "primarykey");
4038         osrfHash* fields = osrfHashGet(meta, "fields");
4039
4040         char* id = oilsFMGetString( target, pkey );
4041
4042         osrfLogDebug(
4043                 OSRF_LOG_MARK,
4044                 "%s updating %s object with %s = %s",
4045                 MODULENAME,
4046                 osrfHashGet(meta, "fieldmapper"),
4047                 pkey,
4048                 id
4049         );
4050
4051         growing_buffer* sql = buffer_init(128);
4052         buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4053
4054         int i = 0;
4055         int first = 1;
4056         char* field_name;
4057         osrfStringArray* field_list = osrfHashKeys( fields );
4058         while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
4059
4060                 osrfHash* field = osrfHashGet( fields, field_name );
4061
4062                 if(!( strcmp( field_name, pkey ) )) continue;
4063                 if( str_is_true( osrfHashGet(osrfHashGet(fields,field_name), "virtual") ) )
4064                         continue;
4065
4066                 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4067
4068                 int value_is_numeric = 0;    // boolean
4069                 char* value;
4070                 if (field_object && field_object->classname) {
4071                         value = oilsFMGetString(
4072                                 field_object,
4073                                 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4074             );
4075                 } else {
4076                         value = jsonObjectToSimpleString( field_object );
4077                         if( field_object && JSON_NUMBER == field_object->type )
4078                                 value_is_numeric = 1;
4079                 }
4080
4081                 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s", osrfHashGet(meta, "fieldmapper"), field_name, value);
4082
4083                 if (!field_object || field_object->type == JSON_NULL) {
4084                         if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) ) && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
4085                                 if (first) first = 0;
4086                                 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4087                                 buffer_fadd( sql, " %s = NULL", field_name );
4088                         }
4089                         
4090                 } else if ( value_is_numeric || !strcmp( get_primitive( field ), "number") ) {
4091                         if (first) first = 0;
4092                         else OSRF_BUFFER_ADD_CHAR(sql, ',');
4093
4094                         const char* numtype = get_datatype( field );
4095                         if ( !strncmp( numtype, "INT", 3 ) ) {
4096                                 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
4097                         } else if ( !strcmp( numtype, "NUMERIC" ) ) {
4098                                 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
4099                         }
4100
4101                         osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
4102
4103                 } else {
4104                         if ( dbi_conn_quote_string(dbhandle, &value) ) {
4105                                 if (first) first = 0;
4106                                 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4107                                 buffer_fadd( sql, " %s = %s", field_name, value );
4108
4109                         } else {
4110                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4111                                 osrfAppSessionStatus(
4112                                         ctx->session,
4113                                         OSRF_STATUS_INTERNALSERVERERROR,
4114                                         "osrfMethodException",
4115                                         ctx->request,
4116                                         "Error quoting string -- please see the error log for more details"
4117                                 );
4118                                 free(value);
4119                                 free(id);
4120                                 buffer_free(sql);
4121                                 *err = -1;
4122                                 return jsonNULL;
4123                         }
4124                 }
4125
4126                 free(value);
4127                 
4128         }
4129
4130         jsonObject* obj = jsonNewObject(id);
4131
4132         if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4133                 dbi_conn_quote_string(dbhandle, &id);
4134
4135         buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
4136
4137         char* query = buffer_release(sql);
4138         osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
4139
4140         dbi_result result = dbi_conn_query(dbhandle, query);
4141         free(query);
4142
4143         if (!result) {
4144                 jsonObjectFree(obj);
4145                 obj = jsonNewObject(NULL);
4146                 osrfLogError(
4147                         OSRF_LOG_MARK,
4148                         "%s ERROR updating %s object with %s = %s",
4149                         MODULENAME,
4150                         osrfHashGet(meta, "fieldmapper"),
4151                         pkey,
4152                         id
4153                 );
4154         }
4155
4156         free(id);
4157
4158         return obj;
4159 }
4160
4161 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4162
4163         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4164
4165         if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4166                 osrfAppSessionStatus(
4167                         ctx->session,
4168                         OSRF_STATUS_BADREQUEST,
4169                         "osrfMethodException",
4170                         ctx->request,
4171                         "No active transaction -- required for DELETE"
4172                 );
4173                 *err = -1;
4174                 return jsonNULL;
4175         }
4176
4177         // The following test is harmless but redundant.  If a class is
4178         // readonly, we don't register a delete method for it.
4179         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4180                 osrfAppSessionStatus(
4181                         ctx->session,
4182                         OSRF_STATUS_BADREQUEST,
4183                         "osrfMethodException",
4184                         ctx->request,
4185                         "Cannot DELETE readonly class"
4186                 );
4187                 *err = -1;
4188                 return jsonNULL;
4189         }
4190
4191         dbhandle = writehandle;
4192
4193         jsonObject* obj;
4194
4195         char* pkey = osrfHashGet(meta, "primarykey");
4196
4197         int _obj_pos = 0;
4198 #ifdef PCRUD
4199                 _obj_pos = 1;
4200 #endif
4201
4202         char* id;
4203         if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4204                 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4205                         *err = -1;
4206                         return jsonNULL;
4207                 }
4208
4209                 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4210         } else {
4211 #ifdef PCRUD
4212         if (!verifyObjectPCRUD( ctx, NULL )) {
4213                         *err = -1;
4214                         return jsonNULL;
4215         }
4216 #endif
4217                 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4218         }
4219
4220         osrfLogDebug(
4221                 OSRF_LOG_MARK,
4222                 "%s deleting %s object with %s = %s",
4223                 MODULENAME,
4224                 osrfHashGet(meta, "fieldmapper"),
4225                 pkey,
4226                 id
4227         );
4228
4229         obj = jsonNewObject(id);
4230
4231         if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4232                 dbi_conn_quote_string(writehandle, &id);
4233
4234         dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4235
4236         if (!result) {
4237                 jsonObjectFree(obj);
4238                 obj = jsonNewObject(NULL);
4239                 osrfLogError(
4240                         OSRF_LOG_MARK,
4241                         "%s ERROR deleting %s object with %s = %s",
4242                         MODULENAME,
4243                         osrfHashGet(meta, "fieldmapper"),
4244                         pkey,
4245                         id
4246                 );
4247         }
4248
4249         free(id);
4250
4251         return obj;
4252
4253 }
4254
4255
4256 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4257         if(!(result && meta)) return jsonNULL;
4258
4259         jsonObject* object = jsonNewObject(NULL);
4260         jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4261
4262         osrfHash* fields = osrfHashGet(meta, "fields");
4263
4264         osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4265
4266         osrfHash* _f;
4267         time_t _tmp_dt;
4268         char dt_string[256];
4269         struct tm gmdt;
4270
4271         int fmIndex;
4272         int columnIndex = 1;
4273         int attr;
4274         unsigned short type;
4275         const char* columnName;
4276
4277         /* cycle through the column list */
4278         while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4279
4280                 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4281
4282                 fmIndex = -1; // reset the position
4283                 
4284                 /* determine the field type and storage attributes */
4285                 type = dbi_result_get_field_type(result, columnName);
4286                 attr = dbi_result_get_field_attribs(result, columnName);
4287
4288                 /* fetch the fieldmapper index */
4289                 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4290                         
4291                         if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4292                                 continue;
4293                         
4294                         const char* pos = (char*)osrfHashGet(_f, "array_position");
4295                         if ( !pos ) continue;
4296
4297                         fmIndex = atoi( pos );
4298                         osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
4299                 } else {
4300                         continue;
4301                 }
4302
4303                 if (dbi_result_field_is_null(result, columnName)) {
4304                         jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
4305                 } else {
4306
4307                         switch( type ) {
4308
4309                                 case DBI_TYPE_INTEGER :
4310
4311                                         if( attr & DBI_INTEGER_SIZE8 ) 
4312                                                 jsonObjectSetIndex( object, fmIndex, 
4313                                                         jsonNewNumberObject(dbi_result_get_longlong(result, columnName)));
4314                                         else 
4315                                                 jsonObjectSetIndex( object, fmIndex, 
4316                                                         jsonNewNumberObject(dbi_result_get_int(result, columnName)));
4317
4318                                         break;
4319
4320                                 case DBI_TYPE_DECIMAL :
4321                                         jsonObjectSetIndex( object, fmIndex, 
4322                                                         jsonNewNumberObject(dbi_result_get_double(result, columnName)));
4323                                         break;
4324
4325                                 case DBI_TYPE_STRING :
4326
4327
4328                                         jsonObjectSetIndex(
4329                                                 object,
4330                                                 fmIndex,
4331                                                 jsonNewObject( dbi_result_get_string(result, columnName) )
4332                                         );
4333
4334                                         break;
4335
4336                                 case DBI_TYPE_DATETIME :
4337
4338                                         memset(dt_string, '\0', sizeof(dt_string));
4339                                         memset(&gmdt, '\0', sizeof(gmdt));
4340
4341                                         _tmp_dt = dbi_result_get_datetime(result, columnName);
4342
4343
4344                                         if (!(attr & DBI_DATETIME_DATE)) {
4345                                                 gmtime_r( &_tmp_dt, &gmdt );
4346                                                 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4347                                         } else if (!(attr & DBI_DATETIME_TIME)) {
4348                                                 localtime_r( &_tmp_dt, &gmdt );
4349                                                 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4350                                         } else {
4351                                                 localtime_r( &_tmp_dt, &gmdt );
4352                                                 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4353                                         }
4354
4355                                         jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
4356
4357                                         break;
4358
4359                                 case DBI_TYPE_BINARY :
4360                                         osrfLogError( OSRF_LOG_MARK, 
4361                                                 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4362                         }
4363                 }
4364         }
4365
4366         return object;
4367 }
4368
4369 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
4370         if(!result) return jsonNULL;
4371
4372         jsonObject* object = jsonNewObject(NULL);
4373
4374         time_t _tmp_dt;
4375         char dt_string[256];
4376         struct tm gmdt;
4377
4378         int fmIndex;
4379         int columnIndex = 1;
4380         int attr;
4381         unsigned short type;
4382         const char* columnName;
4383
4384         /* cycle through the column list */
4385         while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4386
4387                 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4388
4389                 fmIndex = -1; // reset the position
4390                 
4391                 /* determine the field type and storage attributes */
4392                 type = dbi_result_get_field_type(result, columnName);
4393                 attr = dbi_result_get_field_attribs(result, columnName);
4394
4395                 if (dbi_result_field_is_null(result, columnName)) {
4396                         jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
4397                 } else {
4398
4399                         switch( type ) {
4400
4401                                 case DBI_TYPE_INTEGER :
4402
4403                                         if( attr & DBI_INTEGER_SIZE8 ) 
4404                                                 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_longlong(result, columnName)) );
4405                                         else 
4406                                                 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_int(result, columnName)) );
4407                                         break;
4408
4409                                 case DBI_TYPE_DECIMAL :
4410                                         jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_double(result, columnName)) );
4411                                         break;
4412
4413                                 case DBI_TYPE_STRING :
4414                                         jsonObjectSetKey( object, columnName, jsonNewObject(dbi_result_get_string(result, columnName)) );
4415                                         break;
4416
4417                                 case DBI_TYPE_DATETIME :
4418
4419                                         memset(dt_string, '\0', sizeof(dt_string));
4420                                         memset(&gmdt, '\0', sizeof(gmdt));
4421
4422                                         _tmp_dt = dbi_result_get_datetime(result, columnName);
4423
4424
4425                                         if (!(attr & DBI_DATETIME_DATE)) {
4426                                                 gmtime_r( &_tmp_dt, &gmdt );
4427                                                 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4428                                         } else if (!(attr & DBI_DATETIME_TIME)) {
4429                                                 localtime_r( &_tmp_dt, &gmdt );
4430                                                 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4431                                         } else {
4432                                                 localtime_r( &_tmp_dt, &gmdt );
4433                                                 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4434                                         }
4435
4436                                         jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
4437                                         break;
4438
4439                                 case DBI_TYPE_BINARY :
4440                                         osrfLogError( OSRF_LOG_MARK, 
4441                                                 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4442                         }
4443                 }
4444         }
4445
4446         return object;
4447 }
4448
4449 // Interpret a string as true or false
4450 static int str_is_true( const char* str ) {
4451         if( NULL == str || strcasecmp( str, "true" ) )
4452                 return 0;
4453         else
4454                 return 1;
4455 }
4456
4457 // Interpret a jsonObject as true or false
4458 static int obj_is_true( const jsonObject* obj ) {
4459         if( !obj )
4460                 return 0;
4461         else switch( obj->type )
4462         {
4463                 case JSON_BOOL :
4464                         if( obj->value.b )
4465                                 return 1;
4466                         else
4467                                 return 0;
4468                 case JSON_STRING :
4469                         if( strcasecmp( obj->value.s, "true" ) )
4470                                 return 0;
4471                         else
4472                                 return 1;
4473                 case JSON_NUMBER :          // Support 1/0 for perl's sake
4474                         if( jsonObjectGetNumber( obj ) == 1.0 )
4475                                 return 1;
4476                         else
4477                                 return 0;
4478                 default :
4479                         return 0;
4480         }
4481 }
4482
4483 // Translate a numeric code into a text string identifying a type of
4484 // jsonObject.  To be used for building error messages.
4485 static const char* json_type( int code ) {
4486         switch ( code )
4487         {
4488                 case 0 :
4489                         return "JSON_HASH";
4490                 case 1 :
4491                         return "JSON_ARRAY";
4492                 case 2 :
4493                         return "JSON_STRING";
4494                 case 3 :
4495                         return "JSON_NUMBER";
4496                 case 4 :
4497                         return "JSON_NULL";
4498                 case 5 :
4499                         return "JSON_BOOL";
4500                 default :
4501                         return "(unrecognized)";
4502         }
4503 }
4504
4505 // Extract the "primitive" attribute from an IDL field definition.
4506 // If we haven't initialized the app, then we must be running in
4507 // some kind of testbed.  In that case, default to "string".
4508 static const char* get_primitive( osrfHash* field ) {
4509         const char* s = osrfHashGet( field, "primitive" );
4510         if( !s ) {
4511                 if( child_initialized )
4512                         osrfLogError(
4513                                 OSRF_LOG_MARK,
4514                                 "%s ERROR No \"datatype\" attribute for field \"%s\"",
4515                                 MODULENAME,
4516                                 osrfHashGet( field, "name" )
4517                         );
4518                 else
4519                         s = "string";
4520         }
4521         return s;
4522 }
4523
4524 // Extract the "datatype" attribute from an IDL field definition.
4525 // If we haven't initialized the app, then we must be running in
4526 // some kind of testbed.  In that case, default to to NUMERIC,
4527 // since we look at the datatype only for numbers.
4528 static const char* get_datatype( osrfHash* field ) {
4529         const char* s = osrfHashGet( field, "datatype" );
4530         if( !s ) {
4531                 if( child_initialized )
4532                         osrfLogError(
4533                                 OSRF_LOG_MARK,
4534                                 "%s ERROR No \"datatype\" attribute for field \"%s\"",
4535                                 MODULENAME,
4536                                 osrfHashGet( field, "name" )
4537                         );
4538                 else
4539                         s = "NUMERIC";
4540         }
4541         return s;
4542 }
4543
4544 /*
4545 If the input string is potentially a valid SQL identifier, return 1.
4546 Otherwise return 0.
4547
4548 Purpose: to prevent certain kinds of SQL injection.  To that end we
4549 don't necessarily need to follow all the rules exactly, such as requiring
4550 that the first character not be a digit.
4551
4552 We allow leading and trailing white space.  In between, we do not allow
4553 punctuation (except for underscores and dollar signs), control 
4554 characters, or embedded white space.
4555
4556 More pedantically we should allow quoted identifiers containing arbitrary
4557 characters, but for the foreseeable future such quoted identifiers are not
4558 likely to be an issue.
4559 */
4560 static int is_identifier( const char* s) {
4561         if( !s )
4562                 return 0;
4563
4564         // Skip leading white space
4565         while( isspace( (unsigned char) *s ) )
4566                 ++s;
4567
4568         if( !s )
4569                 return 0;   // Nothing but white space?  Not okay.
4570
4571         // Check each character until we reach white space or
4572         // end-of-string.  Letters, digits, underscores, and 
4573         // dollar signs are okay.  Control characters and other
4574         // punctuation characters are not okay.  Anything else
4575         // is okay -- it could for example be part of a multibyte
4576         // UTF8 character such as a letter with diacritical marks,
4577         // and those are allowed.
4578         do {
4579                 if( isalnum( (unsigned char) *s )
4580                         || '_' == *s
4581                         || '$' == *s )
4582                         ;  // Fine; keep going
4583                 else if(   ispunct( (unsigned char) *s )
4584                                 || iscntrl( (unsigned char) *s ) )
4585                         return 0;
4586                         ++s;
4587         } while( *s && ! isspace( (unsigned char) *s ) );
4588
4589         // If we found any white space in the above loop,
4590         // the rest had better be all white space.
4591         
4592         while( isspace( (unsigned char) *s ) )
4593                 ++s;
4594
4595         if( *s )
4596                 return 0;   // White space was embedded within non-white space
4597
4598         return 1;
4599 }