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