]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/c-apps/oils_cstore.c
Implement a NOT operator, expressed as "-not".
[Evergreen.git] / Open-ILS / src / c-apps / oils_cstore.c
1 #include <ctype.h>
2 #include "opensrf/osrf_application.h"
3 #include "opensrf/osrf_settings.h"
4 #include "opensrf/osrf_message.h"
5 #include "opensrf/utils.h"
6 #include "opensrf/osrf_json.h"
7 #include "opensrf/log.h"
8 #include "openils/oils_utils.h"
9 #include <dbi/dbi.h>
10
11 #include <time.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <unistd.h>
15
16 #ifdef RSTORE
17 #  define MODULENAME "open-ils.reporter-store"
18 #else
19 #  ifdef PCRUD
20 #    define MODULENAME "open-ils.pcrud"
21 #  else
22 #    define MODULENAME "open-ils.cstore"
23 #  endif
24 #endif
25
26 #define SUBSELECT       4
27 #define DISABLE_I18N    2
28 #define SELECT_DISTINCT 1
29 #define AND_OP_JOIN     0
30 #define OR_OP_JOIN      1
31
32 int osrfAppChildInit();
33 int osrfAppInitialize();
34 void osrfAppChildExit();
35
36 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
37
38 int beginTransaction ( osrfMethodContext* );
39 int commitTransaction ( osrfMethodContext* );
40 int rollbackTransaction ( osrfMethodContext* );
41
42 int setSavepoint ( osrfMethodContext* );
43 int releaseSavepoint ( osrfMethodContext* );
44 int rollbackSavepoint ( osrfMethodContext* );
45
46 int doJSONSearch ( osrfMethodContext* );
47
48 int dispatchCRUDMethod ( osrfMethodContext* );
49 static jsonObject* doCreate ( osrfMethodContext*, int* );
50 static jsonObject* doRetrieve ( osrfMethodContext*, int* );
51 static jsonObject* doUpdate ( osrfMethodContext*, int* );
52 static jsonObject* doDelete ( osrfMethodContext*, int* );
53 static jsonObject* doFieldmapperSearch ( osrfMethodContext*, osrfHash*,
54         const jsonObject*, int* );
55 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
56 static jsonObject* oilsMakeJSONFromResult( dbi_result );
57
58 static char* searchSimplePredicate ( const char* op, const char* class, 
59                                 osrfHash* field, const jsonObject* node );
60 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
61 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject*);
62 static char* searchFieldTransformPredicate ( const char*, osrfHash*, jsonObject*, const char* );
63 static char* searchBETWEENPredicate ( const char*, osrfHash*, jsonObject* );
64 static char* searchINPredicate ( const char*, osrfHash*,
65                                                                  jsonObject*, const char*, osrfMethodContext* );
66 static char* searchPredicate ( const char*, osrfHash*, jsonObject*, osrfMethodContext* );
67 static char* searchJOIN ( const jsonObject*, osrfHash* );
68 static char* searchWHERE ( const jsonObject*, osrfHash*, int, osrfMethodContext* );
69 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
70
71 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
72
73 void userDataFree( void* );
74 static void sessionDataFree( char*, void* );
75 static char* getSourceDefinition( osrfHash* );
76 static int str_is_true( const char* str );
77 static int obj_is_true( const jsonObject* obj );
78 static const char* json_type( int code );
79 static const char* get_primitive( osrfHash* field );
80 static const char* get_datatype( osrfHash* field );
81 static int is_identifier( const char* s);
82
83 #ifdef PCRUD
84 static jsonObject* verifyUserPCRUD( osrfMethodContext* );
85 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
86 #endif
87
88 static int child_initialized = 0;   /* boolean */
89
90 static dbi_conn writehandle; /* our MASTER db connection */
91 static dbi_conn dbhandle; /* our CURRENT db connection */
92 //static osrfHash * readHandles;
93 static jsonObject* jsonNULL = NULL; // 
94 static int max_flesh_depth = 100;
95
96 /* called when this process is about to exit */
97 void osrfAppChildExit() {
98     osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
99
100     int same = 0;
101     if (writehandle == dbhandle) same = 1;
102     if (writehandle) {
103         dbi_conn_query(writehandle, "ROLLBACK;");
104         dbi_conn_close(writehandle);
105         writehandle = NULL;
106     }
107     if (dbhandle && !same)
108         dbi_conn_close(dbhandle);
109
110     // XXX add cleanup of readHandles whenever that gets used
111
112     return;
113 }
114
115 int osrfAppInitialize() {
116
117     osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
118     osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
119
120     if (!oilsIDLInit( osrf_settings_host_value("/IDL") )) return 1; /* return non-zero to indicate error */
121
122     growing_buffer* method_name = buffer_init(64);
123 #ifndef PCRUD
124     // Generic search thingy
125     buffer_add(method_name, MODULENAME);
126         buffer_add(method_name, ".json_query");
127         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
128                                                    "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
129 #endif
130
131     // first we register all the transaction and savepoint methods
132     buffer_reset(method_name);
133         OSRF_BUFFER_ADD(method_name, MODULENAME);
134         OSRF_BUFFER_ADD(method_name, ".transaction.begin");
135         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
136                                                    "beginTransaction", "", 0, 0 );
137
138     buffer_reset(method_name);
139         OSRF_BUFFER_ADD(method_name, MODULENAME);
140         OSRF_BUFFER_ADD(method_name, ".transaction.commit");
141         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
142                                                    "commitTransaction", "", 0, 0 );
143
144     buffer_reset(method_name);
145         OSRF_BUFFER_ADD(method_name, MODULENAME);
146         OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
147         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
148                                                    "rollbackTransaction", "", 0, 0 );
149
150     buffer_reset(method_name);
151         OSRF_BUFFER_ADD(method_name, MODULENAME);
152         OSRF_BUFFER_ADD(method_name, ".savepoint.set");
153         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
154                                                    "setSavepoint", "", 1, 0 );
155
156     buffer_reset(method_name);
157         OSRF_BUFFER_ADD(method_name, MODULENAME);
158         OSRF_BUFFER_ADD(method_name, ".savepoint.release");
159         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
160                                                    "releaseSavepoint", "", 1, 0 );
161
162     buffer_reset(method_name);
163         OSRF_BUFFER_ADD(method_name, MODULENAME);
164         OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
165         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
166                                                    "rollbackSavepoint", "", 1, 0 );
167
168     buffer_free(method_name);
169
170         static const char* global_method[] = {
171                 "create",
172                 "retrieve",
173                 "update",
174                 "delete",
175                 "search",
176                 "id_list"
177         };
178         const int global_method_count
179                 = sizeof( global_method ) / sizeof ( global_method[0] );
180         
181     int c_index = 0; 
182     char* classname;
183     osrfStringArray* classes = osrfHashKeys( oilsIDL() );
184     osrfLogDebug(OSRF_LOG_MARK, "%d classes loaded", classes->size );
185     osrfLogDebug(OSRF_LOG_MARK,
186                 "At least %d methods will be generated", classes->size * global_method_count);
187
188     while ( (classname = osrfStringArrayGetString(classes, c_index++)) ) {
189         osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
190
191         osrfHash* idlClass = osrfHashGet(oilsIDL(), classname);
192
193         if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), MODULENAME )) {
194             osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on", MODULENAME, classname);
195             continue;
196         }
197
198                 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
199                         osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
200                         continue;
201                 }
202
203         // Look up some other attributes of the current class
204         const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
205                 const char* readonly = osrfHashGet(idlClass, "readonly");
206 #ifdef PCRUD
207         osrfHash* idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
208 #endif
209
210         int i;
211         for( i = 0; i < global_method_count; ++i ) {
212             const char* method_type = global_method[ i ];
213             osrfLogDebug(OSRF_LOG_MARK,
214                 "Using files to build %s class methods for %s", method_type, classname);
215
216             if (!idlClass_fieldmapper) continue;
217
218 #ifdef PCRUD
219             if (!idlClass_permacrud) continue;
220
221             const char* tmp_method = method_type;
222             if ( *tmp_method == 'i' || *tmp_method == 's') {
223                 tmp_method = "retrieve";
224             }
225             if (!osrfHashGet( idlClass_permacrud, tmp_method )) continue;
226 #endif
227
228             if (    str_is_true( readonly ) &&
229                     ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd')
230                ) continue;
231
232             osrfHash* method_meta = osrfNewHash();
233             osrfHashSet(method_meta, idlClass, "class");
234
235             method_name =  buffer_init(64);
236 #ifdef PCRUD
237             buffer_fadd(method_name, "%s.%s.%s", MODULENAME, method_type, classname);
238 #else
239             char* st_tmp = NULL;
240             char* part = NULL;
241             char* _fm = strdup( idlClass_fieldmapper );
242             part = strtok_r(_fm, ":", &st_tmp);
243
244             buffer_fadd(method_name, "%s.direct.%s", MODULENAME, part);
245
246             while ((part = strtok_r(NULL, ":", &st_tmp))) {
247                                 OSRF_BUFFER_ADD_CHAR(method_name, '.');
248                                 OSRF_BUFFER_ADD(method_name, part);
249             }
250                         OSRF_BUFFER_ADD_CHAR(method_name, '.');
251                         OSRF_BUFFER_ADD(method_name, method_type);
252             free(_fm);
253 #endif
254
255             char* method = buffer_release(method_name);
256
257             osrfHashSet( method_meta, method, "methodname" );
258             osrfHashSet( method_meta, strdup(method_type), "methodtype" );
259
260             int flags = 0;
261             if (*method_type == 'i' || *method_type == 's') {
262                 flags = flags | OSRF_METHOD_STREAMING;
263             }
264
265             osrfAppRegisterExtendedMethod(
266                     MODULENAME,
267                     method,
268                     "dispatchCRUDMethod",
269                     "",
270                     1,
271                     flags,
272                     (void*)method_meta
273                     );
274
275             free(method);
276         }
277     }
278
279     return 0;
280 }
281
282 static char* getSourceDefinition( osrfHash* class ) {
283
284         char* tabledef = osrfHashGet(class, "tablename");
285
286         if (tabledef) {
287                 tabledef = strdup(tabledef);
288         } else {
289                 tabledef = osrfHashGet(class, "source_definition");
290                 if( tabledef ) {
291                         growing_buffer* tablebuf = buffer_init(128);
292                         buffer_fadd( tablebuf, "(%s)", tabledef );
293                         tabledef = buffer_release(tablebuf);
294                 } else {
295                         const char* classname = osrfHashGet( class, "classname" );
296                         if( !classname )
297                                 classname = "???";
298                         osrfLogError(
299                                 OSRF_LOG_MARK,
300                                 "%s ERROR No tablename or source_definition for class \"%s\"",
301                                 MODULENAME,
302                                 classname
303                         );
304                 }
305         }
306
307         return tabledef;
308 }
309
310 /**
311  * Connects to the database 
312  */
313 int osrfAppChildInit() {
314
315     osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
316     dbi_initialize(NULL);
317     osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
318
319     char* driver        = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
320     char* user  = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
321     char* host  = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
322     char* port  = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
323     char* db    = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
324     char* pw    = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
325     char* md    = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion", MODULENAME);
326
327     osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
328     writehandle = dbi_conn_new(driver);
329
330     if(!writehandle) {
331         osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
332         return -1;
333     }
334     osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
335
336     osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database.  host=%s, "
337             "port=%s, user=%s, pw=%s, db=%s", MODULENAME, host, port, user, pw, db );
338
339     if(host) dbi_conn_set_option(writehandle, "host", host );
340     if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
341     if(user) dbi_conn_set_option(writehandle, "username", user);
342     if(pw) dbi_conn_set_option(writehandle, "password", pw );
343     if(db) dbi_conn_set_option(writehandle, "dbname", db );
344
345     if(md) max_flesh_depth = atoi(md);
346     if(max_flesh_depth < 0) max_flesh_depth = 1;
347     if(max_flesh_depth > 1000) max_flesh_depth = 1000;
348
349     free(user);
350     free(host);
351     free(port);
352     free(db);
353     free(pw);
354
355     const char* err;
356     if (dbi_conn_connect(writehandle) < 0) {
357         sleep(1);
358         if (dbi_conn_connect(writehandle) < 0) {
359             dbi_conn_error(writehandle, &err);
360             osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
361             return -1;
362         }
363     }
364
365     osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
366
367     int attr;
368     unsigned short type;
369     int i = 0; 
370     char* classname;
371     osrfStringArray* classes = osrfHashKeys( oilsIDL() );
372
373     while ( (classname = osrfStringArrayGetString(classes, i++)) ) {
374         osrfHash* class = osrfHashGet( oilsIDL(), classname );
375         osrfHash* fields = osrfHashGet( class, "fields" );
376
377                 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
378                         osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
379                         continue;
380                 }
381
382         char* tabledef = getSourceDefinition(class);
383                 if( !tabledef )
384                         tabledef = strdup( "(null)" );
385
386         growing_buffer* sql_buf = buffer_init(32);
387         buffer_fadd( sql_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
388
389         free(tabledef);
390
391         char* sql = buffer_release(sql_buf);
392         osrfLogDebug(OSRF_LOG_MARK, "%s Investigatory SQL = %s", MODULENAME, sql);
393
394         dbi_result result = dbi_conn_query(writehandle, sql);
395         free(sql);
396
397         if (result) {
398
399             int columnIndex = 1;
400             const char* columnName;
401             osrfHash* _f;
402             while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
403
404                 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
405
406                 /* fetch the fieldmapper index */
407                 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
408
409                     osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", (char*)columnName);
410
411                     /* determine the field type and storage attributes */
412                     type = dbi_result_get_field_type(result, columnName);
413                     attr = dbi_result_get_field_attribs(result, columnName);
414
415                     switch( type ) {
416
417                         case DBI_TYPE_INTEGER :
418
419                             if ( !osrfHashGet(_f, "primitive") )
420                                 osrfHashSet(_f,"number", "primitive");
421
422                             if( attr & DBI_INTEGER_SIZE8 ) 
423                                 osrfHashSet(_f,"INT8", "datatype");
424                             else 
425                                 osrfHashSet(_f,"INT", "datatype");
426                             break;
427
428                         case DBI_TYPE_DECIMAL :
429                             if ( !osrfHashGet(_f, "primitive") )
430                                 osrfHashSet(_f,"number", "primitive");
431
432                             osrfHashSet(_f,"NUMERIC", "datatype");
433                             break;
434
435                         case DBI_TYPE_STRING :
436                             if ( !osrfHashGet(_f, "primitive") )
437                                 osrfHashSet(_f,"string", "primitive");
438                             osrfHashSet(_f,"TEXT", "datatype");
439                             break;
440
441                         case DBI_TYPE_DATETIME :
442                             if ( !osrfHashGet(_f, "primitive") )
443                                 osrfHashSet(_f,"string", "primitive");
444
445                             osrfHashSet(_f,"TIMESTAMP", "datatype");
446                             break;
447
448                         case DBI_TYPE_BINARY :
449                             if ( !osrfHashGet(_f, "primitive") )
450                                 osrfHashSet(_f,"string", "primitive");
451
452                             osrfHashSet(_f,"BYTEA", "datatype");
453                     }
454
455                     osrfLogDebug(
456                             OSRF_LOG_MARK,
457                             "Setting [%s] to primitive [%s] and datatype [%s]...",
458                             (char*)columnName,
459                             osrfHashGet(_f, "primitive"),
460                             osrfHashGet(_f, "datatype")
461                             );
462                 }
463             }
464             dbi_result_free(result);
465         } else {
466             osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", (char*)classname);
467         }
468     }
469
470     osrfStringArrayFree(classes);
471
472         child_initialized = 1;
473     return 0;
474 }
475
476 /*
477   This function is a sleazy hack intended *only* for testing and
478   debugging.  Any real server process should initialize the 
479   database connection by calling osrfAppChildInit().
480 */
481 void set_cstore_dbi_conn( dbi_conn conn ) {
482         dbhandle = writehandle = conn;
483 }
484
485 void userDataFree( void* blob ) {
486     osrfHashFree( (osrfHash*)blob );
487     return;
488 }
489
490 static void sessionDataFree( char* key, void* item ) {
491     if (!(strcmp(key,"xact_id"))) {
492         if (writehandle)
493             dbi_conn_query(writehandle, "ROLLBACK;");
494         free(item);
495     }
496
497     return;
498 }
499
500 int beginTransaction ( osrfMethodContext* ctx ) {
501         if(osrfMethodVerifyContext( ctx )) {
502                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
503                 return -1;
504         }
505
506 #ifdef PCRUD
507     jsonObject* user = verifyUserPCRUD( ctx );
508     if (!user) return -1;
509     jsonObjectFree(user);
510 #endif
511
512     dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
513     if (!result) {
514         osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
515         osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error starting transaction" );
516         return -1;
517     } else {
518         jsonObject* ret = jsonNewObject(ctx->session->session_id);
519         osrfAppRespondComplete( ctx, ret );
520         jsonObjectFree(ret);
521
522         if (!ctx->session->userData) {
523             ctx->session->userData = osrfNewHash();
524             osrfHashSetCallback((osrfHash*)ctx->session->userData, &sessionDataFree);
525         }
526
527         osrfHashSet( (osrfHash*)ctx->session->userData, strdup( ctx->session->session_id ), "xact_id" );
528         ctx->session->userDataFree = &userDataFree;
529
530     }
531     return 0;
532 }
533
534 int setSavepoint ( osrfMethodContext* ctx ) {
535         if(osrfMethodVerifyContext( ctx )) {
536                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
537                 return -1;
538         }
539
540     int spNamePos = 0;
541 #ifdef PCRUD
542     spNamePos = 1;
543     jsonObject* user = verifyUserPCRUD( ctx );
544     if (!user) return -1;
545     jsonObjectFree(user);
546 #endif
547
548     if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
549         osrfAppSessionStatus(
550                 ctx->session,
551                 OSRF_STATUS_INTERNALSERVERERROR,
552                 "osrfMethodException",
553                 ctx->request,
554                 "No active transaction -- required for savepoints"
555                 );
556         return -1;
557     }
558
559         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
1724         OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1725
1726         return buffer_release(sql_buf);
1727 }
1728
1729 // Receive a JSON_ARRAY representing a function call.  The first
1730 // entry in the array is the function name.  The rest are parameters.
1731 static char* searchValueTransform( const jsonObject* array ) {
1732         growing_buffer* sql_buf = buffer_init(32);
1733
1734         jsonObject* func_item;
1735         
1736         // Get the function name
1737         if( array->size > 0 ) {
1738                 func_item = jsonObjectGetIndex( array, 0 );
1739                 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1740                 OSRF_BUFFER_ADD( sql_buf, "( " );
1741         }
1742         
1743         // Get the parameters
1744         int func_item_index = 1;   // We already grabbed the zeroth entry
1745         while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1746
1747                 // Add a separator comma, if we need one
1748                 if( func_item_index > 2 )
1749                         buffer_add( sql_buf, ", " );
1750
1751                 // Add the current parameter
1752                 if (func_item->type == JSON_NULL) {
1753                         buffer_add( sql_buf, "NULL" );
1754                 } else {
1755                         char* val = jsonObjectToSimpleString(func_item);
1756                         if ( dbi_conn_quote_string(dbhandle, &val) ) {
1757                                 OSRF_BUFFER_ADD( sql_buf, val );
1758                                 free(val);
1759                         } else {
1760                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1761                                 buffer_free(sql_buf);
1762                                 free(val);
1763                                 return NULL;
1764                         }
1765                 }
1766         }
1767
1768         buffer_add( sql_buf, " )" );
1769
1770         return buffer_release(sql_buf);
1771 }
1772
1773 static char* searchFunctionPredicate (const char* class, osrfHash* field,
1774                 const jsonObject* node, const char* node_key) {
1775         growing_buffer* sql_buf = buffer_init(32);
1776
1777         char* val = searchValueTransform(node);
1778         
1779         buffer_fadd(
1780                 sql_buf,
1781                 "\"%s\".%s %s %s",
1782                 class,
1783                 osrfHashGet(field, "name"),
1784                 node_key,
1785                 val
1786         );
1787
1788         free(val);
1789
1790         return buffer_release(sql_buf);
1791 }
1792
1793 // class is a class name
1794 // field is a field definition as stored in the IDL
1795 // node comes from the method parameter, and represents an entry in the SELECT list
1796 static char* searchFieldTransform (const char* class, osrfHash* field, const jsonObject* node) {
1797         growing_buffer* sql_buf = buffer_init(32);
1798
1799         const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
1800         const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
1801
1802         if(transform_subcolumn)
1803                 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' );    // enclose transform in parentheses
1804
1805         if (field_transform) {
1806                 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class, osrfHashGet(field, "name"));
1807                 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
1808
1809                 if (array) {
1810                         if( array->type != JSON_ARRAY ) {
1811                                 osrfLogError( OSRF_LOG_MARK,
1812                                         "%s: Expected JSON_ARRAY for function params; found %s",
1813                                         MODULENAME, json_type( array->type ) );
1814                                 buffer_free( sql_buf );
1815                                 return NULL;
1816                         }
1817                         int func_item_index = 0;
1818                         jsonObject* func_item;
1819                         while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1820
1821                                 char* val = jsonObjectToSimpleString(func_item);
1822
1823                                 if ( !val ) {
1824                                         buffer_add( sql_buf, ",NULL" );
1825                                 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
1826                                         OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
1827                                         OSRF_BUFFER_ADD( sql_buf, val );
1828                                 } else {
1829                                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1830                                         free(val);
1831                                         buffer_free(sql_buf);
1832                                         return NULL;
1833                         }
1834                                 free(val);
1835                         }
1836                 }
1837
1838                 buffer_add( sql_buf, " )" );
1839
1840         } else {
1841                 buffer_fadd( sql_buf, "\"%s\".%s", class, osrfHashGet(field, "name"));
1842         }
1843
1844         if (transform_subcolumn)
1845                 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
1846
1847         return buffer_release(sql_buf);
1848 }
1849
1850 static char* searchFieldTransformPredicate (const char* class, osrfHash* field, jsonObject* node, const char* node_key) {
1851         char* field_transform = searchFieldTransform( class, field, node );
1852         char* value = NULL;
1853
1854         const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
1855         if ( ! value_obj ) {
1856                 value = searchWHERE( node, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1857                 if( !value ) {
1858                         osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME, value);
1859                         free(field_transform);
1860                         return NULL;
1861                 }
1862         } else if ( value_obj->type == JSON_ARRAY ) {
1863                 value = searchValueTransform( value_obj );
1864         } else if ( value_obj->type == JSON_HASH ) {
1865                 value = searchWHERE( value_obj, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1866                 if( !value ) {
1867                         osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME, value);
1868                         free(field_transform);
1869                         return NULL;
1870                 }
1871         } else if ( value_obj->type == JSON_NUMBER ) {
1872                 value = jsonNumberToDBString( field, value_obj );
1873         } else if ( value_obj->type != JSON_NULL ) {
1874                 if ( !strcmp( get_primitive( field ), "number") ) {
1875                         value = jsonNumberToDBString( field, value_obj );
1876                 } else {
1877                         value = jsonObjectToSimpleString( value_obj );
1878                         if ( !dbi_conn_quote_string(dbhandle, &value) ) {
1879                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
1880                                 free(value);
1881                                 free(field_transform);
1882                                 return NULL;
1883                         }
1884                 }
1885         }
1886
1887         growing_buffer* sql_buf = buffer_init(32);
1888         
1889         buffer_fadd(
1890                 sql_buf,
1891                 "%s %s %s",
1892                 field_transform,
1893                 node_key,
1894                 value
1895         );
1896
1897         free(value);
1898         free(field_transform);
1899
1900         return buffer_release(sql_buf);
1901 }
1902
1903 static char* searchSimplePredicate (const char* op, const char* class,
1904                 osrfHash* field, const jsonObject* node) {
1905
1906         char* val = NULL;
1907
1908         // Get the value to which we are comparing the specified column
1909         if (node->type != JSON_NULL) {
1910                 if ( node->type == JSON_NUMBER ) {
1911                         val = jsonNumberToDBString( field, node );
1912                 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
1913                         val = jsonNumberToDBString( field, node );
1914                 } else {
1915                         val = jsonObjectToSimpleString(node);
1916                 }
1917         }
1918
1919         if( val ) {
1920                 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
1921                         // Value is not numeric; enclose it in quotes
1922                         if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
1923                                 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
1924                                 free( val );
1925                                 return NULL;
1926                         }
1927                 }
1928         } else {
1929                 // Compare to a null value
1930                 val = strdup( "NULL" );
1931                 if (strcmp( op, "=" ))
1932                         op = "IS NOT";
1933                 else
1934                         op = "IS";
1935         }
1936
1937         growing_buffer* sql_buf = buffer_init(32);
1938         buffer_fadd( sql_buf, "\"%s\".%s %s %s", class, osrfHashGet(field, "name"), op, val );
1939         char* pred = buffer_release( sql_buf );
1940
1941         free(val);
1942
1943         return pred;
1944 }
1945
1946 static char* searchBETWEENPredicate (const char* class, osrfHash* field, jsonObject* node) {
1947
1948         char* x_string;
1949         char* y_string;
1950
1951         if ( !strcmp( get_primitive( field ), "number") ) {
1952                 x_string = jsonNumberToDBString(field, jsonObjectGetIndex(node,0));
1953                 y_string = jsonNumberToDBString(field, jsonObjectGetIndex(node,1));
1954
1955         } else {
1956                 x_string = jsonObjectToSimpleString(jsonObjectGetIndex(node,0));
1957                 y_string = jsonObjectToSimpleString(jsonObjectGetIndex(node,1));
1958                 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
1959                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]", MODULENAME, x_string, y_string);
1960                         free(x_string);
1961                         free(y_string);
1962                         return NULL;
1963                 }
1964         }
1965
1966         growing_buffer* sql_buf = buffer_init(32);
1967         buffer_fadd( sql_buf, "%s BETWEEN %s AND %s", osrfHashGet(field, "name"), x_string, y_string );
1968         free(x_string);
1969         free(y_string);
1970
1971         return buffer_release(sql_buf);
1972 }
1973
1974 static char* searchPredicate ( const char* class, osrfHash* field, 
1975                                                            jsonObject* node, osrfMethodContext* ctx ) {
1976
1977         char* pred = NULL;
1978         if (node->type == JSON_ARRAY) { // equality IN search
1979                 pred = searchINPredicate( class, field, node, NULL, ctx );
1980         } else if (node->type == JSON_HASH) { // non-equality search
1981                 jsonObject* pred_node;
1982                 jsonIterator* pred_itr = jsonNewIterator( node );
1983                 while ( (pred_node = jsonIteratorNext( pred_itr )) ) {
1984                         if ( !(strcasecmp( pred_itr->key,"between" )) )
1985                                 pred = searchBETWEENPredicate( class, field, pred_node );
1986                         else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
1987                                 pred = searchINPredicate( class, field, pred_node, pred_itr->key, ctx );
1988                         else if ( pred_node->type == JSON_ARRAY )
1989                                 pred = searchFunctionPredicate( class, field, pred_node, pred_itr->key );
1990                         else if ( pred_node->type == JSON_HASH )
1991                                 pred = searchFieldTransformPredicate( class, field, pred_node, pred_itr->key );
1992                         else 
1993                                 pred = searchSimplePredicate( pred_itr->key, class, field, pred_node );
1994
1995                         break;
1996                 }
1997         jsonIteratorFree(pred_itr);
1998         } else if (node->type == JSON_NULL) { // IS NULL search
1999                 growing_buffer* _p = buffer_init(64);
2000                 buffer_fadd(
2001                         _p,
2002                         "\"%s\".%s IS NULL",
2003                         class,
2004                         osrfHashGet(field, "name")
2005                 );
2006                 pred = buffer_release(_p);
2007         } else { // equality search
2008                 pred = searchSimplePredicate( "=", class, field, node );
2009         }
2010
2011         return pred;
2012
2013 }
2014
2015
2016 /*
2017
2018 join : {
2019         acn : {
2020                 field : record,
2021                 fkey : id
2022                 type : left
2023                 filter_op : or
2024                 filter : { ... },
2025                 join : {
2026                         acp : {
2027                                 field : call_number,
2028                                 fkey : id,
2029                                 filter : { ... },
2030                         },
2031                 },
2032         },
2033         mrd : {
2034                 field : record,
2035                 type : inner
2036                 fkey : id,
2037                 filter : { ... },
2038         }
2039 }
2040
2041 */
2042
2043 static char* searchJOIN ( const jsonObject* join_hash, osrfHash* leftmeta ) {
2044
2045         const jsonObject* working_hash;
2046         jsonObject* freeable_hash = NULL;
2047
2048         if (join_hash->type == JSON_STRING) {
2049                 // create a wrapper around a copy of the original
2050                 const char* _tmp = jsonObjectGetString( join_hash );
2051                 freeable_hash = jsonNewObjectType(JSON_HASH);
2052                 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2053                 working_hash = freeable_hash;
2054         }
2055         else {
2056                 if( join_hash->type != JSON_HASH ) {
2057                         osrfLogError(
2058                                 OSRF_LOG_MARK,
2059                                 "%s: JOIN failed; expected JSON object type not found",
2060                                 MODULENAME
2061                         );
2062                         return NULL;
2063                 }
2064                 working_hash = join_hash;
2065         }
2066
2067         growing_buffer* join_buf = buffer_init(128);
2068         const char* leftclass = osrfHashGet(leftmeta, "classname");
2069
2070         jsonObject* snode = NULL;
2071         jsonIterator* search_itr = jsonNewIterator( working_hash );
2072
2073         while ( (snode = jsonIteratorNext( search_itr )) ) {
2074                 const char* class = search_itr->key;
2075                 osrfHash* idlClass = osrfHashGet( oilsIDL(), class );
2076                 if( !idlClass ) {
2077                         osrfLogError(
2078                                 OSRF_LOG_MARK,
2079                                 "%s: JOIN failed.  No class \"%s\" defined in IDL",
2080                                 MODULENAME,
2081                                 search_itr->key
2082                         );
2083                         jsonIteratorFree( search_itr );
2084                         buffer_free( join_buf );
2085                         if( freeable_hash )
2086                                 jsonObjectFree( freeable_hash );
2087                         return NULL;
2088                 }
2089
2090                 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2091                 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2092
2093                 if (field && !fkey) {
2094                         fkey = (const char*)oilsIDLFindPath("/%s/links/%s/key", class, field);
2095                         if (!fkey) {
2096                                 osrfLogError(
2097                                         OSRF_LOG_MARK,
2098                                         "%s: JOIN failed.  No link defined from %s.%s to %s",
2099                                         MODULENAME,
2100                                         class,
2101                                         field,
2102                                         leftclass
2103                                 );
2104                                 buffer_free(join_buf);
2105                                 if(freeable_hash)
2106                                         jsonObjectFree(freeable_hash);
2107                                 jsonIteratorFree(search_itr);
2108                                 return NULL;
2109                         }
2110
2111                 } else if (!field && fkey) {
2112                         field = (const char*)oilsIDLFindPath("/%s/links/%s/key", leftclass, fkey );
2113                         if (!field) {
2114                                 osrfLogError(
2115                                         OSRF_LOG_MARK,
2116                                         "%s: JOIN failed.  No link defined from %s.%s to %s",
2117                                         MODULENAME,
2118                                         leftclass,
2119                                         fkey,
2120                                         class
2121                                 );
2122                                 buffer_free(join_buf);
2123                                 if(freeable_hash)
2124                                         jsonObjectFree(freeable_hash);
2125                                 jsonIteratorFree(search_itr);
2126                                 return NULL;
2127                         }
2128                         field = field;
2129
2130                 } else if (!field && !fkey) {
2131                         osrfHash* _links = oilsIDL_links( leftclass );
2132
2133                         // For each link defined for the left class:
2134                         // see if the link references the joined class
2135                         osrfHashIterator* itr = osrfNewHashIterator( _links );
2136                         osrfHash* curr_link = NULL;
2137                         while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2138                                 const char* other_class = osrfHashGet( curr_link, "class" );
2139                                 if( other_class && !strcmp( other_class, class ) ) {
2140
2141                                         // Found a link between the classes
2142                                         fkey = osrfHashIteratorKey( itr );
2143                                         field = osrfHashGet( curr_link, "key" );
2144                                         break;
2145                                 }
2146                         }
2147                         osrfHashIteratorFree( itr );
2148
2149                         if (!field || !fkey) {
2150                                 // Do another such search, with the classes reversed
2151                                 _links = oilsIDL_links( class );
2152
2153                                 // For each link defined for the joined class:
2154                                 // see if the link references the left class
2155                                 osrfHashIterator* itr = osrfNewHashIterator( _links );
2156                                 osrfHash* curr_link = NULL;
2157                                 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2158                                         const char* other_class = osrfHashGet( curr_link, "class" );
2159                                         if( other_class && !strcmp( other_class, leftclass ) ) {
2160
2161                                                 // Found a link between the classes
2162                                                 fkey = osrfHashIteratorKey( itr );
2163                                                 field = osrfHashGet( curr_link, "key" );
2164                                                 break;
2165                                         }
2166                                 }
2167                                 osrfHashIteratorFree( itr );
2168                         }
2169
2170                         if (!field || !fkey) {
2171                                 osrfLogError(
2172                                         OSRF_LOG_MARK,
2173                                         "%s: JOIN failed.  No link defined between %s and %s",
2174                                         MODULENAME,
2175                                         leftclass,
2176                                         class
2177                                 );
2178                                 buffer_free(join_buf);
2179                                 if(freeable_hash)
2180                                         jsonObjectFree(freeable_hash);
2181                                 jsonIteratorFree(search_itr);
2182                                 return NULL;
2183                         }
2184
2185                 }
2186
2187                 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2188                 if (type) {
2189                         if ( !strcasecmp(type,"left") ) {
2190                                 buffer_add(join_buf, " LEFT JOIN");
2191                         } else if ( !strcasecmp(type,"right") ) {
2192                                 buffer_add(join_buf, " RIGHT JOIN");
2193                         } else if ( !strcasecmp(type,"full") ) {
2194                                 buffer_add(join_buf, " FULL JOIN");
2195                         } else {
2196                                 buffer_add(join_buf, " INNER JOIN");
2197                         }
2198                 } else {
2199                         buffer_add(join_buf, " INNER JOIN");
2200                 }
2201
2202                 char* table = getSourceDefinition(idlClass);
2203                 if( !table ) {
2204                         jsonIteratorFree( search_itr );
2205                         buffer_free( join_buf );
2206                         if( freeable_hash )
2207                                 jsonObjectFree( freeable_hash );
2208                         return NULL;
2209                 }
2210
2211                 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2212                                         table, class, class, field, leftclass, fkey);
2213                 free(table);
2214
2215                 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2216                 if (filter) {
2217                         const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2218                         if ( filter_op && !strcasecmp("or",filter_op) ) {
2219                                 buffer_add( join_buf, " OR " );
2220                         } else {
2221                                 buffer_add( join_buf, " AND " );
2222                         }
2223
2224                         char* jpred = searchWHERE( filter, idlClass, AND_OP_JOIN, NULL );
2225                         if( jpred ) {
2226                                 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2227                                 OSRF_BUFFER_ADD( join_buf, jpred );
2228                                 free(jpred);
2229                         } else {
2230                                 osrfLogError(
2231                                         OSRF_LOG_MARK,
2232                                         "%s: JOIN failed.  Invalid conditional expression.",
2233                                         MODULENAME
2234                                 );
2235                                 jsonIteratorFree( search_itr );
2236                                 buffer_free( join_buf );
2237                                 if( freeable_hash )
2238                                         jsonObjectFree( freeable_hash );
2239                                 return NULL;
2240                         }
2241                 }
2242
2243                 buffer_add(join_buf, " ) ");
2244                 
2245                 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2246                 if (join_filter) {
2247                         char* jpred = searchJOIN( join_filter, idlClass );
2248                         OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2249                         OSRF_BUFFER_ADD( join_buf, jpred );
2250                         free(jpred);
2251                 }
2252         }
2253
2254         if(freeable_hash)
2255                 jsonObjectFree(freeable_hash);
2256         jsonIteratorFree(search_itr);
2257
2258         return buffer_release(join_buf);
2259 }
2260
2261 /*
2262
2263 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2264 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2265 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2266
2267 Generate code to express a set of conditions, as for a WHERE clause.  Parameters:
2268
2269 search_hash is the JSON expression of the conditions.
2270 meta is the class definition from the IDL, for the relevant table.
2271 opjoin_type indicates whether multiple conditions, if present, should be
2272         connected by AND or OR.
2273 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2274         to pass it to other functions -- and all they do with it is to use the session
2275         and request members to send error messages back to the client.
2276
2277 */
2278
2279 static char* searchWHERE ( const jsonObject* search_hash, osrfHash* meta, int opjoin_type, osrfMethodContext* ctx ) {
2280
2281         osrfLogDebug(
2282         OSRF_LOG_MARK,
2283         "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2284         MODULENAME,
2285         search_hash,
2286         meta,
2287         opjoin_type,
2288         ctx
2289     );
2290
2291         growing_buffer* sql_buf = buffer_init(128);
2292
2293         jsonObject* node = NULL;
2294
2295         int first = 1;
2296         if ( search_hash->type == JSON_ARRAY ) {
2297                 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2298                 jsonIterator* search_itr = jsonNewIterator( search_hash );
2299                 if( !jsonIteratorHasNext( search_itr ) ) {
2300                         osrfLogError(
2301                                 OSRF_LOG_MARK,
2302                                 "%s: Invalid predicate structure: empty JSON array",
2303                                 MODULENAME
2304                         );
2305                         jsonIteratorFree( search_itr );
2306                         buffer_free( sql_buf );
2307                         return NULL;
2308                 }
2309
2310                 while ( (node = jsonIteratorNext( search_itr )) ) {
2311             if (first) {
2312                 first = 0;
2313             } else {
2314                 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2315                 else buffer_add(sql_buf, " AND ");
2316             }
2317
2318             char* subpred = searchWHERE( node, meta, opjoin_type, ctx );
2319                         if( subpred ) {
2320                 buffer_fadd(sql_buf, "( %s )", subpred);
2321                 free(subpred);
2322                         } else {
2323                                 jsonIteratorFree( search_itr );
2324                                 buffer_free( sql_buf );
2325                                 return NULL;
2326                         }
2327         }
2328         jsonIteratorFree(search_itr);
2329
2330         } else if ( search_hash->type == JSON_HASH ) {
2331                 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2332                 jsonIterator* search_itr = jsonNewIterator( search_hash );
2333                 if( !jsonIteratorHasNext( search_itr ) ) {
2334                         osrfLogError(
2335                                 OSRF_LOG_MARK,
2336                                 "%s: Invalid predicate structure: empty JSON object",
2337                                 MODULENAME
2338                         );
2339                         jsonIteratorFree( search_itr );
2340                         buffer_free( sql_buf );
2341                         return NULL;
2342                 }
2343
2344                 while ( (node = jsonIteratorNext( search_itr )) ) {
2345
2346             if (first) {
2347                 first = 0;
2348             } else {
2349                 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2350                 else buffer_add(sql_buf, " AND ");
2351             }
2352
2353                         if ( '+' == search_itr->key[ 0 ] ) {
2354                                 if ( node->type == JSON_STRING ) {
2355                                         // Intended purpose; to allow reference to a Boolean column
2356
2357                                         // Verify that the class alias is not empty
2358                                         if( '\0' == search_itr->key[ 1 ] ) {
2359                                                 osrfLogError(
2360                                                         OSRF_LOG_MARK,
2361                                                         "%s: Table alias is empty",
2362                                                         MODULENAME
2363                                                 );
2364                                                 jsonIteratorFree( search_itr );
2365                                                 buffer_free( sql_buf );
2366                                                 return NULL;
2367                                         }
2368
2369                                         // Verify that the string looks like an identifier.
2370                                         const char* subpred = jsonObjectGetString( node );
2371                                         if( ! is_identifier( subpred ) ) {
2372                                                 osrfLogError(
2373                                                          OSRF_LOG_MARK,
2374                                                         "%s: Invalid boolean identifier in WHERE clause: \"%s\"",
2375                                                         MODULENAME,
2376                                                         subpred
2377                                                 );
2378                                                 jsonIteratorFree( search_itr );
2379                                                 buffer_free( sql_buf );
2380                                                 return NULL;
2381                                         }
2382
2383                                         buffer_fadd(sql_buf, " \"%s\".%s ", search_itr->key + 1, subpred);
2384                                 } else {
2385                                         char* subpred = searchWHERE( node, osrfHashGet( oilsIDL(), search_itr->key + 1 ), AND_OP_JOIN, ctx );
2386                                         if( subpred ) {
2387                                                 buffer_fadd(sql_buf, "( %s )", subpred);
2388                                                 free(subpred);
2389                                         } else {
2390                                                 jsonIteratorFree( search_itr );
2391                                                 buffer_free( sql_buf );
2392                                                 return NULL;
2393                                         }
2394                                 }
2395                         } else if ( !strcasecmp("-or",search_itr->key) ) {
2396                                 char* subpred = searchWHERE( node, meta, OR_OP_JOIN, ctx );
2397                                 if( subpred ) {
2398                                         buffer_fadd(sql_buf, "( %s )", subpred);
2399                                         free( subpred );
2400                                 } else {
2401                                         buffer_free( sql_buf );
2402                                         return NULL;
2403                                 }
2404                         } else if ( !strcasecmp("-and",search_itr->key) ) {
2405                                 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2406                                 if( subpred ) {
2407                                         buffer_fadd(sql_buf, "( %s )", subpred);
2408                                         free( subpred );
2409                                 } else {
2410                                         buffer_free( sql_buf );
2411                                         return NULL;
2412                                 }
2413                         } else if ( !strcasecmp("-not",search_itr->key) ) {
2414                                 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2415                                 if( subpred ) {
2416                                         buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2417                                         free( subpred );
2418                                 } else {
2419                                         buffer_free( sql_buf );
2420                                         return NULL;
2421                                 }
2422                         } else if ( !strcasecmp("-exists",search_itr->key) ) {
2423                 char* subpred = SELECT(
2424                     ctx,
2425                     jsonObjectGetKey( node, "select" ),
2426                     jsonObjectGetKey( node, "from" ),
2427                     jsonObjectGetKey( node, "where" ),
2428                     jsonObjectGetKey( node, "having" ),
2429                     jsonObjectGetKey( node, "order_by" ),
2430                     jsonObjectGetKey( node, "limit" ),
2431                     jsonObjectGetKey( node, "offset" ),
2432                     SUBSELECT
2433                 );
2434
2435                 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2436                 free(subpred);
2437             } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2438                 char* subpred = SELECT(
2439                     ctx,
2440                     jsonObjectGetKey( node, "select" ),
2441                     jsonObjectGetKey( node, "from" ),
2442                     jsonObjectGetKey( node, "where" ),
2443                     jsonObjectGetKey( node, "having" ),
2444                     jsonObjectGetKey( node, "order_by" ),
2445                     jsonObjectGetKey( node, "limit" ),
2446                     jsonObjectGetKey( node, "offset" ),
2447                     SUBSELECT
2448                 );
2449
2450                 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2451                 free(subpred);
2452             } else {
2453
2454                 char* class = osrfHashGet(meta, "classname");
2455                 osrfHash* fields = osrfHashGet(meta, "fields");
2456                 osrfHash* field = osrfHashGet( fields, search_itr->key );
2457
2458
2459                 if (!field) {
2460                     char* table = getSourceDefinition(meta);
2461                                         if( !table )
2462                                                 table = strdup( "(?)" );
2463                     osrfLogError(
2464                         OSRF_LOG_MARK,
2465                         "%s: Attempt to reference non-existent column %s on %s (%s)",
2466                         MODULENAME,
2467                         search_itr->key,
2468                         table,
2469                         class
2470                     );
2471                     buffer_free(sql_buf);
2472                     free(table);
2473                                         jsonIteratorFree(search_itr);
2474                                         return NULL;
2475                 }
2476
2477                 char* subpred = searchPredicate( class, field, node, ctx );
2478                 buffer_add( sql_buf, subpred );
2479                 free(subpred);
2480             }
2481         }
2482             jsonIteratorFree(search_itr);
2483
2484     } else {
2485         // ERROR ... only hash and array allowed at this level
2486         char* predicate_string = jsonObjectToJSON( search_hash );
2487         osrfLogError(
2488             OSRF_LOG_MARK,
2489             "%s: Invalid predicate structure: %s",
2490             MODULENAME,
2491             predicate_string
2492         );
2493         buffer_free(sql_buf);
2494         free(predicate_string);
2495         return NULL;
2496     }
2497
2498         return buffer_release(sql_buf);
2499 }
2500
2501 char* SELECT (
2502                 /* method context */ osrfMethodContext* ctx,
2503                 
2504                 /* SELECT   */ jsonObject* selhash,
2505                 /* FROM     */ jsonObject* join_hash,
2506                 /* WHERE    */ jsonObject* search_hash,
2507                 /* HAVING   */ jsonObject* having_hash,
2508                 /* ORDER BY */ jsonObject* order_hash,
2509                 /* LIMIT    */ jsonObject* limit,
2510                 /* OFFSET   */ jsonObject* offset,
2511                 /* flags    */ int flags
2512 ) {
2513         const char* locale = osrf_message_get_last_locale();
2514
2515         // in case we don't get a select list
2516         jsonObject* defaultselhash = NULL;
2517
2518         // general tmp objects
2519         const jsonObject* tmp_const;
2520         jsonObject* selclass = NULL;
2521         jsonObject* selfield = NULL;
2522         jsonObject* snode = NULL;
2523         jsonObject* onode = NULL;
2524
2525         char* string = NULL;
2526         int from_function = 0;
2527         int first = 1;
2528         int gfirst = 1;
2529         //int hfirst = 1;
2530
2531         // the core search class
2532         char* core_class = NULL;
2533
2534         // metadata about the core search class
2535         osrfHash* core_meta = NULL;
2536
2537         // punt if there's no core class
2538         if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2539                 osrfLogError(
2540                         OSRF_LOG_MARK,
2541                         "%s: FROM clause is missing or empty",
2542                         MODULENAME
2543                 );
2544                 if( ctx )
2545                         osrfAppSessionStatus(
2546                                 ctx->session,
2547                                 OSRF_STATUS_INTERNALSERVERERROR,
2548                                 "osrfMethodException",
2549                                 ctx->request,
2550                                 "FROM clause is missing or empty in JSON query"
2551                         );
2552                 return NULL;
2553         }
2554
2555         // get the core class -- the only key of the top level FROM clause, or a string
2556         if (join_hash->type == JSON_HASH) {
2557                 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2558                 snode = jsonIteratorNext( tmp_itr );
2559                 
2560                 core_class = strdup( tmp_itr->key );
2561                 join_hash = snode;
2562                 
2563                 jsonObject* extra = jsonIteratorNext( tmp_itr );
2564
2565                 jsonIteratorFree( tmp_itr );
2566                 snode = NULL;
2567
2568                 // There shouldn't be more than one entry in join_hash
2569                 if( extra ) {
2570                         osrfLogError(
2571                                 OSRF_LOG_MARK,
2572                                 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2573                                 MODULENAME
2574                         );
2575                         if( ctx )
2576                                 osrfAppSessionStatus(
2577                                         ctx->session,
2578                                         OSRF_STATUS_INTERNALSERVERERROR,
2579                                         "osrfMethodException",
2580                                         ctx->request,
2581                                         "Malformed FROM clause in JSON query"
2582                                 );
2583                         free( core_class );
2584                         return NULL;    // Malformed join_hash; extra entry
2585                 }
2586         } else if (join_hash->type == JSON_ARRAY) {
2587                 from_function = 1;
2588                 core_class = jsonObjectToSimpleString( jsonObjectGetIndex(join_hash, 0) );
2589                 selhash = NULL;
2590
2591         } else if (join_hash->type == JSON_STRING) {
2592                 core_class = jsonObjectToSimpleString( join_hash );
2593                 join_hash = NULL;
2594         }
2595         else {
2596                 osrfLogError(
2597                         OSRF_LOG_MARK,
2598                         "%s: FROM clause is unexpected JSON type: %s",
2599                         MODULENAME,
2600                         json_type( join_hash->type )
2601                 );
2602                 if( ctx )
2603                         osrfAppSessionStatus(
2604                                 ctx->session,
2605                                 OSRF_STATUS_INTERNALSERVERERROR,
2606                                 "osrfMethodException",
2607                                 ctx->request,
2608                                 "Ill-formed FROM clause in JSON query"
2609                         );
2610                 free( core_class );
2611                 return NULL;
2612         }
2613
2614         if (!from_function) {
2615                 // Get the IDL class definition for the core class
2616                 core_meta = osrfHashGet( oilsIDL(), core_class );
2617                 if( !core_meta ) {    // Didn't find it?
2618                         osrfLogError(
2619                                 OSRF_LOG_MARK,
2620                                 "%s: SELECT clause references undefined class: \"%s\"",
2621                                 MODULENAME,
2622                                 core_class
2623                         );
2624                         if( ctx )
2625                                 osrfAppSessionStatus(
2626                                         ctx->session,
2627                                         OSRF_STATUS_INTERNALSERVERERROR,
2628                                         "osrfMethodException",
2629                                         ctx->request,
2630                                         "SELECT clause references undefined class in JSON query"
2631                                 );
2632                         free( core_class );
2633                         return NULL;
2634                 }
2635
2636                 // Make sure the class isn't virtual
2637                 if( str_is_true( osrfHashGet( core_meta, "virtual" ) ) ) {
2638                         osrfLogError(
2639                                 OSRF_LOG_MARK,
2640                                 "%s: Core class is virtual: \"%s\"",
2641                                 MODULENAME,
2642                                 core_class
2643                         );
2644                         if( ctx )
2645                                 osrfAppSessionStatus(
2646                                         ctx->session,
2647                                         OSRF_STATUS_INTERNALSERVERERROR,
2648                                         "osrfMethodException",
2649                                         ctx->request,
2650                                         "FROM clause references virtual class in JSON query"
2651                                 );
2652                         free( core_class );
2653                         return NULL;
2654                 }
2655         }
2656
2657         // if the select list is empty, or the core class field list is '*',
2658         // build the default select list ...
2659         if (!selhash) {
2660                 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
2661                 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2662         } else if( selhash->type != JSON_HASH ) {
2663                 osrfLogError(
2664                         OSRF_LOG_MARK,
2665                         "%s: Expected JSON_HASH for SELECT clause; found %s",
2666                         MODULENAME,
2667                         json_type( selhash->type )
2668                 );
2669
2670                 if (ctx)
2671                         osrfAppSessionStatus(
2672                                 ctx->session,
2673                                 OSRF_STATUS_INTERNALSERVERERROR,
2674                                 "osrfMethodException",
2675                                 ctx->request,
2676                                 "Malformed SELECT clause in JSON query"
2677                         );
2678                 free( core_class );
2679                 return NULL;
2680         } else if ( (tmp_const = jsonObjectGetKeyConst( selhash, core_class )) && tmp_const->type == JSON_STRING ) {
2681                 const char* _x = jsonObjectGetString( tmp_const );
2682                 if (!strncmp( "*", _x, 1 )) {
2683                         jsonObjectRemoveKey( selhash, core_class );
2684                         jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2685                 }
2686         }
2687
2688         // the query buffer
2689         growing_buffer* sql_buf = buffer_init(128);
2690
2691         // temp buffer for the SELECT list
2692         growing_buffer* select_buf = buffer_init(128);
2693         growing_buffer* order_buf = buffer_init(128);
2694         growing_buffer* group_buf = buffer_init(128);
2695         growing_buffer* having_buf = buffer_init(128);
2696
2697         int aggregate_found = 0;     // boolean
2698
2699         // Build a select list
2700         if(from_function)   // From a function we select everything
2701                 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
2702         else {
2703
2704                 // If we need to build a default list, prepare to do so
2705                 jsonObject* _tmp = jsonObjectGetKey( selhash, core_class );
2706                 if ( _tmp && !_tmp->size ) {
2707
2708                         osrfHash* core_fields = osrfHashGet( core_meta, "fields" );
2709
2710                         osrfHashIterator* field_itr = osrfNewHashIterator( core_fields );
2711                         osrfHash* field_def;
2712                         while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2713                                 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2714                                         // This field is not virtual, so add it to the list
2715                                         jsonObjectPush( _tmp, jsonNewObject( osrfHashIteratorKey( field_itr ) ) );
2716                                 }
2717                         }
2718                         osrfHashIteratorFree( field_itr );
2719                 }
2720
2721                 // Now build the actual select list
2722             int sel_pos = 1;
2723             first = 1;
2724             gfirst = 1;
2725             jsonIterator* selclass_itr = jsonNewIterator( selhash );
2726             while ( (selclass = jsonIteratorNext( selclass_itr )) ) {    // For each class
2727
2728                     // Make sure the class is defined in the IDL
2729                         const char* cname = selclass_itr->key;
2730                         osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
2731                     if (!idlClass) {
2732                                 osrfLogError(
2733                                         OSRF_LOG_MARK,
2734                                         "%s: Selected class \"%s\" not defined in IDL",
2735                                         MODULENAME,
2736                                         cname
2737                                 );
2738
2739                                 if (ctx)
2740                                         osrfAppSessionStatus(
2741                                                 ctx->session,
2742                                                 OSRF_STATUS_INTERNALSERVERERROR,
2743                                                 "osrfMethodException",
2744                                                 ctx->request,
2745                                                 "Selected class is not defined"
2746                                         );
2747                                 jsonIteratorFree( selclass_itr );
2748                                 buffer_free( sql_buf );
2749                                 buffer_free( select_buf );
2750                                 buffer_free( order_buf );
2751                                 buffer_free( group_buf );
2752                                 buffer_free( having_buf );
2753                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
2754                                 free( core_class );
2755                                 return NULL;
2756                         }
2757
2758                     // Make sure the target relation is in the join tree.
2759                         
2760                         // At this point join_hash is a step down from the join_hash we
2761                         // received as a parameter.  If the original was a JSON_STRING,
2762                         // then json_hash is now NULL.  If the original was a JSON_HASH,
2763                         // then json_hash is now the first (and only) entry in it,
2764                         // denoting the core class.  We've already excluded the
2765                         // possibility that the original was a JSON_ARRAY, because in
2766                         // that case from_function would be non-NULL, and we wouldn't
2767                         // be here.
2768
2769                         int class_in_from_clause;    // boolean
2770                         
2771                     if ( ! strcmp( core_class, cname ))
2772                                 // This is the core class -- no problem
2773                                 class_in_from_clause = 1;
2774                         else {
2775                                 if (!join_hash) 
2776                                         // There's only one class in the FROM clause, and this isn't it
2777                                         class_in_from_clause = 0;
2778                                 else if (join_hash->type == JSON_STRING) {
2779                                         // There's only one class in the FROM clause
2780                                         const char* str = jsonObjectGetString(join_hash);
2781                                         if ( strcmp( str, cname ) )
2782                                                 class_in_from_clause = 0;    // This isn't it
2783                                         else 
2784                                                 class_in_from_clause = 1;    // This is it
2785                                 } else {
2786                                         jsonObject* found = jsonObjectFindPath(join_hash, "//%s", cname);
2787                                         if ( 0 == found->size )
2788                                                 class_in_from_clause = 0;   // Nowhere in the join tree
2789                                         else
2790                                                 class_in_from_clause = 1;   // Found it
2791                                         jsonObjectFree( found );
2792                                 }
2793                         }
2794
2795                         // If the class isn't in the FROM clause, bail out
2796                         if( ! class_in_from_clause ) {
2797                                 osrfLogError(
2798                                         OSRF_LOG_MARK,
2799                                         "%s: SELECT clause references class not in FROM clause: \"%s\"",
2800                                         MODULENAME,
2801                                         cname
2802                                 );
2803                                 if( ctx )
2804                                         osrfAppSessionStatus(
2805                                                 ctx->session,
2806                                                 OSRF_STATUS_INTERNALSERVERERROR,
2807                                                 "osrfMethodException",
2808                                                 ctx->request,
2809                                                 "Selected class not in FROM clause in JSON query"
2810                                         );
2811                                 jsonIteratorFree( selclass_itr );
2812                                 buffer_free( sql_buf );
2813                                 buffer_free( select_buf );
2814                                 buffer_free( order_buf );
2815                                 buffer_free( group_buf );
2816                                 buffer_free( having_buf );
2817                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
2818                                 free( core_class );
2819                                 return NULL;
2820                         }
2821
2822                         // Look up some attributes of the current class, so that we 
2823                         // don't have to look them up again for each field
2824                         osrfHash* class_field_set = osrfHashGet( idlClass, "fields" );
2825                         const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
2826                         const char* class_tname = osrfHashGet( idlClass, "tablename" );
2827                         
2828                     // stitch together the column list ...
2829                     jsonIterator* select_itr = jsonNewIterator( selclass );
2830                     while ( (selfield = jsonIteratorNext( select_itr )) ) {   // for each SELECT column
2831
2832                                 // If we need a separator comma, add one
2833                                 if (first) {
2834                                         first = 0;
2835                                 } else {
2836                                         OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
2837                                 }
2838
2839                                 // ... if it's a string, just toss it on the pile
2840                                 if (selfield->type == JSON_STRING) {
2841
2842                                         // Look up the field in the IDL
2843                                         const char* col_name = jsonObjectGetString( selfield );
2844                                         osrfHash* field_def = osrfHashGet( class_field_set, col_name );
2845                                         if ( !field_def ) {
2846                                                 // No such field in current class
2847                                                 osrfLogError(
2848                                                         OSRF_LOG_MARK,
2849                                                         "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
2850                                                         MODULENAME,
2851                                                         col_name,
2852                                                         cname
2853                                                 );
2854                                                 if( ctx )
2855                                                         osrfAppSessionStatus(
2856                                                                 ctx->session,
2857                                                                 OSRF_STATUS_INTERNALSERVERERROR,
2858                                                                 "osrfMethodException",
2859                                                                 ctx->request,
2860                                                                 "Selected column not defined in JSON query"
2861                                                         );
2862                                                 jsonIteratorFree( select_itr );
2863                                                 jsonIteratorFree( selclass_itr );
2864                                                 buffer_free( sql_buf );
2865                                                 buffer_free( select_buf );
2866                                                 buffer_free( order_buf );
2867                                                 buffer_free( group_buf );
2868                                                 buffer_free( having_buf );
2869                                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
2870                                                 free( core_class );
2871                                                 return NULL;
2872                                         } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2873                                                 // Virtual field not allowed
2874                                                 osrfLogError(
2875                                                         OSRF_LOG_MARK,
2876                                                         "%s: Selected column \"%s\" for class \"%s\" is virtual",
2877                                                         MODULENAME,
2878                                                         col_name,
2879                                                         cname
2880                                                 );
2881                                                 if( ctx )
2882                                                         osrfAppSessionStatus(
2883                                                                 ctx->session,
2884                                                                 OSRF_STATUS_INTERNALSERVERERROR,
2885                                                                 "osrfMethodException",
2886                                                                 ctx->request,
2887                                                                 "Selected column may not be virtual in JSON query"
2888                                                         );
2889                                                 jsonIteratorFree( select_itr );
2890                                                 jsonIteratorFree( selclass_itr );
2891                                                 buffer_free( sql_buf );
2892                                                 buffer_free( select_buf );
2893                                                 buffer_free( order_buf );
2894                                                 buffer_free( group_buf );
2895                                                 buffer_free( having_buf );
2896                                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
2897                                                 free( core_class );
2898                                                 return NULL;
2899                                         }
2900
2901                                         if (locale) {
2902                                                 const char* i18n;
2903                                                 if (flags & DISABLE_I18N)
2904                                                         i18n = NULL;
2905                                                 else
2906                                                         i18n = osrfHashGet(field_def, "i18n");
2907
2908                                                 if( str_is_true( i18n ) ) {
2909                             buffer_fadd( select_buf,
2910                                                                 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
2911                                                                 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
2912                         } else {
2913                                             buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
2914                         }
2915                     } else {
2916                                         buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
2917                     }
2918                                         
2919                                 // ... but it could be an object, in which case we check for a Field Transform
2920                                 } else if (selfield->type == JSON_HASH) {
2921
2922                                         const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
2923
2924                                         // Get the field definition from the IDL
2925                                         osrfHash* field_def = osrfHashGet( class_field_set, col_name );
2926                                         if ( !field_def ) {
2927                                                 // No such field in current class
2928                                                 osrfLogError(
2929                                                         OSRF_LOG_MARK,
2930                                                         "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
2931                                                         MODULENAME,
2932                                                         col_name,
2933                                                         cname
2934                                                 );
2935                                                 if( ctx )
2936                                                         osrfAppSessionStatus(
2937                                                                 ctx->session,
2938                                                                 OSRF_STATUS_INTERNALSERVERERROR,
2939                                                                 "osrfMethodException",
2940                                                                 ctx->request,
2941                                                                 "Selected column is not defined in JSON query"
2942                                                         );
2943                                                 jsonIteratorFree( select_itr );
2944                                                 jsonIteratorFree( selclass_itr );
2945                                                 buffer_free( sql_buf );
2946                                                 buffer_free( select_buf );
2947                                                 buffer_free( order_buf );
2948                                                 buffer_free( group_buf );
2949                                                 buffer_free( having_buf );
2950                                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
2951                                                 free( core_class );
2952                                                 return NULL;
2953                                         } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2954                                                 // No such field in current class
2955                                                 osrfLogError(
2956                                                         OSRF_LOG_MARK,
2957                                                         "%s: Selected column \"%s\" is virtual for class \"%s\"",
2958                                                         MODULENAME,
2959                                                         col_name,
2960                                                         cname
2961                                                 );
2962                                                 if( ctx )
2963                                                         osrfAppSessionStatus(
2964                                                                 ctx->session,
2965                                                                 OSRF_STATUS_INTERNALSERVERERROR,
2966                                                                 "osrfMethodException",
2967                                                                 ctx->request,
2968                                                                 "Selected column is virtual in JSON query"
2969                                                         );
2970                                                 jsonIteratorFree( select_itr );
2971                                                 jsonIteratorFree( selclass_itr );
2972                                                 buffer_free( sql_buf );
2973                                                 buffer_free( select_buf );
2974                                                 buffer_free( order_buf );
2975                                                 buffer_free( group_buf );
2976                                                 buffer_free( having_buf );
2977                                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
2978                                                 free( core_class );
2979                                                 return NULL;
2980                                         }
2981
2982                                         // Decide what to use as a column alias
2983                                         const char* _alias;
2984                                         if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
2985                                                 _alias = jsonObjectGetString( tmp_const );
2986                                         } else {         // Use field name as the alias
2987                                                 _alias = col_name;
2988                                         }
2989
2990                                         if (jsonObjectGetKeyConst( selfield, "transform" )) {
2991                                                 char* transform_str = searchFieldTransform(cname, field_def, selfield);
2992                                                 if( transform_str ) {
2993                                                         buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
2994                                                         free(transform_str);
2995                                                 } else {
2996                                                         if( ctx )
2997                                                                 osrfAppSessionStatus(
2998                                                                         ctx->session,
2999                                                                         OSRF_STATUS_INTERNALSERVERERROR,
3000                                                                         "osrfMethodException",
3001                                                                         ctx->request,
3002                                                                         "Unable to generate transform function in JSON query"
3003                                                                 );
3004                                                         jsonIteratorFree( select_itr );
3005                                                         jsonIteratorFree( selclass_itr );
3006                                                         buffer_free( sql_buf );
3007                                                         buffer_free( select_buf );
3008                                                         buffer_free( order_buf );
3009                                                         buffer_free( group_buf );
3010                                                         buffer_free( having_buf );
3011                                                         if( defaultselhash ) jsonObjectFree( defaultselhash );
3012                                                         free( core_class );
3013                                                         return NULL;
3014                                                 }
3015                                         } else {
3016
3017                                                 if (locale) {
3018                                                         const char* i18n;
3019                                                         if (flags & DISABLE_I18N)
3020                                                                 i18n = NULL;
3021                                                         else
3022                                                                 i18n = osrfHashGet(field_def, "i18n");
3023
3024                                                         if( str_is_true( i18n ) ) {
3025                                                                 buffer_fadd( select_buf,
3026                                                                         " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3027                                                                         class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3028                                                         } else {
3029                                                                 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3030                                                         }
3031                                                 } else {
3032                                                         buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3033                                                 }
3034                                         }
3035                                 }
3036                                 else {
3037                                         osrfLogError(
3038                                                 OSRF_LOG_MARK,
3039                                                 "%s: Selected item is unexpected JSON type: %s",
3040                                                 MODULENAME,
3041                                                 json_type( selfield->type )
3042                                         );
3043                                         if( ctx )
3044                                                 osrfAppSessionStatus(
3045                                                         ctx->session,
3046                                                         OSRF_STATUS_INTERNALSERVERERROR,
3047                                                         "osrfMethodException",
3048                                                         ctx->request,
3049                                                         "Ill-formed SELECT item in JSON query"
3050                                                 );
3051                                         jsonIteratorFree( select_itr );
3052                                         jsonIteratorFree( selclass_itr );
3053                                         buffer_free( sql_buf );
3054                                         buffer_free( select_buf );
3055                                         buffer_free( order_buf );
3056                                         buffer_free( group_buf );
3057                                         buffer_free( having_buf );
3058                                         if( defaultselhash ) jsonObjectFree( defaultselhash );
3059                                         free( core_class );
3060                                         return NULL;
3061                                 }
3062
3063                                 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3064                                 if( obj_is_true( agg_obj ) )
3065                                         aggregate_found = 1;
3066                                 else {
3067                                         // Append a comma (except for the first one)
3068                                         // and add the column to a GROUP BY clause
3069                                         if (gfirst)
3070                                                 gfirst = 0;
3071                                         else
3072                                                 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3073
3074                                         buffer_fadd(group_buf, " %d", sel_pos);
3075                                 }
3076
3077 #if 0
3078                             if (is_agg->size || (flags & SELECT_DISTINCT)) {
3079
3080                                         const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3081                                     if ( ! obj_is_true( aggregate_obj ) ) {
3082                                             if (gfirst) {
3083                                                     gfirst = 0;
3084                                             } else {
3085                                                         OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3086                                             }
3087
3088                                             buffer_fadd(group_buf, " %d", sel_pos);
3089
3090                                         /*
3091                                     } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3092                                             if (gfirst) {
3093                                                     gfirst = 0;
3094                                             } else {
3095                                                         OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3096                                             }
3097
3098                                             _column = searchFieldTransform(cname, field, selfield);
3099                                                 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3100                                                 OSRF_BUFFER_ADD(group_buf, _column);
3101                                             _column = searchFieldTransform(cname, field, selfield);
3102                                         */
3103                                     }
3104                             }
3105 #endif
3106
3107                             sel_pos++;
3108                     } // end while -- iterating across SELECT columns
3109
3110             jsonIteratorFree(select_itr);
3111             } // end while -- iterating across classes
3112
3113         jsonIteratorFree(selclass_itr);
3114     }
3115
3116
3117         char* col_list = buffer_release(select_buf);
3118         char* table = NULL;
3119         if (from_function) table = searchValueTransform(join_hash);
3120         else table = getSourceDefinition(core_meta);
3121         
3122         if( !table ) {
3123                 if (ctx)
3124                         osrfAppSessionStatus(
3125                                 ctx->session,
3126                                 OSRF_STATUS_INTERNALSERVERERROR,
3127                                 "osrfMethodException",
3128                                 ctx->request,
3129                                 "Unable to identify table for core class"
3130                         );
3131                 free( col_list );
3132                 buffer_free( sql_buf );
3133                 buffer_free( order_buf );
3134                 buffer_free( group_buf );
3135                 buffer_free( having_buf );
3136                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3137                 free( core_class );
3138                 return NULL;    
3139         }
3140         
3141         // Put it all together
3142         buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3143         free(col_list);
3144         free(table);
3145
3146     if (!from_function) {
3147             // Now, walk the join tree and add that clause
3148             if ( join_hash ) {
3149                     char* join_clause = searchJOIN( join_hash, core_meta );
3150                         if( join_clause ) {
3151                                 buffer_add(sql_buf, join_clause);
3152                         free(join_clause);
3153                         } else {
3154                                 if (ctx)
3155                                         osrfAppSessionStatus(
3156                                                 ctx->session,
3157                                                 OSRF_STATUS_INTERNALSERVERERROR,
3158                                                 "osrfMethodException",
3159                                                 ctx->request,
3160                                                 "Unable to construct JOIN clause(s)"
3161                                         );
3162                                 buffer_free( sql_buf );
3163                                 buffer_free( order_buf );
3164                                 buffer_free( group_buf );
3165                                 buffer_free( having_buf );
3166                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3167                                 free( core_class );
3168                                 return NULL;
3169                         }
3170             }
3171
3172                 // Build a WHERE clause, if there is one
3173             if ( search_hash ) {
3174                     buffer_add(sql_buf, " WHERE ");
3175
3176                     // and it's on the WHERE clause
3177                     char* pred = searchWHERE( search_hash, core_meta, AND_OP_JOIN, ctx );
3178
3179                     if (pred) {
3180                                 buffer_add(sql_buf, pred);
3181                                 free(pred);
3182                         } else {
3183                                 if (ctx) {
3184                                 osrfAppSessionStatus(
3185                                         ctx->session,
3186                                         OSRF_STATUS_INTERNALSERVERERROR,
3187                                         "osrfMethodException",
3188                                         ctx->request,
3189                                         "Severe query error in WHERE predicate -- see error log for more details"
3190                                 );
3191                             }
3192                             free(core_class);
3193                             buffer_free(having_buf);
3194                             buffer_free(group_buf);
3195                             buffer_free(order_buf);
3196                             buffer_free(sql_buf);
3197                             if (defaultselhash) jsonObjectFree(defaultselhash);
3198                             return NULL;
3199                     }
3200         }
3201
3202                 // Build a HAVING clause, if there is one
3203             if ( having_hash ) {
3204                     buffer_add(sql_buf, " HAVING ");
3205
3206                     // and it's on the the WHERE clause
3207                     char* pred = searchWHERE( having_hash, core_meta, AND_OP_JOIN, ctx );
3208
3209                     if (pred) {
3210                                 buffer_add(sql_buf, pred);
3211                                 free(pred);
3212                         } else {
3213                                 if (ctx) {
3214                                 osrfAppSessionStatus(
3215                                         ctx->session,
3216                                         OSRF_STATUS_INTERNALSERVERERROR,
3217                                         "osrfMethodException",
3218                                         ctx->request,
3219                                         "Severe query error in HAVING predicate -- see error log for more details"
3220                                 );
3221                             }
3222                             free(core_class);
3223                             buffer_free(having_buf);
3224                             buffer_free(group_buf);
3225                             buffer_free(order_buf);
3226                             buffer_free(sql_buf);
3227                             if (defaultselhash) jsonObjectFree(defaultselhash);
3228                             return NULL;
3229                     }
3230             }
3231
3232                 // Build an ORDER BY clause, if there is one
3233             first = 1;
3234             jsonIterator* class_itr = jsonNewIterator( order_hash );
3235             while ( (snode = jsonIteratorNext( class_itr )) ) {
3236
3237                     if (!jsonObjectGetKeyConst(selhash,class_itr->key))
3238                             continue;
3239
3240                     if ( snode->type == JSON_HASH ) {
3241
3242                         jsonIterator* order_itr = jsonNewIterator( snode );
3243                             while ( (onode = jsonIteratorNext( order_itr )) ) {
3244
3245                                         if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ))
3246                                                 continue;
3247
3248                                         const char* direction = NULL;
3249                                         if ( onode->type == JSON_HASH ) {
3250                                                 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3251                                                     string = searchFieldTransform(
3252                                                             class_itr->key,
3253                                                             oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ),
3254                                                             onode
3255                                                     );
3256                                             } else {
3257                                                     growing_buffer* field_buf = buffer_init(16);
3258                                                     buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3259                                                     string = buffer_release(field_buf);
3260                                             }
3261
3262                                                 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3263                                                         const char* dir = jsonObjectGetString(tmp_const);
3264                                                         if (!strncasecmp(dir, "d", 1)) {
3265                                                                 direction = " DESC";
3266                                                         } else {
3267                                                                 direction = " ASC";
3268                                                         }
3269                                                 }
3270
3271                                         } else {
3272                                                 string = strdup(order_itr->key);
3273                                                 const char* dir = jsonObjectGetString(onode);
3274                                                 if (!strncasecmp(dir, "d", 1)) {
3275                                                         direction = " DESC";
3276                                                 } else {
3277                                                         direction = " ASC";
3278                                                 }
3279                                         }
3280
3281                                     if (first) {
3282                                             first = 0;
3283                                     } else {
3284                                             buffer_add(order_buf, ", ");
3285                                     }
3286
3287                                     buffer_add(order_buf, string);
3288                                     free(string);
3289
3290                                     if (direction) {
3291                                             buffer_add(order_buf, direction);
3292                                     }
3293
3294                             } // end while
3295                 // jsonIteratorFree(order_itr);
3296
3297                     } else if ( snode->type == JSON_ARRAY ) {
3298
3299                         jsonIterator* order_itr = jsonNewIterator( snode );
3300                             while ( (onode = jsonIteratorNext( order_itr )) ) {
3301
3302                                         const char* _f = jsonObjectGetString( onode );
3303
3304                                         if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, _f))
3305                                                 continue;
3306
3307                                         if (first) {
3308                                                 first = 0;
3309                                         } else {
3310                                                 buffer_add(order_buf, ", ");
3311                                         }
3312
3313                                         buffer_add(order_buf, _f);
3314
3315                                 } // end while
3316                 // jsonIteratorFree(order_itr);
3317
3318
3319                     // IT'S THE OOOOOOOOOOOLD STYLE!
3320                     } else {
3321                             osrfLogError(OSRF_LOG_MARK, "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3322                             if (ctx) {
3323                                 osrfAppSessionStatus(
3324                                         ctx->session,
3325                                         OSRF_STATUS_INTERNALSERVERERROR,
3326                                         "osrfMethodException",
3327                                         ctx->request,
3328                                         "Severe query error -- see error log for more details"
3329                                 );
3330                             }
3331
3332                             free(core_class);
3333                             buffer_free(having_buf);
3334                             buffer_free(group_buf);
3335                             buffer_free(order_buf);
3336                             buffer_free(sql_buf);
3337                             if (defaultselhash) jsonObjectFree(defaultselhash);
3338                             jsonIteratorFree(class_itr);
3339                             return NULL;
3340                     }
3341
3342             } // end while
3343                 // jsonIteratorFree(class_itr);
3344         }
3345
3346
3347         string = buffer_release(group_buf);
3348
3349         if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
3350                 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3351                 OSRF_BUFFER_ADD( sql_buf, string );
3352         }
3353
3354         free(string);
3355
3356         string = buffer_release(having_buf);
3357  
3358         if ( *string ) {
3359                 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3360                 OSRF_BUFFER_ADD( sql_buf, string );
3361         }
3362
3363         free(string);
3364
3365         string = buffer_release(order_buf);
3366
3367         if ( *string ) {
3368                 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3369                 OSRF_BUFFER_ADD( sql_buf, string );
3370         }
3371
3372         free(string);
3373
3374         if ( limit ){
3375                 const char* str = jsonObjectGetString(limit);
3376                 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
3377         }
3378
3379         if (offset) {
3380                 const char* str = jsonObjectGetString(offset);
3381                 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
3382         }
3383
3384         if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3385
3386         free(core_class);
3387         if (defaultselhash) jsonObjectFree(defaultselhash);
3388
3389         return buffer_release(sql_buf);
3390
3391 }
3392
3393 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
3394
3395         const char* locale = osrf_message_get_last_locale();
3396
3397         osrfHash* fields = osrfHashGet(meta, "fields");
3398         char* core_class = osrfHashGet(meta, "classname");
3399
3400         const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
3401
3402         jsonObject* node = NULL;
3403         jsonObject* snode = NULL;
3404         jsonObject* onode = NULL;
3405         const jsonObject* _tmp = NULL;
3406         jsonObject* selhash = NULL;
3407         jsonObject* defaultselhash = NULL;
3408
3409         growing_buffer* sql_buf = buffer_init(128);
3410         growing_buffer* select_buf = buffer_init(128);
3411
3412         if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
3413                 defaultselhash = jsonNewObjectType(JSON_HASH);
3414                 selhash = defaultselhash;
3415         }
3416         
3417         if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
3418                 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
3419                 jsonObject* flist = jsonObjectGetKey( selhash, core_class );
3420                 
3421                 int i = 0;
3422                 char* field;
3423
3424                 osrfStringArray* keys = osrfHashKeys( fields );
3425                 while ( (field = osrfStringArrayGetString(keys, i++)) ) {
3426                         if( ! str_is_true( osrfHashGet( osrfHashGet( fields, field ), "virtual" ) ) )
3427                                 jsonObjectPush( flist, jsonNewObject( field ) );
3428                 }
3429                 osrfStringArrayFree(keys);
3430         }
3431
3432         int first = 1;
3433         jsonIterator* class_itr = jsonNewIterator( selhash );
3434         while ( (snode = jsonIteratorNext( class_itr )) ) {
3435
3436                 char* cname = class_itr->key;
3437                 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
3438                 if (!idlClass) continue;
3439
3440                 if (strcmp(core_class,class_itr->key)) {
3441                         if (!join_hash) continue;
3442
3443                         jsonObject* found =  jsonObjectFindPath(join_hash, "//%s", class_itr->key);
3444                         if (!found->size) {
3445                                 jsonObjectFree(found);
3446                                 continue;
3447                         }
3448
3449                         jsonObjectFree(found);
3450                 }
3451
3452                 jsonIterator* select_itr = jsonNewIterator( snode );
3453                 while ( (node = jsonIteratorNext( select_itr )) ) {
3454                         const char* item_str = jsonObjectGetString( node );
3455                         osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
3456                         char* fname = osrfHashGet(field, "name");
3457
3458                         if (!field) continue;
3459
3460                         if (first) {
3461                                 first = 0;
3462                         } else {
3463                                 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
3464                         }
3465
3466             if (locale) {
3467                         const char* i18n;
3468                                 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
3469                                 if ( obj_is_true( no_i18n_obj ) )    // Suppress internationalization?
3470                                         i18n = NULL;
3471                                 else
3472                                         i18n = osrfHashGet(field, "i18n");
3473
3474                                 if( str_is_true( i18n ) ) {
3475                         char* pkey = osrfHashGet(idlClass, "primarykey");
3476                         char* tname = osrfHashGet(idlClass, "tablename");
3477
3478                     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);
3479                 } else {
3480                                 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3481                 }
3482             } else {
3483                             buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3484             }
3485                 }
3486
3487         jsonIteratorFree(select_itr);
3488         }
3489
3490     jsonIteratorFree(class_itr);
3491
3492         char* col_list = buffer_release(select_buf);
3493         char* table = getSourceDefinition(meta);
3494         if( !table )
3495                 table = strdup( "(null)" );
3496
3497         buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
3498         free(col_list);
3499         free(table);
3500
3501         if ( join_hash ) {
3502                 char* join_clause = searchJOIN( join_hash, meta );
3503                 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
3504                 OSRF_BUFFER_ADD(sql_buf, join_clause);
3505                 free(join_clause);
3506         }
3507
3508         osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL =  %s",
3509                                  MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
3510
3511         buffer_add(sql_buf, " WHERE ");
3512
3513         char* pred = searchWHERE( search_hash, meta, AND_OP_JOIN, ctx );
3514         if (!pred) {
3515                 osrfAppSessionStatus(
3516                         ctx->session,
3517                         OSRF_STATUS_INTERNALSERVERERROR,
3518                                 "osrfMethodException",
3519                                 ctx->request,
3520                                 "Severe query error -- see error log for more details"
3521                         );
3522                 buffer_free(sql_buf);
3523                 if(defaultselhash) jsonObjectFree(defaultselhash);
3524                 return NULL;
3525         } else {
3526                 buffer_add(sql_buf, pred);
3527                 free(pred);
3528         }
3529
3530         if (order_hash) {
3531                 char* string = NULL;
3532                 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
3533
3534                         growing_buffer* order_buf = buffer_init(128);
3535
3536                         first = 1;
3537                         jsonIterator* class_itr = jsonNewIterator( _tmp );
3538                         while ( (snode = jsonIteratorNext( class_itr )) ) {
3539
3540                                 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
3541                                         continue;
3542
3543                                 if ( snode->type == JSON_HASH ) {
3544
3545                                         jsonIterator* order_itr = jsonNewIterator( snode );
3546                                         while ( (onode = jsonIteratorNext( order_itr )) ) {
3547
3548                                                 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ))
3549                                                         continue;
3550
3551                                                 char* direction = NULL;
3552                                                 if ( onode->type == JSON_HASH ) {
3553                                                         if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3554                                                                 string = searchFieldTransform(
3555                                                                         class_itr->key,
3556                                                                         oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ),
3557                                                                         onode
3558                                                                 );
3559                                                         } else {
3560                                                                 growing_buffer* field_buf = buffer_init(16);
3561                                                                 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3562                                                                 string = buffer_release(field_buf);
3563                                                         }
3564
3565                                                         if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
3566                                                                 const char* dir = jsonObjectGetString(_tmp);
3567                                                                 if (!strncasecmp(dir, "d", 1)) {
3568                                                                         direction = " DESC";
3569                                                                 } else {
3570                                                                         free(direction);
3571                                                                 }
3572                                                         }
3573
3574                                                 } else {
3575                                                         string = strdup(order_itr->key);
3576                                                         const char* dir = jsonObjectGetString(onode);
3577                                                         if (!strncasecmp(dir, "d", 1)) {
3578                                                                 direction = " DESC";
3579                                                         } else {
3580                                                                 direction = " ASC";
3581                                                         }
3582                                                 }
3583
3584                                                 if (first) {
3585                                                         first = 0;
3586                                                 } else {
3587                                                         buffer_add(order_buf, ", ");
3588                                                 }
3589
3590                                                 buffer_add(order_buf, string);
3591                                                 free(string);
3592
3593                                                 if (direction) {
3594                                                         buffer_add(order_buf, direction);
3595                                                 }
3596
3597                                         }
3598
3599                     jsonIteratorFree(order_itr);
3600
3601                                 } else {
3602                                         const char* str = jsonObjectGetString(snode);
3603                                         buffer_add(order_buf, str);
3604                                         break;
3605                                 }
3606
3607                         }
3608
3609                         jsonIteratorFree(class_itr);
3610
3611                         string = buffer_release(order_buf);
3612
3613                         if ( *string ) {
3614                                 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3615                                 OSRF_BUFFER_ADD( sql_buf, string );
3616                         }
3617
3618                         free(string);
3619                 }
3620
3621                 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
3622                         const char* str = jsonObjectGetString(_tmp);
3623                         buffer_fadd(
3624                                 sql_buf,
3625                                 " LIMIT %d",
3626                                 atoi(str)
3627                         );
3628                 }
3629
3630                 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
3631                 if (_tmp) {
3632                         const char* str = jsonObjectGetString(_tmp);
3633                         buffer_fadd(
3634                                 sql_buf,
3635                                 " OFFSET %d",
3636                                 atoi(str)
3637                         );
3638                 }
3639         }
3640
3641         if (defaultselhash) jsonObjectFree(defaultselhash);
3642
3643         OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3644         return buffer_release(sql_buf);
3645 }
3646
3647 int doJSONSearch ( osrfMethodContext* ctx ) {
3648         if(osrfMethodVerifyContext( ctx )) {
3649                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
3650                 return -1;
3651         }
3652
3653         osrfLogDebug(OSRF_LOG_MARK, "Recieved query request");
3654
3655         int err = 0;
3656
3657         // XXX for now...
3658         dbhandle = writehandle;
3659
3660         jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
3661
3662         int flags = 0;
3663
3664         if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
3665                 flags |= SELECT_DISTINCT;
3666
3667         if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
3668                 flags |= DISABLE_I18N;
3669
3670         osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
3671         char* sql = SELECT(
3672                         ctx,
3673                         jsonObjectGetKey( hash, "select" ),
3674                         jsonObjectGetKey( hash, "from" ),
3675                         jsonObjectGetKey( hash, "where" ),
3676                         jsonObjectGetKey( hash, "having" ),
3677                         jsonObjectGetKey( hash, "order_by" ),
3678                         jsonObjectGetKey( hash, "limit" ),
3679                         jsonObjectGetKey( hash, "offset" ),
3680                         flags
3681         );
3682
3683         if (!sql) {
3684                 err = -1;
3685                 return err;
3686         }
3687         
3688         osrfLogDebug(OSRF_LOG_MARK, "%s SQL =  %s", MODULENAME, sql);
3689         dbi_result result = dbi_conn_query(dbhandle, sql);
3690
3691         if(result) {
3692                 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
3693
3694                 if (dbi_result_first_row(result)) {
3695                         /* JSONify the result */
3696                         osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
3697
3698                         do {
3699                                 jsonObject* return_val = oilsMakeJSONFromResult( result );
3700                                 osrfAppRespond( ctx, return_val );
3701                 jsonObjectFree( return_val );
3702                         } while (dbi_result_next_row(result));
3703
3704                 } else {
3705                         osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
3706                 }
3707
3708                 osrfAppRespondComplete( ctx, NULL );
3709
3710                 /* clean up the query */
3711                 dbi_result_free(result); 
3712
3713         } else {
3714                 err = -1;
3715                 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
3716                 osrfAppSessionStatus(
3717                         ctx->session,
3718                         OSRF_STATUS_INTERNALSERVERERROR,
3719                         "osrfMethodException",
3720                         ctx->request,
3721                         "Severe query error -- see error log for more details"
3722                 );
3723         }
3724
3725         free(sql);
3726         return err;
3727 }
3728
3729 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
3730                 const jsonObject* params, int* err ) {
3731
3732         // XXX for now...
3733         dbhandle = writehandle;
3734
3735         osrfHash* links = osrfHashGet(meta, "links");
3736         osrfHash* fields = osrfHashGet(meta, "fields");
3737         char* core_class = osrfHashGet(meta, "classname");
3738         char* pkey = osrfHashGet(meta, "primarykey");
3739
3740         const jsonObject* _tmp;
3741         jsonObject* obj;
3742         jsonObject* search_hash = jsonObjectGetIndex(params, 0);
3743         jsonObject* order_hash = jsonObjectGetIndex(params, 1);
3744
3745         char* sql = buildSELECT( search_hash, order_hash, meta, ctx );
3746         if (!sql) {
3747                 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
3748                 *err = -1;
3749                 return NULL;
3750         }
3751         
3752         osrfLogDebug(OSRF_LOG_MARK, "%s SQL =  %s", MODULENAME, sql);
3753
3754         dbi_result result = dbi_conn_query(dbhandle, sql);
3755         if( NULL == result ) {
3756                 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
3757                         MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
3758                 osrfAppSessionStatus(
3759                         ctx->session,
3760                         OSRF_STATUS_INTERNALSERVERERROR,
3761                         "osrfMethodException",
3762                         ctx->request,
3763                         "Severe query error -- see error log for more details"
3764                 );
3765                 *err = -1;
3766                 free(sql);
3767                 return jsonNULL;
3768
3769         } else {
3770                 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
3771         }
3772
3773         jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
3774         osrfHash* dedup = osrfNewHash();
3775
3776         if (dbi_result_first_row(result)) {
3777                 /* JSONify the result */
3778                 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
3779                 do {
3780                         obj = oilsMakeFieldmapperFromResult( result, meta );
3781                         char* pkey_val = oilsFMGetString( obj, pkey );
3782                         if ( osrfHashGet( dedup, pkey_val ) ) {
3783                                 jsonObjectFree(obj);
3784                                 free(pkey_val);
3785                         } else {
3786                                 osrfHashSet( dedup, pkey_val, pkey_val );
3787                                 jsonObjectPush(res_list, obj);
3788                         }
3789                 } while (dbi_result_next_row(result));
3790         } else {
3791                 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
3792                         MODULENAME, sql );
3793         }
3794
3795         osrfHashFree(dedup);
3796         /* clean up the query */
3797         dbi_result_free(result);
3798         free(sql);
3799
3800         if (res_list->size && order_hash) {
3801                 _tmp = jsonObjectGetKeyConst( order_hash, "flesh" );
3802                 if (_tmp) {
3803                         int x = (int)jsonObjectGetNumber(_tmp);
3804                         if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
3805
3806                         const jsonObject* temp_blob;
3807                         if ((temp_blob = jsonObjectGetKeyConst( order_hash, "flesh_fields" )) && x > 0) {
3808
3809                                 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
3810                                 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
3811
3812                                 osrfStringArray* link_fields = NULL;
3813
3814                                 if (flesh_fields) {
3815                                         if (flesh_fields->size == 1) {
3816                                                 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
3817                                                 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
3818                                         }
3819
3820                                         if (!link_fields) {
3821                                                 jsonObject* _f;
3822                                                 link_fields = osrfNewStringArray(1);
3823                                                 jsonIterator* _i = jsonNewIterator( flesh_fields );
3824                                                 while ((_f = jsonIteratorNext( _i ))) {
3825                                                         osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
3826                                                 }
3827                         jsonIteratorFree(_i);
3828                                         }
3829                                 }
3830
3831                                 jsonObject* cur;
3832                                 jsonIterator* itr = jsonNewIterator( res_list );
3833                                 while ((cur = jsonIteratorNext( itr ))) {
3834
3835                                         int i = 0;
3836                                         char* link_field;
3837                                         
3838                                         while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
3839
3840                                                 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
3841
3842                                                 osrfHash* kid_link = osrfHashGet(links, link_field);
3843                                                 if (!kid_link) continue;
3844
3845                                                 osrfHash* field = osrfHashGet(fields, link_field);
3846                                                 if (!field) continue;
3847
3848                                                 osrfHash* value_field = field;
3849
3850                                                 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
3851                                                 if (!kid_idl) continue;
3852
3853                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
3854                                                         value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
3855                                                 }
3856                                                         
3857                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
3858                                                         value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
3859                                                 }
3860
3861                                                 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
3862
3863                                                 if (link_map->size > 0) {
3864                                                         jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
3865                                                         jsonObjectPush(
3866                                                                 _kid_key,
3867                                                                 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
3868                                                         );
3869
3870                                                         jsonObjectSetKey(
3871                                                                 flesh_blob,
3872                                                                 osrfHashGet(kid_link, "class"),
3873                                                                 _kid_key
3874                                                         );
3875                                                 };
3876
3877                                                 osrfLogDebug(
3878                                                         OSRF_LOG_MARK,
3879                                                         "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
3880                                                         osrfHashGet(kid_link, "field"),
3881                                                         osrfHashGet(kid_link, "class"),
3882                                                         osrfHashGet(kid_link, "key"),
3883                                                         osrfHashGet(kid_link, "reltype")
3884                                                 );
3885
3886                                                 jsonObject* fake_params = jsonNewObjectType(JSON_ARRAY);
3887                                                 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH)); // search hash
3888                                                 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH)); // order/flesh hash
3889
3890                                                 osrfLogDebug(OSRF_LOG_MARK, "Creating dummy params object...");
3891
3892                                                 const char* search_key = jsonObjectGetString(
3893                                                         jsonObjectGetIndex(
3894                                                                 cur,
3895                                                                 atoi( osrfHashGet(value_field, "array_position") )
3896                                                         )
3897                                                 );
3898
3899                                                 if (!search_key) {
3900                                                         osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
3901                                                         continue;
3902                                                 }
3903
3904                                                 jsonObjectSetKey(
3905                                                         jsonObjectGetIndex(fake_params, 0),
3906                                                         osrfHashGet(kid_link, "key"),
3907                                                         jsonNewObject( search_key )
3908                                                 );
3909
3910                                                 jsonObjectSetKey(
3911                                                         jsonObjectGetIndex(fake_params, 1),
3912                                                         "flesh",
3913                                                         jsonNewNumberObject( (double)(x - 1 + link_map->size) )
3914                                                 );
3915
3916                                                 if (flesh_blob)
3917                                                         jsonObjectSetKey( jsonObjectGetIndex(fake_params, 1), "flesh_fields", jsonObjectClone(flesh_blob) );
3918
3919                                                 if (jsonObjectGetKeyConst(order_hash, "order_by")) {
3920                                                         jsonObjectSetKey(
3921                                                                 jsonObjectGetIndex(fake_params, 1),
3922                                                                 "order_by",
3923                                                                 jsonObjectClone(jsonObjectGetKeyConst(order_hash, "order_by"))
3924                                                         );
3925                                                 }
3926
3927                                                 if (jsonObjectGetKeyConst(order_hash, "select")) {
3928                                                         jsonObjectSetKey(
3929                                                                 jsonObjectGetIndex(fake_params, 1),
3930                                                                 "select",
3931                                                                 jsonObjectClone(jsonObjectGetKeyConst(order_hash, "select"))
3932                                                         );
3933                                                 }
3934
3935                                                 jsonObject* kids = doFieldmapperSearch(ctx, kid_idl, fake_params, err);
3936
3937                                                 if(*err) {
3938                                                         jsonObjectFree( fake_params );
3939                                                         osrfStringArrayFree(link_fields);
3940                                                         jsonIteratorFree(itr);
3941                                                         jsonObjectFree(res_list);
3942                                                         jsonObjectFree(flesh_blob);
3943                                                         return jsonNULL;
3944                                                 }
3945
3946                                                 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
3947
3948                                                 jsonObject* X = NULL;
3949                                                 if ( link_map->size > 0 && kids->size > 0 ) {
3950                                                         X = kids;
3951                                                         kids = jsonNewObjectType(JSON_ARRAY);
3952
3953                                                         jsonObject* _k_node;
3954                                                         jsonIterator* _k = jsonNewIterator( X );
3955                                                         while ((_k_node = jsonIteratorNext( _k ))) {
3956                                                                 jsonObjectPush(
3957                                                                         kids,
3958                                                                         jsonObjectClone(
3959                                                                                 jsonObjectGetIndex(
3960                                                                                         _k_node,
3961                                                                                         (unsigned long)atoi(
3962                                                                                                 osrfHashGet(
3963                                                                                                         osrfHashGet(
3964                                                                                                                 osrfHashGet(
3965                                                                                                                         osrfHashGet(
3966                                                                                                                                 oilsIDL(),
3967                                                                                                                                 osrfHashGet(kid_link, "class")
3968                                                                                                                         ),
3969                                                                                                                         "fields"
3970                                                                                                                 ),
3971                                                                                                                 osrfStringArrayGetString( link_map, 0 )
3972                                                                                                         ),
3973                                                                                                         "array_position"
3974                                                                                                 )
3975                                                                                         )
3976                                                                                 )
3977                                                                         )
3978                                                                 );
3979                                                         }
3980                                                         jsonIteratorFree(_k);
3981                                                 }
3982
3983                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
3984                                                         osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
3985                                                         jsonObjectSetIndex(
3986                                                                 cur,
3987                                                                 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
3988                                                                 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
3989                                                         );
3990                                                 }
3991
3992                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
3993                                                         osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
3994                                                         jsonObjectSetIndex(
3995                                                                 cur,
3996                                                                 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
3997                                                                 jsonObjectClone( kids )
3998                                                         );
3999                                                 }
4000
4001                                                 if (X) {
4002                                                         jsonObjectFree(kids);
4003                                                         kids = X;
4004                                                 }
4005
4006                                                 jsonObjectFree( kids );
4007                                                 jsonObjectFree( fake_params );
4008
4009                                                 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4010                                                 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4011
4012                                         }
4013                                 }
4014                                 jsonObjectFree( flesh_blob );
4015                                 osrfStringArrayFree(link_fields);
4016                                 jsonIteratorFree(itr);
4017                         }
4018                 }
4019         }
4020
4021         return res_list;
4022 }
4023
4024
4025 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
4026
4027         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4028 #ifdef PCRUD
4029         jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
4030 #else
4031         jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
4032 #endif
4033
4034         if (!verifyObjectClass(ctx, target)) {
4035                 *err = -1;
4036                 return jsonNULL;
4037         }
4038
4039         if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4040                 osrfAppSessionStatus(
4041                         ctx->session,
4042                         OSRF_STATUS_BADREQUEST,
4043                         "osrfMethodException",
4044                         ctx->request,
4045                         "No active transaction -- required for UPDATE"
4046                 );
4047                 *err = -1;
4048                 return jsonNULL;
4049         }
4050
4051         // The following test is harmless but redundant.  If a class is
4052         // readonly, we don't register an update method for it.
4053         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4054                 osrfAppSessionStatus(
4055                         ctx->session,
4056                         OSRF_STATUS_BADREQUEST,
4057                         "osrfMethodException",
4058                         ctx->request,
4059                         "Cannot UPDATE readonly class"
4060                 );
4061                 *err = -1;
4062                 return jsonNULL;
4063         }
4064
4065         dbhandle = writehandle;
4066
4067         char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4068
4069         // Set the last_xact_id
4070         int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4071         if (index > -1) {
4072                 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
4073                 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4074         }       
4075
4076         char* pkey = osrfHashGet(meta, "primarykey");
4077         osrfHash* fields = osrfHashGet(meta, "fields");
4078
4079         char* id = oilsFMGetString( target, pkey );
4080
4081         osrfLogDebug(
4082                 OSRF_LOG_MARK,
4083                 "%s updating %s object with %s = %s",
4084                 MODULENAME,
4085                 osrfHashGet(meta, "fieldmapper"),
4086                 pkey,
4087                 id
4088         );
4089
4090         growing_buffer* sql = buffer_init(128);
4091         buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4092
4093         int i = 0;
4094         int first = 1;
4095         char* field_name;
4096         osrfStringArray* field_list = osrfHashKeys( fields );
4097         while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
4098
4099                 osrfHash* field = osrfHashGet( fields, field_name );
4100
4101                 if(!( strcmp( field_name, pkey ) )) continue;
4102                 if( str_is_true( osrfHashGet(osrfHashGet(fields,field_name), "virtual") ) )
4103                         continue;
4104
4105                 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4106
4107                 int value_is_numeric = 0;    // boolean
4108                 char* value;
4109                 if (field_object && field_object->classname) {
4110                         value = oilsFMGetString(
4111                                 field_object,
4112                                 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4113             );
4114                 } else {
4115                         value = jsonObjectToSimpleString( field_object );
4116                         if( field_object && JSON_NUMBER == field_object->type )
4117                                 value_is_numeric = 1;
4118                 }
4119
4120                 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s", osrfHashGet(meta, "fieldmapper"), field_name, value);
4121
4122                 if (!field_object || field_object->type == JSON_NULL) {
4123                         if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) ) && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
4124                                 if (first) first = 0;
4125                                 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4126                                 buffer_fadd( sql, " %s = NULL", field_name );
4127                         }
4128                         
4129                 } else if ( value_is_numeric || !strcmp( get_primitive( field ), "number") ) {
4130                         if (first) first = 0;
4131                         else OSRF_BUFFER_ADD_CHAR(sql, ',');
4132
4133                         const char* numtype = get_datatype( field );
4134                         if ( !strncmp( numtype, "INT", 3 ) ) {
4135                                 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
4136                         } else if ( !strcmp( numtype, "NUMERIC" ) ) {
4137                                 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
4138                         } else {
4139                                 // Must really be intended as a string, so quote it
4140                                 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4141                                         buffer_fadd( sql, " %s = %s", field_name, value );
4142                                 } else {
4143                                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4144                                         osrfAppSessionStatus(
4145                                                 ctx->session,
4146                                                 OSRF_STATUS_INTERNALSERVERERROR,
4147                                                 "osrfMethodException",
4148                                                 ctx->request,
4149                                                 "Error quoting string -- please see the error log for more details"
4150                                         );
4151                                         free(value);
4152                                         free(id);
4153                                         buffer_free(sql);
4154                                         *err = -1;
4155                                         return jsonNULL;
4156                                 }
4157                         }
4158
4159                         osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
4160
4161                 } else {
4162                         if ( dbi_conn_quote_string(dbhandle, &value) ) {
4163                                 if (first) first = 0;
4164                                 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4165                                 buffer_fadd( sql, " %s = %s", field_name, value );
4166
4167                         } else {
4168                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4169                                 osrfAppSessionStatus(
4170                                         ctx->session,
4171                                         OSRF_STATUS_INTERNALSERVERERROR,
4172                                         "osrfMethodException",
4173                                         ctx->request,
4174                                         "Error quoting string -- please see the error log for more details"
4175                                 );
4176                                 free(value);
4177                                 free(id);
4178                                 buffer_free(sql);
4179                                 *err = -1;
4180                                 return jsonNULL;
4181                         }
4182                 }
4183
4184                 free(value);
4185                 
4186         }
4187
4188         jsonObject* obj = jsonNewObject(id);
4189
4190         if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4191                 dbi_conn_quote_string(dbhandle, &id);
4192
4193         buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
4194
4195         char* query = buffer_release(sql);
4196         osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
4197
4198         dbi_result result = dbi_conn_query(dbhandle, query);
4199         free(query);
4200
4201         if (!result) {
4202                 jsonObjectFree(obj);
4203                 obj = jsonNewObject(NULL);
4204                 osrfLogError(
4205                         OSRF_LOG_MARK,
4206                         "%s ERROR updating %s object with %s = %s",
4207                         MODULENAME,
4208                         osrfHashGet(meta, "fieldmapper"),
4209                         pkey,
4210                         id
4211                 );
4212         }
4213
4214         free(id);
4215
4216         return obj;
4217 }
4218
4219 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4220
4221         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4222
4223         if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4224                 osrfAppSessionStatus(
4225                         ctx->session,
4226                         OSRF_STATUS_BADREQUEST,
4227                         "osrfMethodException",
4228                         ctx->request,
4229                         "No active transaction -- required for DELETE"
4230                 );
4231                 *err = -1;
4232                 return jsonNULL;
4233         }
4234
4235         // The following test is harmless but redundant.  If a class is
4236         // readonly, we don't register a delete method for it.
4237         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4238                 osrfAppSessionStatus(
4239                         ctx->session,
4240                         OSRF_STATUS_BADREQUEST,
4241                         "osrfMethodException",
4242                         ctx->request,
4243                         "Cannot DELETE readonly class"
4244                 );
4245                 *err = -1;
4246                 return jsonNULL;
4247         }
4248
4249         dbhandle = writehandle;
4250
4251         jsonObject* obj;
4252
4253         char* pkey = osrfHashGet(meta, "primarykey");
4254
4255         int _obj_pos = 0;
4256 #ifdef PCRUD
4257                 _obj_pos = 1;
4258 #endif
4259
4260         char* id;
4261         if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4262                 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4263                         *err = -1;
4264                         return jsonNULL;
4265                 }
4266
4267                 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4268         } else {
4269 #ifdef PCRUD
4270         if (!verifyObjectPCRUD( ctx, NULL )) {
4271                         *err = -1;
4272                         return jsonNULL;
4273         }
4274 #endif
4275                 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4276         }
4277
4278         osrfLogDebug(
4279                 OSRF_LOG_MARK,
4280                 "%s deleting %s object with %s = %s",
4281                 MODULENAME,
4282                 osrfHashGet(meta, "fieldmapper"),
4283                 pkey,
4284                 id
4285         );
4286
4287         obj = jsonNewObject(id);
4288
4289         if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4290                 dbi_conn_quote_string(writehandle, &id);
4291
4292         dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4293
4294         if (!result) {
4295                 jsonObjectFree(obj);
4296                 obj = jsonNewObject(NULL);
4297                 osrfLogError(
4298                         OSRF_LOG_MARK,
4299                         "%s ERROR deleting %s object with %s = %s",
4300                         MODULENAME,
4301                         osrfHashGet(meta, "fieldmapper"),
4302                         pkey,
4303                         id
4304                 );
4305         }
4306
4307         free(id);
4308
4309         return obj;
4310
4311 }
4312
4313
4314 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4315         if(!(result && meta)) return jsonNULL;
4316
4317         jsonObject* object = jsonNewObject(NULL);
4318         jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4319
4320         osrfHash* fields = osrfHashGet(meta, "fields");
4321
4322         osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4323
4324         osrfHash* _f;
4325         time_t _tmp_dt;
4326         char dt_string[256];
4327         struct tm gmdt;
4328
4329         int fmIndex;
4330         int columnIndex = 1;
4331         int attr;
4332         unsigned short type;
4333         const char* columnName;
4334
4335         /* cycle through the column list */
4336         while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4337
4338                 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4339
4340                 fmIndex = -1; // reset the position
4341                 
4342                 /* determine the field type and storage attributes */
4343                 type = dbi_result_get_field_type(result, columnName);
4344                 attr = dbi_result_get_field_attribs(result, columnName);
4345
4346                 /* fetch the fieldmapper index */
4347                 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4348                         
4349                         if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4350                                 continue;
4351                         
4352                         const char* pos = (char*)osrfHashGet(_f, "array_position");
4353                         if ( !pos ) continue;
4354
4355                         fmIndex = atoi( pos );
4356                         osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
4357                 } else {
4358                         continue;
4359                 }
4360
4361                 if (dbi_result_field_is_null(result, columnName)) {
4362                         jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
4363                 } else {
4364
4365                         switch( type ) {
4366
4367                                 case DBI_TYPE_INTEGER :
4368
4369                                         if( attr & DBI_INTEGER_SIZE8 ) 
4370                                                 jsonObjectSetIndex( object, fmIndex, 
4371                                                         jsonNewNumberObject(dbi_result_get_longlong(result, columnName)));
4372                                         else 
4373                                                 jsonObjectSetIndex( object, fmIndex, 
4374                                                         jsonNewNumberObject(dbi_result_get_int(result, columnName)));
4375
4376                                         break;
4377
4378                                 case DBI_TYPE_DECIMAL :
4379                                         jsonObjectSetIndex( object, fmIndex, 
4380                                                         jsonNewNumberObject(dbi_result_get_double(result, columnName)));
4381                                         break;
4382
4383                                 case DBI_TYPE_STRING :
4384
4385
4386                                         jsonObjectSetIndex(
4387                                                 object,
4388                                                 fmIndex,
4389                                                 jsonNewObject( dbi_result_get_string(result, columnName) )
4390                                         );
4391
4392                                         break;
4393
4394                                 case DBI_TYPE_DATETIME :
4395
4396                                         memset(dt_string, '\0', sizeof(dt_string));
4397                                         memset(&gmdt, '\0', sizeof(gmdt));
4398
4399                                         _tmp_dt = dbi_result_get_datetime(result, columnName);
4400
4401
4402                                         if (!(attr & DBI_DATETIME_DATE)) {
4403                                                 gmtime_r( &_tmp_dt, &gmdt );
4404                                                 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4405                                         } else if (!(attr & DBI_DATETIME_TIME)) {
4406                                                 localtime_r( &_tmp_dt, &gmdt );
4407                                                 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4408                                         } else {
4409                                                 localtime_r( &_tmp_dt, &gmdt );
4410                                                 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4411                                         }
4412
4413                                         jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
4414
4415                                         break;
4416
4417                                 case DBI_TYPE_BINARY :
4418                                         osrfLogError( OSRF_LOG_MARK, 
4419                                                 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4420                         }
4421                 }
4422         }
4423
4424         return object;
4425 }
4426
4427 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
4428         if(!result) return jsonNULL;
4429
4430         jsonObject* object = jsonNewObject(NULL);
4431
4432         time_t _tmp_dt;
4433         char dt_string[256];
4434         struct tm gmdt;
4435
4436         int fmIndex;
4437         int columnIndex = 1;
4438         int attr;
4439         unsigned short type;
4440         const char* columnName;
4441
4442         /* cycle through the column list */
4443         while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4444
4445                 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4446
4447                 fmIndex = -1; // reset the position
4448                 
4449                 /* determine the field type and storage attributes */
4450                 type = dbi_result_get_field_type(result, columnName);
4451                 attr = dbi_result_get_field_attribs(result, columnName);
4452
4453                 if (dbi_result_field_is_null(result, columnName)) {
4454                         jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
4455                 } else {
4456
4457                         switch( type ) {
4458
4459                                 case DBI_TYPE_INTEGER :
4460
4461                                         if( attr & DBI_INTEGER_SIZE8 ) 
4462                                                 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_longlong(result, columnName)) );
4463                                         else 
4464                                                 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_int(result, columnName)) );
4465                                         break;
4466
4467                                 case DBI_TYPE_DECIMAL :
4468                                         jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_double(result, columnName)) );
4469                                         break;
4470
4471                                 case DBI_TYPE_STRING :
4472                                         jsonObjectSetKey( object, columnName, jsonNewObject(dbi_result_get_string(result, columnName)) );
4473                                         break;
4474
4475                                 case DBI_TYPE_DATETIME :
4476
4477                                         memset(dt_string, '\0', sizeof(dt_string));
4478                                         memset(&gmdt, '\0', sizeof(gmdt));
4479
4480                                         _tmp_dt = dbi_result_get_datetime(result, columnName);
4481
4482
4483                                         if (!(attr & DBI_DATETIME_DATE)) {
4484                                                 gmtime_r( &_tmp_dt, &gmdt );
4485                                                 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4486                                         } else if (!(attr & DBI_DATETIME_TIME)) {
4487                                                 localtime_r( &_tmp_dt, &gmdt );
4488                                                 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4489                                         } else {
4490                                                 localtime_r( &_tmp_dt, &gmdt );
4491                                                 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4492                                         }
4493
4494                                         jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
4495                                         break;
4496
4497                                 case DBI_TYPE_BINARY :
4498                                         osrfLogError( OSRF_LOG_MARK, 
4499                                                 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4500                         }
4501                 }
4502         }
4503
4504         return object;
4505 }
4506
4507 // Interpret a string as true or false
4508 static int str_is_true( const char* str ) {
4509         if( NULL == str || strcasecmp( str, "true" ) )
4510                 return 0;
4511         else
4512                 return 1;
4513 }
4514
4515 // Interpret a jsonObject as true or false
4516 static int obj_is_true( const jsonObject* obj ) {
4517         if( !obj )
4518                 return 0;
4519         else switch( obj->type )
4520         {
4521                 case JSON_BOOL :
4522                         if( obj->value.b )
4523                                 return 1;
4524                         else
4525                                 return 0;
4526                 case JSON_STRING :
4527                         if( strcasecmp( obj->value.s, "true" ) )
4528                                 return 0;
4529                         else
4530                                 return 1;
4531                 case JSON_NUMBER :          // Support 1/0 for perl's sake
4532                         if( jsonObjectGetNumber( obj ) == 1.0 )
4533                                 return 1;
4534                         else
4535                                 return 0;
4536                 default :
4537                         return 0;
4538         }
4539 }
4540
4541 // Translate a numeric code into a text string identifying a type of
4542 // jsonObject.  To be used for building error messages.
4543 static const char* json_type( int code ) {
4544         switch ( code )
4545         {
4546                 case 0 :
4547                         return "JSON_HASH";
4548                 case 1 :
4549                         return "JSON_ARRAY";
4550                 case 2 :
4551                         return "JSON_STRING";
4552                 case 3 :
4553                         return "JSON_NUMBER";
4554                 case 4 :
4555                         return "JSON_NULL";
4556                 case 5 :
4557                         return "JSON_BOOL";
4558                 default :
4559                         return "(unrecognized)";
4560         }
4561 }
4562
4563 // Extract the "primitive" attribute from an IDL field definition.
4564 // If we haven't initialized the app, then we must be running in
4565 // some kind of testbed.  In that case, default to "string".
4566 static const char* get_primitive( osrfHash* field ) {
4567         const char* s = osrfHashGet( field, "primitive" );
4568         if( !s ) {
4569                 if( child_initialized )
4570                         osrfLogError(
4571                                 OSRF_LOG_MARK,
4572                                 "%s ERROR No \"datatype\" attribute for field \"%s\"",
4573                                 MODULENAME,
4574                                 osrfHashGet( field, "name" )
4575                         );
4576                 else
4577                         s = "string";
4578         }
4579         return s;
4580 }
4581
4582 // Extract the "datatype" attribute from an IDL field definition.
4583 // If we haven't initialized the app, then we must be running in
4584 // some kind of testbed.  In that case, default to to NUMERIC,
4585 // since we look at the datatype only for numbers.
4586 static const char* get_datatype( osrfHash* field ) {
4587         const char* s = osrfHashGet( field, "datatype" );
4588         if( !s ) {
4589                 if( child_initialized )
4590                         osrfLogError(
4591                                 OSRF_LOG_MARK,
4592                                 "%s ERROR No \"datatype\" attribute for field \"%s\"",
4593                                 MODULENAME,
4594                                 osrfHashGet( field, "name" )
4595                         );
4596                 else
4597                         s = "NUMERIC";
4598         }
4599         return s;
4600 }
4601
4602 /*
4603 If the input string is potentially a valid SQL identifier, return 1.
4604 Otherwise return 0.
4605
4606 Purpose: to prevent certain kinds of SQL injection.  To that end we
4607 don't necessarily need to follow all the rules exactly, such as requiring
4608 that the first character not be a digit.
4609
4610 We allow leading and trailing white space.  In between, we do not allow
4611 punctuation (except for underscores and dollar signs), control 
4612 characters, or embedded white space.
4613
4614 More pedantically we should allow quoted identifiers containing arbitrary
4615 characters, but for the foreseeable future such quoted identifiers are not
4616 likely to be an issue.
4617 */
4618 static int is_identifier( const char* s) {
4619         if( !s )
4620                 return 0;
4621
4622         // Skip leading white space
4623         while( isspace( (unsigned char) *s ) )
4624                 ++s;
4625
4626         if( !s )
4627                 return 0;   // Nothing but white space?  Not okay.
4628
4629         // Check each character until we reach white space or
4630         // end-of-string.  Letters, digits, underscores, and 
4631         // dollar signs are okay.  Control characters and other
4632         // punctuation characters are not okay.  Anything else
4633         // is okay -- it could for example be part of a multibyte
4634         // UTF8 character such as a letter with diacritical marks,
4635         // and those are allowed.
4636         do {
4637                 if( isalnum( (unsigned char) *s )
4638                         || '_' == *s
4639                         || '$' == *s )
4640                         ;  // Fine; keep going
4641                 else if(   ispunct( (unsigned char) *s )
4642                                 || iscntrl( (unsigned char) *s ) )
4643                         return 0;
4644                         ++s;
4645         } while( *s && ! isspace( (unsigned char) *s ) );
4646
4647         // If we found any white space in the above loop,
4648         // the rest had better be all white space.
4649         
4650         while( isspace( (unsigned char) *s ) )
4651                 ++s;
4652
4653         if( *s )
4654                 return 0;   // White space was embedded within non-white space
4655
4656         return 1;
4657 }