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