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