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