]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/c-apps/oils_cstore.c
1. Renamed getSourceDefinition() to getRelation(), since "SourceDefinition"
[working/Evergreen.git] / Open-ILS / src / c-apps / oils_cstore.c
1 /**
2         @file oils_cstore.c
3         @brief As a server, perform database operations at the request of clients.
4 */
5
6 #include <ctype.h>
7 #include "opensrf/osrf_application.h"
8 #include "opensrf/osrf_settings.h"
9 #include "opensrf/osrf_message.h"
10 #include "opensrf/utils.h"
11 #include "opensrf/osrf_json.h"
12 #include "opensrf/log.h"
13 #include "openils/oils_utils.h"
14 #include <dbi/dbi.h>
15
16 #include <time.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <unistd.h>
20
21 #ifdef RSTORE
22 #  define MODULENAME "open-ils.reporter-store"
23 #else
24 #  ifdef PCRUD
25 #    define MODULENAME "open-ils.pcrud"
26 #  else
27 #    define MODULENAME "open-ils.cstore"
28 #  endif
29 #endif
30
31 // The next four macros are OR'd together as needed to form a set
32 // of bitflags.  SUBCOMBO enables an extra pair of parentheses when
33 // nesting one UNION, INTERSECT or EXCEPT inside another.
34 // SUBSELECT tells us we're in a subquery, so don't add the
35 // terminal semicolon yet.
36 #define SUBCOMBO    8
37 #define SUBSELECT   4
38 #define DISABLE_I18N    2
39 #define SELECT_DISTINCT 1
40
41 #define AND_OP_JOIN     0
42 #define OR_OP_JOIN      1
43
44 struct ClassInfoStruct;
45 typedef struct ClassInfoStruct ClassInfo;
46
47 #define ALIAS_STORE_SIZE 16
48 #define CLASS_NAME_STORE_SIZE 16
49
50 struct ClassInfoStruct {
51         char* alias;
52         char* class_name;
53         char* source_def;
54         osrfHash* class_def;      // Points into IDL
55         osrfHash* fields;         // Points into IDL
56         osrfHash* links;          // Points into IDL
57
58         // The remaining members are private and internal.  Client code should not
59         // access them directly.
60
61         ClassInfo* next;          // Supports linked list of joined classes
62         int in_use;               // boolean
63
64         // We usually store the alias and class name in the following arrays, and
65         // point the corresponding pointers at them.  When the string is too big
66         // for the array (which will probably never happen in practice), we strdup it.
67
68         char alias_store[ ALIAS_STORE_SIZE + 1 ];
69         char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
70 };
71
72 struct QueryFrameStruct;
73 typedef struct QueryFrameStruct QueryFrame;
74
75 struct QueryFrameStruct {
76         ClassInfo core;
77         ClassInfo* join_list;  // linked list of classes joined to the core class
78         QueryFrame* next;      // implements stack as linked list
79         int in_use;            // boolean
80 };
81
82 int osrfAppChildInit();
83 int osrfAppInitialize();
84 void osrfAppChildExit();
85
86 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
87
88 int beginTransaction ( osrfMethodContext* );
89 int commitTransaction ( osrfMethodContext* );
90 int rollbackTransaction ( osrfMethodContext* );
91
92 int setSavepoint ( osrfMethodContext* );
93 int releaseSavepoint ( osrfMethodContext* );
94 int rollbackSavepoint ( osrfMethodContext* );
95
96 int doJSONSearch ( osrfMethodContext* );
97
98 int dispatchCRUDMethod ( osrfMethodContext* );
99 static jsonObject* doCreate ( osrfMethodContext*, int* );
100 static jsonObject* doRetrieve ( osrfMethodContext*, int* );
101 static jsonObject* doUpdate ( osrfMethodContext*, int* );
102 static jsonObject* doDelete ( osrfMethodContext*, int* );
103 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
104                 jsonObject* where_hash, jsonObject* query_hash, int* err );
105 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
106 static jsonObject* oilsMakeJSONFromResult( dbi_result );
107
108 static char* searchSimplePredicate ( const char* op, const char* class_alias,
109                                 osrfHash* field, const jsonObject* node );
110 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
111 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject*);
112 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*, const char* );
113 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
114 static char* searchINPredicate ( const char*, osrfHash*,
115                                                                  jsonObject*, const char*, osrfMethodContext* );
116 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
117 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
118 static char* searchWHERE ( const jsonObject*, const ClassInfo*, int, osrfMethodContext* );
119 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
120 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
121
122 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
123
124 void userDataFree( void* );
125 static void sessionDataFree( char*, void* );
126 static char* getRelation( osrfHash* );
127 static int str_is_true( const char* str );
128 static int obj_is_true( const jsonObject* obj );
129 static const char* json_type( int code );
130 static const char* get_primitive( osrfHash* field );
131 static const char* get_datatype( osrfHash* field );
132 static int is_identifier( const char* s);
133 static int is_good_operator( const char* op );
134 static void pop_query_frame( void );
135 static void push_query_frame( void );
136 static int add_query_core( const char* alias, const char* class_name );
137 static ClassInfo* search_alias( const char* target );
138 static ClassInfo* search_all_alias( const char* target );
139 static ClassInfo* add_joined_class( const char* alias, const char* classname );
140 static void clear_query_stack( void );
141
142 #ifdef PCRUD
143 static jsonObject* verifyUserPCRUD( osrfMethodContext* );
144 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
145 static char* org_tree_root( osrfMethodContext* ctx );
146 static jsonObject* single_hash( const char* key, const char* value );
147 #endif
148
149 static int child_initialized = 0;   /* boolean */
150
151 static dbi_conn writehandle; /* our MASTER db connection */
152 static dbi_conn dbhandle; /* our CURRENT db connection */
153 //static osrfHash * readHandles;
154 static jsonObject* const jsonNULL = NULL; //
155 static int max_flesh_depth = 100;
156
157 // The following points to the top of a stack of QueryFrames.  It's a little
158 // confusing because the top level of the query is at the bottom of the stack.
159 static QueryFrame* curr_query = NULL;
160
161 /**
162         @brief Disconnect from the database.
163
164         This function is called when the server drone is about to terminate.
165 */
166 void osrfAppChildExit() {
167         osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
168
169         int same = 0;
170         if (writehandle == dbhandle) same = 1;
171         if (writehandle) {
172                 dbi_conn_query(writehandle, "ROLLBACK;");
173                 dbi_conn_close(writehandle);
174                 writehandle = NULL;
175         }
176         if (dbhandle && !same)
177         dbi_conn_close(dbhandle);
178
179         // XXX add cleanup of readHandles whenever that gets used
180
181         return;
182 }
183
184 /**
185         @brief Initialize the application.
186         @return Zero if successful, or non-zero if not.
187
188         Load the IDL file into an internal data structure for future reference.  Each non-virtual
189         class in the IDL corresponds to a table or view in the database, or to a subquery defined
190         in the IDL.  Ignore all virtual tables and virtual fields.
191
192         Register a number of methods, some of them general-purpose and others specific for
193         particular classes.
194
195         The name of the application is given by the MODULENAME macro, whose value depends on
196         conditional compilation.  The method names also incorporate MODULENAME, followed by a
197         dot, as a prefix.  Some methods are registered or not registered depending on whether
198         the PCRUD macro is defined, and on whether the IDL includes permacrud entries for the
199         class and method.
200
201         The general-purpose methods are as follows (minus their MODULENAME prefixes):
202
203         - json_query (not registered for PCRUD)
204         - transaction.begin
205         - transaction.commit
206         - transaction.rollback
207         - savepoint.set
208         - savepoint.release
209         - savepoint.rollback
210
211         For each non-virtual class, create up to eight class-specific methods:
212
213         - create    (not for readonly classes)
214         - retrieve
215         - update    (not for readonly classes)
216         - delete    (not for readonly classes
217         - search    (atomic and non-atomic versions)
218         - id_list   (atomic and non-atomic versions)
219
220         For PCRUD, the full method names follow the pattern "MODULENAME.method_type.classname".
221         Otherwise they follow the pattern "MODULENAME.direct.XXX.method_type", where XXX is the
222         fieldmapper name from the IDL, with every run of one or more consecutive colons replaced
223         by a period.  In addition, the names of atomic methods have a suffix of ".atomic".
224
225         This function is called when the registering the application, and is executed by the
226         listener before spawning the drones.
227 */
228 int osrfAppInitialize() {
229
230         osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
231         osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
232
233         if (!oilsIDLInit( osrf_settings_host_value("/IDL") ))
234                 return 1; /* return non-zero to indicate error */
235
236         growing_buffer* method_name = buffer_init(64);
237 #ifndef PCRUD
238         // Generic search thingy
239         buffer_add(method_name, MODULENAME);
240         buffer_add(method_name, ".json_query");
241         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
242                         "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
243 #endif
244
245         // first we register all the transaction and savepoint methods
246         buffer_reset(method_name);
247         OSRF_BUFFER_ADD(method_name, MODULENAME);
248         OSRF_BUFFER_ADD(method_name, ".transaction.begin");
249         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
250                         "beginTransaction", "", 0, 0 );
251
252         buffer_reset(method_name);
253         OSRF_BUFFER_ADD(method_name, MODULENAME);
254         OSRF_BUFFER_ADD(method_name, ".transaction.commit");
255         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
256                         "commitTransaction", "", 0, 0 );
257
258         buffer_reset(method_name);
259         OSRF_BUFFER_ADD(method_name, MODULENAME);
260         OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
261         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
262                         "rollbackTransaction", "", 0, 0 );
263
264         buffer_reset(method_name);
265         OSRF_BUFFER_ADD(method_name, MODULENAME);
266         OSRF_BUFFER_ADD(method_name, ".savepoint.set");
267         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
268                         "setSavepoint", "", 1, 0 );
269
270         buffer_reset(method_name);
271         OSRF_BUFFER_ADD(method_name, MODULENAME);
272         OSRF_BUFFER_ADD(method_name, ".savepoint.release");
273         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
274                         "releaseSavepoint", "", 1, 0 );
275
276         buffer_reset(method_name);
277         OSRF_BUFFER_ADD(method_name, MODULENAME);
278         OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
279         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
280                         "rollbackSavepoint", "", 1, 0 );
281
282         static const char* global_method[] = {
283                 "create",
284                 "retrieve",
285                 "update",
286                 "delete",
287                 "search",
288                 "id_list"
289         };
290         const int global_method_count
291                 = sizeof( global_method ) / sizeof ( global_method[0] );
292
293         unsigned long class_count = osrfHashGetCount( oilsIDL() );
294         osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
295         osrfLogDebug(OSRF_LOG_MARK,
296                 "At most %lu methods will be generated",
297                 (unsigned long) (class_count * global_method_count) );
298
299         osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
300         osrfHash* idlClass = NULL;
301
302         // For each class in the IDL...
303         while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
304
305                 const char* classname = osrfHashIteratorKey( class_itr );
306                 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
307
308                 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), MODULENAME )) {
309                         osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on",
310                                         MODULENAME, classname);
311                         continue;
312                 }
313
314                 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
315                         osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
316                         continue;
317                 }
318
319                 // Look up some other attributes of the current class
320                 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
321                 if( !idlClass_fieldmapper ) {
322                         osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL",
323                                         classname );
324                         continue;
325                 }
326
327 #ifdef PCRUD
328                 // For PCRUD, ignore classes with no permacrud attribute
329                 osrfHash* idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
330                 if (!idlClass_permacrud) {
331                         osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no permacrud in IDL", classname );
332                         continue;
333                 }
334 #endif
335                 const char* readonly = osrfHashGet(idlClass, "readonly");
336
337                 int i;
338                 for( i = 0; i < global_method_count; ++i ) {  // for each global method
339                         const char* method_type = global_method[ i ];
340                         osrfLogDebug(OSRF_LOG_MARK,
341                                 "Using files to build %s class methods for %s", method_type, classname);
342
343 #ifdef PCRUD
344                         // Treat "id_list" or "search" as forms of "retrieve"
345                         const char* tmp_method = method_type;
346                         if ( *tmp_method == 'i' || *tmp_method == 's') {  // "id_list" or "search"
347                                 tmp_method = "retrieve";
348                         }
349                         // Skip this method if there is no permacrud entry for it
350                         if (!osrfHashGet( idlClass_permacrud, tmp_method ))
351                                 continue;
352 #endif
353
354                         // No create, update, or delete methods for a readonly class
355                         if ( str_is_true( readonly ) &&
356                                 ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd')
357                                 ) continue;
358
359                         buffer_reset( method_name );
360
361                         // Build the method name
362 #ifdef PCRUD
363                         // For PCRUD: MODULENAME.method_type.classname
364                         buffer_fadd(method_name, "%s.%s.%s", MODULENAME, method_type, classname);
365 #else
366                         // For non-PCRUD: MODULENAME.MODULENAME.direct.XXX.method_type
367                         // where XXX is the fieldmapper name from the IDL, with every run of
368                         // one or more consecutive colons replaced by a period.
369                         char* st_tmp = NULL;
370                         char* part = NULL;
371                         char* _fm = strdup( idlClass_fieldmapper );
372                         part = strtok_r(_fm, ":", &st_tmp);
373
374                         buffer_fadd(method_name, "%s.direct.%s", MODULENAME, part);
375
376                         while ((part = strtok_r(NULL, ":", &st_tmp))) {
377                                 OSRF_BUFFER_ADD_CHAR(method_name, '.');
378                                 OSRF_BUFFER_ADD(method_name, part);
379                         }
380                         OSRF_BUFFER_ADD_CHAR(method_name, '.');
381                         OSRF_BUFFER_ADD(method_name, method_type);
382                         free(_fm);
383 #endif
384
385                         // For an id_list or search method we specify the OSRF_METHOD_STREAMING option.
386                         // The consequence is that we implicitly create an atomic method in addition to
387                         // the usual non-atomic method.
388                         int flags = 0;
389                         if (*method_type == 'i' || *method_type == 's') {  // id_list or search
390                                 flags = flags | OSRF_METHOD_STREAMING;
391                         }
392
393                         osrfHash* method_meta = osrfNewHash();
394                         osrfHashSet( method_meta, idlClass, "class");
395                         osrfHashSet( method_meta, buffer_data( method_name ), "methodname" );
396                         osrfHashSet( method_meta, strdup(method_type), "methodtype" );
397
398                         // Register the method, with a pointer to an osrfHash to tell the method
399                         // its name, type, and class.
400                         osrfAppRegisterExtendedMethod(
401                                         MODULENAME,
402                                         OSRF_BUFFER_C_STR( method_name ),
403                                         "dispatchCRUDMethod",
404                                         "",
405                                         1,
406                                         flags,
407                                         (void*)method_meta
408                                 );
409
410                 } // end for each global method
411         } // end for each class in IDL
412
413         buffer_free( method_name );
414         osrfHashIteratorFree( class_itr );
415
416         return 0;
417 }
418
419 /**
420         @brief Get a table name, view name, or subquery for use in a FROM clause.
421         @param class Pointer to the IDL class entry.
422         @return A table name, a view name, or a subquery in parentheses.
423
424         In some cases the IDL defines a class, not with a table name or a view name, but with
425         a SELECT statement, which may be used as a subquery.
426 */
427 static char* getRelation( osrfHash* class ) {
428
429         char* source_def = NULL;
430         const char* tabledef = osrfHashGet(class, "tablename");
431
432         if ( tabledef ) {
433                 source_def = strdup( tabledef );   // Return the name of a table or view
434         } else {
435                 tabledef = osrfHashGet( class, "source_definition" );
436                 if( tabledef ) {
437                         // Return a subquery, enclosed in parentheses
438                         source_def = safe_malloc( strlen( tabledef ) + 3 );
439                         source_def[ 0 ] = '(';
440                         strcpy( source_def + 1, tabledef );
441                         strcat( source_def, ")" );
442                 } else {
443                         // Not found: return an error
444                         const char* classname = osrfHashGet( class, "classname" );
445                         if( !classname )
446                                 classname = "???";
447                         osrfLogError(
448                                 OSRF_LOG_MARK,
449                                 "%s ERROR No tablename or source_definition for class \"%s\"",
450                                 MODULENAME,
451                                 classname
452                         );
453                 }
454         }
455
456         return source_def;
457 }
458
459 /**
460         @brief Initialize a server drone.
461         @return Zero if successful, -1 if not.
462
463         Connect to the database.  For each non-virtual class in the IDL, execute a dummy "SELECT * "
464         query to get the datatype of each column.  Record the datatypes in the loaded IDL.
465
466         This function is called by a server drone shortly after it is spawned by the listener.
467 */
468 int osrfAppChildInit() {
469
470         osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
471         dbi_initialize(NULL);
472         osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
473
474         char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
475         char* user   = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
476         char* host   = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
477         char* port   = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
478         char* db     = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
479         char* pw     = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
480         char* md     = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion",
481                         MODULENAME);
482
483         osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
484         writehandle = dbi_conn_new(driver);
485
486         if(!writehandle) {
487                 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
488                 return -1;
489         }
490         osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
491
492         osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database.  host=%s, "
493                         "port=%s, user=%s, pw=%s, db=%s", MODULENAME, host, port, user, pw, db );
494
495         if(host) dbi_conn_set_option(writehandle, "host", host );
496         if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
497         if(user) dbi_conn_set_option(writehandle, "username", user);
498         if(pw)   dbi_conn_set_option(writehandle, "password", pw );
499         if(db)   dbi_conn_set_option(writehandle, "dbname", db );
500
501         if(md)                     max_flesh_depth = atoi(md);
502         if(max_flesh_depth < 0)    max_flesh_depth = 1;
503         if(max_flesh_depth > 1000) max_flesh_depth = 1000;
504
505         free(user);
506         free(host);
507         free(port);
508         free(db);
509         free(pw);
510
511         const char* err;
512         if (dbi_conn_connect(writehandle) < 0) {
513                 sleep(1);
514                 if (dbi_conn_connect(writehandle) < 0) {
515                         dbi_conn_error(writehandle, &err);
516                         osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
517                         return -1;
518                 }
519         }
520
521         osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
522
523         osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
524         osrfHash* class = NULL;
525         growing_buffer* query_buf = buffer_init( 64 );
526
527         // For each class in the IDL...
528         while( (class = osrfHashIteratorNext( class_itr ) ) ) {
529                 const char* classname = osrfHashIteratorKey( class_itr );
530                 osrfHash* fields = osrfHashGet( class, "fields" );
531
532                 // If the class is virtual, ignore it
533                 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
534                         osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
535                         continue;
536                 }
537
538                 char* tabledef = getRelation(class);
539                 if( !tabledef )
540                         continue;   // No such relation -- a query of it would be doomed to failure
541
542                 buffer_reset( query_buf );
543                 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
544
545                 free(tabledef);
546
547                 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s", 
548                                 MODULENAME, OSRF_BUFFER_C_STR( query_buf ) );
549
550                 dbi_result result = dbi_conn_query( writehandle, OSRF_BUFFER_C_STR( query_buf ) );
551                 if (result) {
552
553                         int columnIndex = 1;
554                         const char* columnName;
555                         osrfHash* _f;
556                         while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
557
558                                 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
559                                                 columnName );
560
561                                 /* fetch the fieldmapper index */
562                                 if( (_f = osrfHashGet(fields, columnName)) ) {
563
564                                         osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
565
566                                         /* determine the field type and storage attributes */
567
568                                         switch( dbi_result_get_field_type_idx(result, columnIndex) ) {
569
570                                                 case DBI_TYPE_INTEGER : {
571
572                                                         if ( !osrfHashGet(_f, "primitive") )
573                                                                 osrfHashSet(_f, "number", "primitive");
574
575                                                         int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
576                                                         if( attr & DBI_INTEGER_SIZE8 )
577                                                                 osrfHashSet(_f, "INT8", "datatype");
578                                                         else
579                                                                 osrfHashSet(_f, "INT", "datatype");
580                                                         break;
581                                                 }
582                                                 case DBI_TYPE_DECIMAL :
583                                                         if ( !osrfHashGet(_f, "primitive") )
584                                                                 osrfHashSet(_f, "number", "primitive");
585
586                                                         osrfHashSet(_f,"NUMERIC", "datatype");
587                                                         break;
588
589                                                 case DBI_TYPE_STRING :
590                                                         if ( !osrfHashGet(_f, "primitive") )
591                                                                 osrfHashSet(_f,"string", "primitive");
592
593                                                         osrfHashSet(_f,"TEXT", "datatype");
594                                                         break;
595
596                                                 case DBI_TYPE_DATETIME :
597                                                         if ( !osrfHashGet(_f, "primitive") )
598                                                                 osrfHashSet(_f,"string", "primitive");
599
600                                                         osrfHashSet(_f,"TIMESTAMP", "datatype");
601                                                         break;
602
603                                                 case DBI_TYPE_BINARY :
604                                                         if ( !osrfHashGet(_f, "primitive") )
605                                                                 osrfHashSet(_f,"string", "primitive");
606
607                                                         osrfHashSet(_f,"BYTEA", "datatype");
608                                         }
609
610                                         osrfLogDebug(
611                                                         OSRF_LOG_MARK,
612                                                         "Setting [%s] to primitive [%s] and datatype [%s]...",
613                                                         columnName,
614                                                         osrfHashGet(_f, "primitive"),
615                                                         osrfHashGet(_f, "datatype")
616                                                         );
617                                 }
618                                 ++columnIndex;
619                         } // end while loop for traversing columns of result
620                         dbi_result_free(result);
621                 } else {
622                         osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", classname);
623                 }
624         } // end for each class in IDL
625
626         buffer_free( query_buf );
627         osrfHashIteratorFree( class_itr );
628         child_initialized = 1;
629         return 0;
630 }
631
632 /*
633   This function is a sleazy hack intended *only* for testing and
634   debugging.  Any real server process should initialize the
635   database connection by calling osrfAppChildInit().
636 */
637 void set_cstore_dbi_conn( dbi_conn conn ) {
638         dbhandle = writehandle = conn;
639 }
640
641 void userDataFree( void* blob ) {
642     osrfHashFree( (osrfHash*)blob );
643     return;
644 }
645
646 static void sessionDataFree( char* key, void* item ) {
647     if (!(strcmp(key,"xact_id"))) {
648         if (writehandle)
649             dbi_conn_query(writehandle, "ROLLBACK;");
650         free(item);
651     }
652
653     return;
654 }
655
656 int beginTransaction ( osrfMethodContext* ctx ) {
657         if(osrfMethodVerifyContext( ctx )) {
658                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
659                 return -1;
660         }
661
662 #ifdef PCRUD
663         jsonObject* user = verifyUserPCRUD( ctx );
664         if (!user)
665                 return -1;
666         jsonObjectFree(user);
667 #endif
668
669     dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
670     if (!result) {
671         osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
672         osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error starting transaction" );
673         return -1;
674     } else {
675         jsonObject* ret = jsonNewObject(ctx->session->session_id);
676         osrfAppRespondComplete( ctx, ret );
677         jsonObjectFree(ret);
678
679         if (!ctx->session->userData) {
680             ctx->session->userData = osrfNewHash();
681             osrfHashSetCallback((osrfHash*)ctx->session->userData, &sessionDataFree);
682         }
683
684         osrfHashSet( (osrfHash*)ctx->session->userData, strdup( ctx->session->session_id ), "xact_id" );
685         ctx->session->userDataFree = &userDataFree;
686
687     }
688     return 0;
689 }
690
691 int setSavepoint ( osrfMethodContext* ctx ) {
692         if(osrfMethodVerifyContext( ctx )) {
693                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
694                 return -1;
695         }
696
697     int spNamePos = 0;
698 #ifdef PCRUD
699         spNamePos = 1;
700         jsonObject* user = verifyUserPCRUD( ctx );
701         if (!user)
702                 return -1;
703         jsonObjectFree(user);
704 #endif
705
706     if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
707         osrfAppSessionStatus(
708                 ctx->session,
709                 OSRF_STATUS_INTERNALSERVERERROR,
710                 "osrfMethodException",
711                 ctx->request,
712                 "No active transaction -- required for savepoints"
713                 );
714         return -1;
715     }
716
717         const char* spName = jsonObjectGetString(jsonObjectGetIndex(ctx->params, spNamePos));
718
719         dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
720         if (!result) {
721                 osrfLogError(
722                         OSRF_LOG_MARK,
723                         "%s: Error creating savepoint %s in transaction %s",
724                         MODULENAME,
725                         spName,
726                         osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
727                 );
728                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
729                                 "osrfMethodException", ctx->request, "Error creating savepoint" );
730                 return -1;
731         } else {
732                 jsonObject* ret = jsonNewObject(spName);
733                 osrfAppRespondComplete( ctx, ret );
734                 jsonObjectFree(ret);
735         }
736         return 0;
737 }
738
739 int releaseSavepoint ( osrfMethodContext* ctx ) {
740         if(osrfMethodVerifyContext( ctx )) {
741                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
742                 return -1;
743         }
744
745         int spNamePos = 0;
746 #ifdef PCRUD
747         spNamePos = 1;
748         jsonObject* user = verifyUserPCRUD( ctx );
749         if (!user)
750                 return -1;
751         jsonObjectFree(user);
752 #endif
753
754     if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
755         osrfAppSessionStatus(
756                 ctx->session,
757                 OSRF_STATUS_INTERNALSERVERERROR,
758                 "osrfMethodException",
759                 ctx->request,
760                 "No active transaction -- required for savepoints"
761                 );
762         return -1;
763     }
764
765         const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
766
767     dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
768     if (!result) {
769         osrfLogError(
770                 OSRF_LOG_MARK,
771                 "%s: Error releasing savepoint %s in transaction %s",
772                 MODULENAME,
773                 spName,
774                 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
775                 );
776                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
777                                 "osrfMethodException", ctx->request, "Error releasing savepoint" );
778         return -1;
779     } else {
780         jsonObject* ret = jsonNewObject(spName);
781         osrfAppRespondComplete( ctx, ret );
782         jsonObjectFree(ret);
783     }
784     return 0;
785 }
786
787 int rollbackSavepoint ( osrfMethodContext* ctx ) {
788         if(osrfMethodVerifyContext( ctx )) {
789                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
790                 return -1;
791         }
792
793         int spNamePos = 0;
794 #ifdef PCRUD
795         spNamePos = 1;
796         jsonObject* user = verifyUserPCRUD( ctx );
797         if (!user)
798                 return -1;
799         jsonObjectFree(user);
800 #endif
801
802     if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
803         osrfAppSessionStatus(
804                 ctx->session,
805                 OSRF_STATUS_INTERNALSERVERERROR,
806                 "osrfMethodException",
807                 ctx->request,
808                 "No active transaction -- required for savepoints"
809                 );
810         return -1;
811     }
812
813         const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
814
815     dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
816     if (!result) {
817         osrfLogError(
818                 OSRF_LOG_MARK,
819                 "%s: Error rolling back savepoint %s in transaction %s",
820                 MODULENAME,
821                 spName,
822                 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
823                 );
824                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
825                                 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
826         return -1;
827     } else {
828         jsonObject* ret = jsonNewObject(spName);
829         osrfAppRespondComplete( ctx, ret );
830         jsonObjectFree(ret);
831     }
832     return 0;
833 }
834
835 int commitTransaction ( osrfMethodContext* ctx ) {
836         if(osrfMethodVerifyContext( ctx )) {
837                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
838                 return -1;
839         }
840
841 #ifdef PCRUD
842         jsonObject* user = verifyUserPCRUD( ctx );
843         if (!user)
844                 return -1;
845         jsonObjectFree(user);
846 #endif
847
848     if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
849         osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
850                                 "osrfMethodException", ctx->request, "No active transaction to commit" );
851         return -1;
852     }
853
854     dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
855     if (!result) {
856         osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
857         osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
858                                 "osrfMethodException", ctx->request, "Error committing transaction" );
859         return -1;
860     } else {
861         osrfHashRemove(ctx->session->userData, "xact_id");
862         jsonObject* ret = jsonNewObject(ctx->session->session_id);
863         osrfAppRespondComplete( ctx, ret );
864         jsonObjectFree(ret);
865     }
866     return 0;
867 }
868
869 int rollbackTransaction ( osrfMethodContext* ctx ) {
870         if(osrfMethodVerifyContext( ctx )) {
871                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
872                 return -1;
873         }
874
875 #ifdef PCRUD
876         jsonObject* user = verifyUserPCRUD( ctx );
877         if (!user)
878                 return -1;
879         jsonObjectFree(user);
880 #endif
881
882     if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
883         osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
884                                 "osrfMethodException", ctx->request, "No active transaction to roll back" );
885         return -1;
886     }
887
888     dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
889     if (!result) {
890         osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
891         osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
892                                 "osrfMethodException", ctx->request, "Error rolling back transaction" );
893         return -1;
894     } else {
895         osrfHashRemove(ctx->session->userData, "xact_id");
896         jsonObject* ret = jsonNewObject(ctx->session->session_id);
897         osrfAppRespondComplete( ctx, ret );
898         jsonObjectFree(ret);
899     }
900     return 0;
901 }
902
903 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
904         if(osrfMethodVerifyContext( ctx )) {
905                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
906                 return -1;
907         }
908
909         osrfHash* meta = (osrfHash*) ctx->method->userData;
910     osrfHash* class_obj = osrfHashGet( meta, "class" );
911
912     int err = 0;
913
914     const char* methodtype = osrfHashGet(meta, "methodtype");
915     jsonObject * obj = NULL;
916
917     if (!strcmp(methodtype, "create")) {
918         obj = doCreate(ctx, &err);
919         osrfAppRespondComplete( ctx, obj );
920     }
921     else if (!strcmp(methodtype, "retrieve")) {
922         obj = doRetrieve(ctx, &err);
923         osrfAppRespondComplete( ctx, obj );
924     }
925     else if (!strcmp(methodtype, "update")) {
926         obj = doUpdate(ctx, &err);
927         osrfAppRespondComplete( ctx, obj );
928     }
929     else if (!strcmp(methodtype, "delete")) {
930         obj = doDelete(ctx, &err);
931         osrfAppRespondComplete( ctx, obj );
932     }
933     else if (!strcmp(methodtype, "search")) {
934
935                 jsonObject* where_clause;
936                 jsonObject* rest_of_query;
937
938 #ifdef PCRUD
939                 where_clause  = jsonObjectGetIndex( ctx->params, 1 );
940                 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
941 #else
942                 where_clause  = jsonObjectGetIndex( ctx->params, 0 );
943                 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
944 #endif
945
946                 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
947
948                 if(err) return err;
949
950                 jsonObject* cur = 0;
951                 unsigned long res_idx = 0;
952                 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
953 #ifdef PCRUD
954                         if(!verifyObjectPCRUD(ctx, cur)) continue;
955 #endif
956                         osrfAppRespond( ctx, cur );
957                 }
958                 osrfAppRespondComplete( ctx, NULL );
959
960         } else if (!strcmp(methodtype, "id_list")) {
961
962                 jsonObject* where_clause;
963                 jsonObject* rest_of_query;
964
965                 // We use the where clause without change.  But we need
966                 // to massage the rest of the query, so we work with a copy
967                 // of it instead of modifying the original.
968 #ifdef PCRUD
969                 where_clause  = jsonObjectGetIndex( ctx->params, 1 );
970                 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
971 #else
972                 where_clause  = jsonObjectGetIndex( ctx->params, 0 );
973                 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
974 #endif
975
976                 if ( rest_of_query ) {
977                         jsonObjectRemoveKey( rest_of_query, "select" );
978                         jsonObjectRemoveKey( rest_of_query, "no_i18n" );
979                         jsonObjectRemoveKey( rest_of_query, "flesh" );
980                         jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
981                 } else {
982                         rest_of_query = jsonNewObjectType( JSON_HASH );
983                 }
984
985                 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
986
987                 // Build a SELECT list containing just the primary key,
988                 // i.e. like { "classname":["keyname"] }
989                 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
990                 jsonObjectPush( col_list_obj,     // Load array with name of primary key
991                         jsonNewObject( osrfHashGet( class_obj, "primarykey" ) ) );
992                 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
993                 jsonObjectSetKey( select_clause, osrfHashGet( class_obj, "classname" ), col_list_obj );
994
995                 jsonObjectSetKey( rest_of_query, "select", select_clause );
996
997                 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
998
999                 jsonObjectFree( rest_of_query );
1000                 if(err) return err;
1001
1002                 jsonObject* cur;
1003                 unsigned long res_idx = 0;
1004                 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1005 #ifdef PCRUD
1006                         if(!verifyObjectPCRUD(ctx, cur)) continue;
1007 #endif
1008                         osrfAppRespond(
1009                                 ctx,
1010                                 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
1011                                 );
1012                 }
1013                 osrfAppRespondComplete( ctx, NULL );
1014
1015     } else {
1016         osrfAppRespondComplete( ctx, obj );
1017     }
1018
1019     jsonObjectFree(obj);
1020
1021     return err;
1022 }
1023
1024 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1025
1026     int ret = 1;
1027     osrfHash* meta = (osrfHash*) ctx->method->userData;
1028     osrfHash* class = osrfHashGet( meta, "class" );
1029
1030     if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1031
1032                 const char* temp_classname = param->classname;
1033                 if( ! temp_classname )
1034                         temp_classname = "(null)";
1035
1036         growing_buffer* msg = buffer_init(128);
1037         buffer_fadd(
1038                 msg,
1039                 "%s: %s method for type %s was passed a %s",
1040                 MODULENAME,
1041                 osrfHashGet(meta, "methodtype"),
1042                 osrfHashGet(class, "classname"),
1043                 temp_classname
1044                 );
1045
1046         char* m = buffer_release(msg);
1047         osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1048                                 ctx->request, m );
1049
1050         free(m);
1051
1052         return 0;
1053     }
1054
1055 #ifdef PCRUD
1056     ret = verifyObjectPCRUD( ctx, param );
1057 #endif
1058
1059     return ret;
1060 }
1061
1062 #ifdef PCRUD
1063
1064 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1065         const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1066     jsonObject* auth_object = jsonNewObject(auth);
1067     jsonObject* user = oilsUtilsQuickReq("open-ils.auth","open-ils.auth.session.retrieve",
1068                         auth_object);
1069     jsonObjectFree(auth_object);
1070
1071     if (!user->classname || strcmp(user->classname, "au")) {
1072
1073         growing_buffer* msg = buffer_init(128);
1074         buffer_fadd(
1075             msg,
1076             "%s: permacrud received a bad auth token: %s",
1077             MODULENAME,
1078             auth
1079         );
1080
1081         char* m = buffer_release(msg);
1082         osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1083                                 ctx->request, m );
1084
1085         free(m);
1086         jsonObjectFree(user);
1087         user = jsonNULL;
1088     }
1089
1090     return user;
1091
1092 }
1093
1094 static int verifyObjectPCRUD (  osrfMethodContext* ctx, const jsonObject* obj ) {
1095
1096         dbhandle = writehandle;
1097
1098         osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1099         osrfHash* class = osrfHashGet( method_metadata, "class" );
1100         const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1101         int fetch = 0;
1102
1103         if ( ( *method_type == 's' || *method_type == 'i' ) ) {
1104                 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1105         } else if ( *method_type == 'u' || *method_type == 'd' ) {
1106                 fetch = 1; // MUST go to the db for the object for update and delete
1107         }
1108
1109         osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
1110
1111     if (!pcrud) {
1112         // No permacrud for this method type on this class
1113
1114         growing_buffer* msg = buffer_init(128);
1115         buffer_fadd(
1116             msg,
1117             "%s: %s on class %s has no permacrud IDL entry",
1118             MODULENAME,
1119             osrfHashGet(method_metadata, "methodtype"),
1120             osrfHashGet(class, "classname")
1121         );
1122
1123         char* m = buffer_release(msg);
1124         osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN, "osrfMethodException", ctx->request, m );
1125
1126         free(m);
1127
1128         return 0;
1129     }
1130
1131         jsonObject* user = verifyUserPCRUD( ctx );
1132         if (!user)
1133                 return 0;
1134
1135     int userid = atoi( oilsFMGetString( user, "id" ) );
1136     jsonObjectFree(user);
1137
1138     osrfStringArray* permission = osrfHashGet(pcrud, "permission");
1139     osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
1140     osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
1141
1142     osrfStringArray* context_org_array = osrfNewStringArray(1);
1143
1144     int err = 0;
1145     char* pkey_value = NULL;
1146         if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1147                 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions required, fetching top of the org tree" );
1148
1149                 // check for perm at top of org tree
1150                 char* org_tree_root_id = org_tree_root( ctx );
1151                 if( org_tree_root_id ) {
1152                         osrfStringArrayAdd( context_org_array, org_tree_root_id );
1153                         osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1154                 } else  {
1155                         osrfStringArrayFree( context_org_array );
1156                         return 0;
1157                 }
1158
1159         } else {
1160             osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, fetching context org ids" );
1161             const char* pkey = osrfHashGet(class, "primarykey");
1162         jsonObject *param = NULL;
1163
1164         if (obj->classname) {
1165             pkey_value = oilsFMGetString( obj, pkey );
1166             if (!fetch) param = jsonObjectClone(obj);
1167                 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s", pkey_value );
1168         } else {
1169             pkey_value = jsonObjectToSimpleString( obj );
1170             fetch = 1;
1171                 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value of %s and retrieving from the database", pkey_value );
1172         }
1173
1174                 if (fetch) {
1175                         jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1176                         jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1177                         jsonObjectFree(_tmp_params);
1178
1179                         param = jsonObjectExtractIndex(_list, 0);
1180                         jsonObjectFree(_list);
1181                 }
1182
1183         if (!param) {
1184             osrfLogDebug( OSRF_LOG_MARK, "Object not found in the database with primary key %s of %s", pkey, pkey_value );
1185
1186             growing_buffer* msg = buffer_init(128);
1187             buffer_fadd(
1188                 msg,
1189                 "%s: no object found with primary key %s of %s",
1190                 MODULENAME,
1191                 pkey,
1192                 pkey_value
1193             );
1194
1195             char* m = buffer_release(msg);
1196             osrfAppSessionStatus(
1197                 ctx->session,
1198                 OSRF_STATUS_INTERNALSERVERERROR,
1199                 "osrfMethodException",
1200                 ctx->request,
1201                 m
1202             );
1203
1204             free(m);
1205             if (pkey_value) free(pkey_value);
1206
1207             return 0;
1208         }
1209
1210         if (local_context->size > 0) {
1211                 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified", local_context->size);
1212             int i = 0;
1213             const char* lcontext = NULL;
1214             while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1215                 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1216                     osrfLogDebug(
1217                     OSRF_LOG_MARK,
1218                     "adding class-local field %s (value: %s) to the context org list",
1219                     lcontext,
1220                     osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1221                 );
1222             }
1223         }
1224
1225
1226                 if (foreign_context) {
1227                         unsigned long class_count = osrfHashGetCount( foreign_context );
1228                         osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count);
1229
1230                         if (class_count > 0) {
1231
1232                                 osrfHash* fcontext = NULL;
1233                                 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1234                                 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1235                                         const char* class_name = osrfHashIteratorKey( class_itr );
1236                                         osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1237
1238                         osrfLogDebug(
1239                         OSRF_LOG_MARK,
1240                         "%d foreign context fields(s) specified for class %s",
1241                         ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1242                         class_name
1243                     );
1244
1245                     char* foreign_pkey = osrfHashGet(fcontext, "field");
1246                     char* foreign_pkey_value = oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1247
1248                                         jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1249
1250                                         jsonObject* _list = doFieldmapperSearch(
1251                                                 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1252
1253                     jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1254                     jsonObjectFree(_tmp_params);
1255                     jsonObjectFree(_list);
1256
1257                     osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1258
1259                     if (_fparam && jump_list) {
1260                         const char* flink = NULL;
1261                         int k = 0;
1262                         while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1263                             free(foreign_pkey_value);
1264
1265                             osrfHash* foreign_link_hash = oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1266
1267                                                         foreign_pkey_value = oilsFMGetString(_fparam, flink);
1268                                                         foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1269
1270                                                         _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1271
1272                                                         _list = doFieldmapperSearch(
1273                                                                 ctx,
1274                                                                 osrfHashGet( oilsIDL(), osrfHashGet( foreign_link_hash, "class" ) ),
1275                                                                 _tmp_params,
1276                                                                 NULL,
1277                                                                 &err
1278                                                         );
1279
1280                             _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1281                             jsonObjectFree(_tmp_params);
1282                             jsonObjectFree(_list);
1283                         }
1284                     }
1285
1286                     if (!_fparam) {
1287
1288                         growing_buffer* msg = buffer_init(128);
1289                         buffer_fadd(
1290                             msg,
1291                             "%s: no object found with primary key %s of %s",
1292                             MODULENAME,
1293                             foreign_pkey,
1294                             foreign_pkey_value
1295                         );
1296
1297                         char* m = buffer_release(msg);
1298                         osrfAppSessionStatus(
1299                             ctx->session,
1300                             OSRF_STATUS_INTERNALSERVERERROR,
1301                             "osrfMethodException",
1302                             ctx->request,
1303                             m
1304                         );
1305
1306                         free(m);
1307                         osrfHashIteratorFree(class_itr);
1308                         free(foreign_pkey_value);
1309                         jsonObjectFree(param);
1310
1311                         return 0;
1312                     }
1313
1314                     free(foreign_pkey_value);
1315
1316                     int j = 0;
1317                     const char* foreign_field = NULL;
1318                     while ( (foreign_field = osrfStringArrayGetString(osrfHashGet(fcontext,"context"), j++)) ) {
1319                         osrfStringArrayAdd( context_org_array, oilsFMGetString( _fparam, foreign_field ) );
1320                             osrfLogDebug(
1321                             OSRF_LOG_MARK,
1322                             "adding foreign class %s field %s (value: %s) to the context org list",
1323                             class_name,
1324                             foreign_field,
1325                             osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1326                         );
1327                                         }
1328
1329                                         jsonObjectFree(_fparam);
1330                                 }
1331
1332                                 osrfHashIteratorFree( class_itr );
1333                         }
1334                 }
1335
1336                 jsonObjectFree(param);
1337         }
1338
1339     const char* context_org = NULL;
1340     const char* perm = NULL;
1341     int OK = 0;
1342
1343     if (permission->size == 0) {
1344             osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1345         OK = 1;
1346     }
1347
1348     int i = 0;
1349     while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1350         int j = 0;
1351         while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1352             dbi_result result;
1353
1354             if (pkey_value) {
1355                     osrfLogDebug(
1356                     OSRF_LOG_MARK,
1357                     "Checking object permission [%s] for user %d on object %s (class %s) at org %d",
1358                     perm,
1359                     userid,
1360                     pkey_value,
1361                     osrfHashGet(class, "classname"),
1362                     atoi(context_org)
1363                 );
1364
1365                 result = dbi_conn_queryf(
1366                     writehandle,
1367                     "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1368                     userid,
1369                     perm,
1370                     osrfHashGet(class, "classname"),
1371                     pkey_value,
1372                     atoi(context_org)
1373                 );
1374
1375                 if (result) {
1376                     osrfLogDebug(
1377                         OSRF_LOG_MARK,
1378                         "Received a result for object permission [%s] for user %d on object %s (class %s) at org %d",
1379                         perm,
1380                         userid,
1381                         pkey_value,
1382                         osrfHashGet(class, "classname"),
1383                         atoi(context_org)
1384                     );
1385
1386                     if (dbi_result_first_row(result)) {
1387                         jsonObject* return_val = oilsMakeJSONFromResult( result );
1388                                                 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1389
1390                             osrfLogDebug(
1391                             OSRF_LOG_MARK,
1392                             "Status of object permission [%s] for user %d on object %s (class %s) at org %d is %s",
1393                             perm,
1394                             userid,
1395                             pkey_value,
1396                             osrfHashGet(class, "classname"),
1397                             atoi(context_org),
1398                             has_perm
1399                         );
1400
1401                         if ( *has_perm == 't' ) OK = 1;
1402                         jsonObjectFree(return_val);
1403                     }
1404
1405                     dbi_result_free(result);
1406                     if (OK) break;
1407                 }
1408             }
1409
1410                 osrfLogDebug( OSRF_LOG_MARK, "Checking non-object permission [%s] for user %d at org %d", perm, userid, atoi(context_org) );
1411             result = dbi_conn_queryf(
1412                 writehandle,
1413                 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1414                 userid,
1415                 perm,
1416                 atoi(context_org)
1417             );
1418
1419                         if (result) {
1420                                 osrfLogDebug( OSRF_LOG_MARK, "Received a result for permission [%s] for user %d at org %d",
1421                                                 perm, userid, atoi(context_org) );
1422                                 if ( dbi_result_first_row(result) ) {
1423                                         jsonObject* return_val = oilsMakeJSONFromResult( result );
1424                                         const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1425                                         osrfLogDebug( OSRF_LOG_MARK, "Status of permission [%s] for user %d at org %d is [%s]",
1426                                                         perm, userid, atoi(context_org), has_perm );
1427                                         if ( *has_perm == 't' ) OK = 1;
1428                                         jsonObjectFree(return_val);
1429                                 }
1430
1431                                 dbi_result_free(result);
1432                                 if (OK) break;
1433                         }
1434
1435         }
1436         if (OK) break;
1437     }
1438
1439     if (pkey_value) free(pkey_value);
1440     osrfStringArrayFree(context_org_array);
1441
1442     return OK;
1443 }
1444
1445 /**
1446  * Look up the root of the org_unit tree.  If you find it, return
1447  * a string containing the id, which the caller is responsible for freeing.
1448  * Otherwise return NULL.
1449  */
1450 static char* org_tree_root( osrfMethodContext* ctx ) {
1451
1452         static char cached_root_id[ 32 ] = "";  // extravagantly large buffer
1453         static time_t last_lookup_time = 0;
1454         time_t current_time = time( NULL );
1455
1456         if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1457                 // We successfully looked this up less than an hour ago.
1458                 // It's not likely to have changed since then.
1459                 return strdup( cached_root_id );
1460         }
1461         last_lookup_time = current_time;
1462
1463         int err = 0;
1464         jsonObject* where_clause = single_hash( "parent_ou", NULL );
1465         jsonObject* result = doFieldmapperSearch(
1466                 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1467         jsonObjectFree( where_clause );
1468
1469         jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1470
1471         if (! tree_top) {
1472                 jsonObjectFree( result );
1473
1474                 growing_buffer* msg = buffer_init(128);
1475                 OSRF_BUFFER_ADD( msg, MODULENAME );
1476                 OSRF_BUFFER_ADD( msg,
1477                                 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1478
1479                 char* m = buffer_release(msg);
1480                 osrfAppSessionStatus( ctx->session,
1481                                 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1482                 free(m);
1483
1484                 cached_root_id[ 0 ] = '\0';
1485                 return NULL;
1486         }
1487
1488         char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1489         osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1490
1491         jsonObjectFree( result );
1492
1493         strcpy( cached_root_id, root_org_unit_id );
1494         return root_org_unit_id;
1495 }
1496
1497 /**
1498 Utility function: create a JSON_HASH with a single key/value pair.
1499 This function is equivalent to:
1500
1501         jsonParseFmt( "{\"%s\":\"%s\"}", key, value )
1502
1503 or, if value is NULL:
1504
1505         jsonParseFmt( "{\"%s\":null}", key )
1506
1507 ...but faster because it doesn't create and parse a JSON string.
1508 */
1509 static jsonObject* single_hash( const char* key, const char* value ) {
1510         // Sanity check
1511         if( ! key ) key = "";
1512
1513         jsonObject* hash = jsonNewObjectType( JSON_HASH );
1514         jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1515         return hash;
1516 }
1517 #endif
1518
1519
1520 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1521
1522         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1523 #ifdef PCRUD
1524         jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1525         jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1526 #else
1527         jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1528         jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1529 #endif
1530
1531         if (!verifyObjectClass(ctx, target)) {
1532                 *err = -1;
1533                 return jsonNULL;
1534         }
1535
1536         osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1537
1538         char* trans_id = NULL;
1539         if( ctx->session && ctx->session->userData )
1540                 trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
1541
1542         if ( !trans_id ) {
1543                 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1544
1545                 osrfAppSessionStatus(
1546                         ctx->session,
1547                         OSRF_STATUS_BADREQUEST,
1548                         "osrfMethodException",
1549                         ctx->request,
1550                         "No active transaction -- required for CREATE"
1551                 );
1552                 *err = -1;
1553                 return jsonNULL;
1554         }
1555
1556         // The following test is harmless but redundant.  If a class is
1557         // readonly, we don't register a create method for it.
1558         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1559                 osrfAppSessionStatus(
1560                         ctx->session,
1561                         OSRF_STATUS_BADREQUEST,
1562                         "osrfMethodException",
1563                         ctx->request,
1564                         "Cannot INSERT readonly class"
1565                 );
1566                 *err = -1;
1567                 return jsonNULL;
1568         }
1569
1570         // Set the last_xact_id
1571         int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1572         if (index > -1) {
1573                 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
1574                 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1575         }
1576
1577         osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1578
1579         dbhandle = writehandle;
1580
1581         osrfHash* fields = osrfHashGet(meta, "fields");
1582         char* pkey = osrfHashGet(meta, "primarykey");
1583         char* seq = osrfHashGet(meta, "sequence");
1584
1585         growing_buffer* table_buf = buffer_init(128);
1586         growing_buffer* col_buf = buffer_init(128);
1587         growing_buffer* val_buf = buffer_init(128);
1588
1589         OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1590         OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1591         OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1592         buffer_add(val_buf,"VALUES (");
1593
1594
1595         int first = 1;
1596         osrfHash* field = NULL;
1597         osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1598         while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1599
1600                 const char* field_name = osrfHashIteratorKey( field_itr );
1601
1602                 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1603                         continue;
1604
1605                 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1606
1607                 char* value;
1608                 if (field_object && field_object->classname) {
1609                         value = oilsFMGetString(
1610                                 field_object,
1611                                 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1612                         );
1613                 } else if( field_object && JSON_BOOL == field_object->type ) {
1614                         if( jsonBoolIsTrue( field_object ) )
1615                                 value = strdup( "t" );
1616                         else
1617                                 value = strdup( "f" );
1618                 } else {
1619                         value = jsonObjectToSimpleString( field_object );
1620                 }
1621
1622                 if (first) {
1623                         first = 0;
1624                 } else {
1625                         OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1626                         OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1627                 }
1628
1629                 buffer_add(col_buf, field_name);
1630
1631                 if (!field_object || field_object->type == JSON_NULL) {
1632                         buffer_add( val_buf, "DEFAULT" );
1633
1634                 } else if ( !strcmp(get_primitive( field ), "number") ) {
1635                         const char* numtype = get_datatype( field );
1636                         if ( !strcmp( numtype, "INT8") ) {
1637                                 buffer_fadd( val_buf, "%lld", atoll(value) );
1638
1639                         } else if ( !strcmp( numtype, "INT") ) {
1640                                 buffer_fadd( val_buf, "%d", atoi(value) );
1641
1642                         } else if ( !strcmp( numtype, "NUMERIC") ) {
1643                                 buffer_fadd( val_buf, "%f", atof(value) );
1644                         }
1645                 } else {
1646                         if ( dbi_conn_quote_string(writehandle, &value) ) {
1647                                 OSRF_BUFFER_ADD( val_buf, value );
1648
1649                         } else {
1650                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1651                                 osrfAppSessionStatus(
1652                                         ctx->session,
1653                                         OSRF_STATUS_INTERNALSERVERERROR,
1654                                         "osrfMethodException",
1655                                         ctx->request,
1656                                         "Error quoting string -- please see the error log for more details"
1657                                 );
1658                                 free(value);
1659                                 buffer_free(table_buf);
1660                                 buffer_free(col_buf);
1661                                 buffer_free(val_buf);
1662                                 *err = -1;
1663                                 return jsonNULL;
1664                         }
1665                 }
1666
1667                 free(value);
1668
1669         }
1670
1671         osrfHashIteratorFree( field_itr );
1672
1673         OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1674         OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1675
1676         char* table_str = buffer_release(table_buf);
1677         char* col_str   = buffer_release(col_buf);
1678         char* val_str   = buffer_release(val_buf);
1679         growing_buffer* sql = buffer_init(128);
1680         buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1681         free(table_str);
1682         free(col_str);
1683         free(val_str);
1684
1685         char* query = buffer_release(sql);
1686
1687         osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1688
1689
1690         dbi_result result = dbi_conn_query(writehandle, query);
1691
1692         jsonObject* obj = NULL;
1693
1694         if (!result) {
1695                 obj = jsonNewObject(NULL);
1696                 osrfLogError(
1697                         OSRF_LOG_MARK,
1698                         "%s ERROR inserting %s object using query [%s]",
1699                         MODULENAME,
1700                         osrfHashGet(meta, "fieldmapper"),
1701                         query
1702                 );
1703                 osrfAppSessionStatus(
1704                         ctx->session,
1705                         OSRF_STATUS_INTERNALSERVERERROR,
1706                         "osrfMethodException",
1707                         ctx->request,
1708                         "INSERT error -- please see the error log for more details"
1709                 );
1710                 *err = -1;
1711         } else {
1712
1713                 char* id = oilsFMGetString(target, pkey);
1714                 if (!id) {
1715                         unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1716                         growing_buffer* _id = buffer_init(10);
1717                         buffer_fadd(_id, "%lld", new_id);
1718                         id = buffer_release(_id);
1719                 }
1720
1721                 // Find quietness specification, if present
1722                 const char* quiet_str = NULL;
1723                 if ( options ) {
1724                         const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1725                         if( quiet_obj )
1726                                 quiet_str = jsonObjectGetString( quiet_obj );
1727                 }
1728
1729                 if( str_is_true( quiet_str ) ) {  // if quietness is specified
1730                         obj = jsonNewObject(id);
1731                 }
1732                 else {
1733
1734                         jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1735                         jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1736
1737                         jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1738
1739                         jsonObjectFree( where_clause );
1740
1741                         if(*err) {
1742                                 obj = jsonNULL;
1743                         } else {
1744                                 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1745                         }
1746
1747                         jsonObjectFree( list );
1748                 }
1749
1750                 free(id);
1751         }
1752
1753         free(query);
1754
1755         return obj;
1756
1757 }
1758
1759 /*
1760  * Fetch one row from a specified table, using a specified value
1761  * for the primary key
1762 */
1763 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1764
1765     int id_pos = 0;
1766     int order_pos = 1;
1767
1768 #ifdef PCRUD
1769     id_pos = 1;
1770     order_pos = 2;
1771 #endif
1772
1773         osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1774
1775         const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos);  // key value
1776
1777         osrfLogDebug(
1778                 OSRF_LOG_MARK,
1779                 "%s retrieving %s object with primary key value of %s",
1780                 MODULENAME,
1781                 osrfHashGet( class_def, "fieldmapper" ),
1782                 jsonObjectGetString( id_obj )
1783         );
1784
1785         // Build a WHERE clause based on the key value
1786         jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1787         jsonObjectSetKey(
1788                 where_clause,
1789                 osrfHashGet( class_def, "primarykey" ),
1790                 jsonObjectClone( id_obj )
1791         );
1792
1793         jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
1794
1795         jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
1796
1797         jsonObjectFree( where_clause );
1798         if(*err)
1799                 return jsonNULL;
1800
1801         jsonObject* obj = jsonObjectExtractIndex( list, 0 );
1802         jsonObjectFree( list );
1803
1804 #ifdef PCRUD
1805         if(!verifyObjectPCRUD(ctx, obj)) {
1806         jsonObjectFree(obj);
1807         *err = -1;
1808
1809         growing_buffer* msg = buffer_init(128);
1810                 OSRF_BUFFER_ADD( msg, MODULENAME );
1811                 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1812
1813         char* m = buffer_release(msg);
1814         osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1815
1816         free(m);
1817
1818                 return jsonNULL;
1819         }
1820 #endif
1821
1822         return obj;
1823 }
1824
1825 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1826         growing_buffer* val_buf = buffer_init(32);
1827         const char* numtype = get_datatype( field );
1828
1829         if ( !strncmp( numtype, "INT", 3 ) ) {
1830                 if (value->type == JSON_NUMBER)
1831                         //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1832                         buffer_fadd( val_buf, jsonObjectGetString( value ) );
1833                 else {
1834                         //const char* val_str = jsonObjectGetString( value );
1835                         //buffer_fadd( val_buf, "%ld", atol(val_str) );
1836                         buffer_fadd( val_buf, jsonObjectGetString( value ) );
1837                 }
1838
1839         } else if ( !strcmp( numtype, "NUMERIC" ) ) {
1840                 if (value->type == JSON_NUMBER)
1841                         //buffer_fadd( val_buf, "%f",  jsonObjectGetNumber(value) );
1842                         buffer_fadd( val_buf, jsonObjectGetString( value ) );
1843                 else {
1844                         //const char* val_str = jsonObjectGetString( value );
1845                         //buffer_fadd( val_buf, "%f", atof(val_str) );
1846                         buffer_fadd( val_buf, jsonObjectGetString( value ) );
1847                 }
1848
1849         } else {
1850                 // Presumably this was really intended ot be a string, so quote it
1851                 char* str = jsonObjectToSimpleString( value );
1852                 if ( dbi_conn_quote_string(dbhandle, &str) ) {
1853                         OSRF_BUFFER_ADD( val_buf, str );
1854                         free(str);
1855                 } else {
1856                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
1857                         free(str);
1858                         buffer_free(val_buf);
1859                         return NULL;
1860                 }
1861         }
1862
1863         return buffer_release(val_buf);
1864 }
1865
1866 static char* searchINPredicate (const char* class_alias, osrfHash* field,
1867                 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1868         growing_buffer* sql_buf = buffer_init(32);
1869
1870         buffer_fadd(
1871                 sql_buf,
1872                 "\"%s\".%s ",
1873                 class_alias,
1874                 osrfHashGet(field, "name")
1875         );
1876
1877         if (!op) {
1878                 buffer_add(sql_buf, "IN (");
1879         } else if (!(strcasecmp(op,"not in"))) {
1880                 buffer_add(sql_buf, "NOT IN (");
1881         } else {
1882                 buffer_add(sql_buf, "IN (");
1883         }
1884
1885         if (node->type == JSON_HASH) {
1886                 // subquery predicate
1887                 char* subpred = buildQuery( ctx, node, SUBSELECT );
1888                 if( ! subpred ) {
1889                         buffer_free( sql_buf );
1890                         return NULL;
1891                 }
1892
1893                 buffer_add(sql_buf, subpred);
1894                 free(subpred);
1895
1896         } else if (node->type == JSON_ARRAY) {
1897                 // literal value list
1898         int in_item_index = 0;
1899         int in_item_first = 1;
1900         const jsonObject* in_item;
1901         while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1902
1903                         if (in_item_first)
1904                                 in_item_first = 0;
1905                         else
1906                                 buffer_add(sql_buf, ", ");
1907
1908                         // Sanity check
1909                         if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1910                                 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1911                                                 MODULENAME, json_type( in_item->type ) );
1912                                 buffer_free(sql_buf);
1913                                 return NULL;
1914                         }
1915
1916                         // Append the literal value -- quoted if not a number
1917                         if ( JSON_NUMBER == in_item->type ) {
1918                                 char* val = jsonNumberToDBString( field, in_item );
1919                                 OSRF_BUFFER_ADD( sql_buf, val );
1920                                 free(val);
1921
1922                         } else if ( !strcmp( get_primitive( field ), "number") ) {
1923                                 char* val = jsonNumberToDBString( field, in_item );
1924                                 OSRF_BUFFER_ADD( sql_buf, val );
1925                                 free(val);
1926
1927                         } else {
1928                                 char* key_string = jsonObjectToSimpleString(in_item);
1929                                 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1930                                         OSRF_BUFFER_ADD( sql_buf, key_string );
1931                                         free(key_string);
1932                                 } else {
1933                                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1934                                         free(key_string);
1935                                         buffer_free(sql_buf);
1936                                         return NULL;
1937                                 }
1938                         }
1939                 }
1940
1941                 if( in_item_first ) {
1942                         osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1943                         buffer_free( sql_buf );
1944                         return NULL;
1945                 }
1946         } else {
1947                 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1948                         MODULENAME, json_type( node->type ) );
1949                 buffer_free(sql_buf);
1950                 return NULL;
1951         }
1952
1953         OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1954
1955         return buffer_release(sql_buf);
1956 }
1957
1958 // Receive a JSON_ARRAY representing a function call.  The first
1959 // entry in the array is the function name.  The rest are parameters.
1960 static char* searchValueTransform( const jsonObject* array ) {
1961
1962         if( array->size < 1 ) {
1963                 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
1964                 return NULL;
1965         }
1966
1967         // Get the function name
1968         jsonObject* func_item = jsonObjectGetIndex( array, 0 );
1969         if( func_item->type != JSON_STRING ) {
1970                 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
1971                         MODULENAME, json_type( func_item->type ) );
1972                 return NULL;
1973         }
1974
1975         growing_buffer* sql_buf = buffer_init(32);
1976
1977         OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1978         OSRF_BUFFER_ADD( sql_buf, "( " );
1979
1980         // Get the parameters
1981         int func_item_index = 1;   // We already grabbed the zeroth entry
1982         while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1983
1984                 // Add a separator comma, if we need one
1985                 if( func_item_index > 2 )
1986                         buffer_add( sql_buf, ", " );
1987
1988                 // Add the current parameter
1989                 if (func_item->type == JSON_NULL) {
1990                         buffer_add( sql_buf, "NULL" );
1991                 } else {
1992                         char* val = jsonObjectToSimpleString(func_item);
1993                         if ( dbi_conn_quote_string(dbhandle, &val) ) {
1994                                 OSRF_BUFFER_ADD( sql_buf, val );
1995                                 free(val);
1996                         } else {
1997                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1998                                 buffer_free(sql_buf);
1999                                 free(val);
2000                                 return NULL;
2001                         }
2002                 }
2003         }
2004
2005         buffer_add( sql_buf, " )" );
2006
2007         return buffer_release(sql_buf);
2008 }
2009
2010 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
2011                 const jsonObject* node, const char* op) {
2012
2013         if( ! is_good_operator( op ) ) {
2014                 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2015                 return NULL;
2016         }
2017
2018         char* val = searchValueTransform(node);
2019         if( !val )
2020                 return NULL;
2021
2022         growing_buffer* sql_buf = buffer_init(32);
2023         buffer_fadd(
2024                 sql_buf,
2025                 "\"%s\".%s %s %s",
2026                 class_alias,
2027                 osrfHashGet(field, "name"),
2028                 op,
2029                 val
2030         );
2031
2032         free(val);
2033
2034         return buffer_release(sql_buf);
2035 }
2036
2037 // class_alias is a class name or other table alias
2038 // field is a field definition as stored in the IDL
2039 // node comes from the method parameter, and may represent an entry in the SELECT list
2040 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
2041         growing_buffer* sql_buf = buffer_init(32);
2042
2043         const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
2044         const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
2045
2046         if(transform_subcolumn) {
2047                 if( ! is_identifier( transform_subcolumn ) ) {
2048                         osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2049                                         MODULENAME, transform_subcolumn );
2050                         buffer_free( sql_buf );
2051                         return NULL;
2052                 }
2053                 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' );    // enclose transform in parentheses
2054         }
2055
2056         if (field_transform) {
2057
2058                 if( ! is_identifier( field_transform ) ) {
2059                         osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2060                                         MODULENAME, field_transform );
2061                         buffer_free( sql_buf );
2062                         return NULL;
2063                 }
2064
2065                 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class_alias, osrfHashGet(field, "name"));
2066                 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2067
2068                 if (array) {
2069                         if( array->type != JSON_ARRAY ) {
2070                                 osrfLogError( OSRF_LOG_MARK,
2071                                         "%s: Expected JSON_ARRAY for function params; found %s",
2072                                         MODULENAME, json_type( array->type ) );
2073                                 buffer_free( sql_buf );
2074                                 return NULL;
2075                         }
2076                         int func_item_index = 0;
2077                         jsonObject* func_item;
2078                         while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2079
2080                                 char* val = jsonObjectToSimpleString(func_item);
2081
2082                                 if ( !val ) {
2083                                         buffer_add( sql_buf, ",NULL" );
2084                                 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
2085                                         OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2086                                         OSRF_BUFFER_ADD( sql_buf, val );
2087                                 } else {
2088                                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
2089                                         free(val);
2090                                         buffer_free(sql_buf);
2091                                         return NULL;
2092                         }
2093                                 free(val);
2094                         }
2095                 }
2096
2097                 buffer_add( sql_buf, " )" );
2098
2099         } else {
2100                 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
2101         }
2102
2103         if (transform_subcolumn)
2104                 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2105
2106         return buffer_release(sql_buf);
2107 }
2108
2109 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2110                 const jsonObject* node, const char* op ) {
2111
2112         if( ! is_good_operator( op ) ) {
2113                 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2114                 return NULL;
2115         }
2116
2117         char* field_transform = searchFieldTransform( class_info->alias, field, node );
2118         if( ! field_transform )
2119                 return NULL;
2120         char* value = NULL;
2121         int extra_parens = 0;   // boolean
2122
2123         const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2124         if ( ! value_obj ) {
2125                 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2126                 if( !value ) {
2127                         osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
2128                         free(field_transform);
2129                         return NULL;
2130                 }
2131                 extra_parens = 1;
2132         } else if ( value_obj->type == JSON_ARRAY ) {
2133                 value = searchValueTransform( value_obj );
2134                 if( !value ) {
2135                         osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
2136                         free( field_transform );
2137                         return NULL;
2138                 }
2139         } else if ( value_obj->type == JSON_HASH ) {
2140                 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2141                 if( !value ) {
2142                         osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
2143                         free(field_transform);
2144                         return NULL;
2145                 }
2146                 extra_parens = 1;
2147         } else if ( value_obj->type == JSON_NUMBER ) {
2148                 value = jsonNumberToDBString( field, value_obj );
2149         } else if ( value_obj->type == JSON_NULL ) {
2150                 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
2151                 free(field_transform);
2152                 return NULL;
2153         } else if ( value_obj->type == JSON_BOOL ) {
2154                 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
2155                 free(field_transform);
2156                 return NULL;
2157         } else {
2158                 if ( !strcmp( get_primitive( field ), "number") ) {
2159                         value = jsonNumberToDBString( field, value_obj );
2160                 } else {
2161                         value = jsonObjectToSimpleString( value_obj );
2162                         if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2163                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
2164                                 free(value);
2165                                 free(field_transform);
2166                                 return NULL;
2167                         }
2168                 }
2169         }
2170
2171         const char* left_parens  = "";
2172         const char* right_parens = "";
2173
2174         if( extra_parens ) {
2175                 left_parens  = "(";
2176                 right_parens = ")";
2177         }
2178
2179         growing_buffer* sql_buf = buffer_init(32);
2180
2181         buffer_fadd(
2182                 sql_buf,
2183                 "%s%s %s %s %s %s%s",
2184                 left_parens,
2185                 field_transform,
2186                 op,
2187                 left_parens,
2188                 value,
2189                 right_parens,
2190                 right_parens
2191         );
2192
2193         free(value);
2194         free(field_transform);
2195
2196         return buffer_release(sql_buf);
2197 }
2198
2199 static char* searchSimplePredicate (const char* op, const char* class_alias,
2200                 osrfHash* field, const jsonObject* node) {
2201
2202         if( ! is_good_operator( op ) ) {
2203                 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2204                 return NULL;
2205         }
2206
2207         char* val = NULL;
2208
2209         // Get the value to which we are comparing the specified column
2210         if (node->type != JSON_NULL) {
2211                 if ( node->type == JSON_NUMBER ) {
2212                         val = jsonNumberToDBString( field, node );
2213                 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2214                         val = jsonNumberToDBString( field, node );
2215                 } else {
2216                         val = jsonObjectToSimpleString(node);
2217                 }
2218         }
2219
2220         if( val ) {
2221                 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2222                         // Value is not numeric; enclose it in quotes
2223                         if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2224                                 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2225                                 free( val );
2226                                 return NULL;
2227                         }
2228                 }
2229         } else {
2230                 // Compare to a null value
2231                 val = strdup( "NULL" );
2232                 if (strcmp( op, "=" ))
2233                         op = "IS NOT";
2234                 else
2235                         op = "IS";
2236         }
2237
2238         growing_buffer* sql_buf = buffer_init(32);
2239         buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2240         char* pred = buffer_release( sql_buf );
2241
2242         free(val);
2243
2244         return pred;
2245 }
2246
2247 static char* searchBETWEENPredicate (const char* class_alias,
2248                 osrfHash* field, const jsonObject* node) {
2249
2250         const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2251         const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2252
2253         if( NULL == y_node ) {
2254                 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2255                 return NULL;
2256         }
2257         else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2258                 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2259                 return NULL;
2260         }
2261
2262         char* x_string;
2263         char* y_string;
2264
2265         if ( !strcmp( get_primitive( field ), "number") ) {
2266                 x_string = jsonNumberToDBString(field, x_node);
2267                 y_string = jsonNumberToDBString(field, y_node);
2268
2269         } else {
2270                 x_string = jsonObjectToSimpleString(x_node);
2271                 y_string = jsonObjectToSimpleString(y_node);
2272                 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2273                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2274                                         MODULENAME, x_string, y_string);
2275                         free(x_string);
2276                         free(y_string);
2277                         return NULL;
2278                 }
2279         }
2280
2281         growing_buffer* sql_buf = buffer_init(32);
2282         buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2283                         class_alias, osrfHashGet(field, "name"), x_string, y_string );
2284         free(x_string);
2285         free(y_string);
2286
2287         return buffer_release(sql_buf);
2288 }
2289
2290 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2291                                                            jsonObject* node, osrfMethodContext* ctx ) {
2292
2293         char* pred = NULL;
2294         if (node->type == JSON_ARRAY) { // equality IN search
2295                 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2296         } else if (node->type == JSON_HASH) { // other search
2297                 jsonIterator* pred_itr = jsonNewIterator( node );
2298                 if( !jsonIteratorHasNext( pred_itr ) ) {
2299                         osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2300                                         MODULENAME, osrfHashGet(field, "name") );
2301                 } else {
2302                         jsonObject* pred_node = jsonIteratorNext( pred_itr );
2303
2304                         // Verify that there are no additional predicates
2305                         if( jsonIteratorHasNext( pred_itr ) ) {
2306                                 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2307                                                 MODULENAME, osrfHashGet(field, "name") );
2308                         } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2309                                 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2310                         else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2311                                 pred = searchINPredicate( class_info->alias, field, pred_node, pred_itr->key, ctx );
2312                         else if ( pred_node->type == JSON_ARRAY )
2313                                 pred = searchFunctionPredicate( class_info->alias, field, pred_node, pred_itr->key );
2314                         else if ( pred_node->type == JSON_HASH )
2315                                 pred = searchFieldTransformPredicate( class_info, field, pred_node, pred_itr->key );
2316                         else
2317                                 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2318                 }
2319                 jsonIteratorFree(pred_itr);
2320
2321         } else if (node->type == JSON_NULL) { // IS NULL search
2322                 growing_buffer* _p = buffer_init(64);
2323                 buffer_fadd(
2324                         _p,
2325                         "\"%s\".%s IS NULL",
2326                         class_info->class_name,
2327                         osrfHashGet(field, "name")
2328                 );
2329                 pred = buffer_release(_p);
2330         } else { // equality search
2331                 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2332         }
2333
2334         return pred;
2335
2336 }
2337
2338
2339 /*
2340
2341 join : {
2342         acn : {
2343                 field : record,
2344                 fkey : id
2345                 type : left
2346                 filter_op : or
2347                 filter : { ... },
2348                 join : {
2349                         acp : {
2350                                 field : call_number,
2351                                 fkey : id,
2352                                 filter : { ... },
2353                         },
2354                 },
2355         },
2356         mrd : {
2357                 field : record,
2358                 type : inner
2359                 fkey : id,
2360                 filter : { ... },
2361         }
2362 }
2363
2364 */
2365
2366 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2367
2368         const jsonObject* working_hash;
2369         jsonObject* freeable_hash = NULL;
2370
2371         if (join_hash->type == JSON_HASH) {
2372                 working_hash = join_hash;
2373         } else if (join_hash->type == JSON_STRING) {
2374                 // turn it into a JSON_HASH by creating a wrapper
2375                 // around a copy of the original
2376                 const char* _tmp = jsonObjectGetString( join_hash );
2377                 freeable_hash = jsonNewObjectType(JSON_HASH);
2378                 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2379                 working_hash = freeable_hash;
2380         } else {
2381                 osrfLogError(
2382                         OSRF_LOG_MARK,
2383                         "%s: JOIN failed; expected JSON object type not found",
2384                         MODULENAME
2385                 );
2386                 return NULL;
2387         }
2388
2389         growing_buffer* join_buf = buffer_init(128);
2390         const char* leftclass = left_info->class_name;
2391
2392         jsonObject* snode = NULL;
2393         jsonIterator* search_itr = jsonNewIterator( working_hash );
2394
2395         while ( (snode = jsonIteratorNext( search_itr )) ) {
2396                 const char* right_alias = search_itr->key;
2397                 const char* class =
2398                                 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2399                 if( ! class )
2400                         class = right_alias;
2401
2402                 const ClassInfo* right_info = add_joined_class( right_alias, class );
2403                 if( !right_info ) {
2404                         osrfLogError(
2405                                 OSRF_LOG_MARK,
2406                                 "%s: JOIN failed.  Class \"%s\" not resolved in IDL",
2407                                 MODULENAME,
2408                                 search_itr->key
2409                         );
2410                         jsonIteratorFree( search_itr );
2411                         buffer_free( join_buf );
2412                         if( freeable_hash )
2413                                 jsonObjectFree( freeable_hash );
2414                         return NULL;
2415                 }
2416                 osrfHash* links    = right_info->links;
2417                 const char* table  = right_info->source_def;
2418
2419                 const char* fkey  = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2420                 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2421
2422                 if (field && !fkey) {
2423                         // Look up the corresponding join column in the IDL.
2424                         // The link must be defined in the child table,
2425                         // and point to the right parent table.
2426                         osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2427                         const char* reltype = NULL;
2428                         const char* other_class = NULL;
2429                         reltype = osrfHashGet( idl_link, "reltype" );
2430                         if( reltype && strcmp( reltype, "has_many" ) )
2431                                 other_class = osrfHashGet( idl_link, "class" );
2432                         if( other_class && !strcmp( other_class, leftclass ) )
2433                                 fkey = osrfHashGet( idl_link, "key" );
2434                         if (!fkey) {
2435                                 osrfLogError(
2436                                         OSRF_LOG_MARK,
2437                                         "%s: JOIN failed.  No link defined from %s.%s to %s",
2438                                         MODULENAME,
2439                                         class,
2440                                         field,
2441                                         leftclass
2442                                 );
2443                                 buffer_free(join_buf);
2444                                 if(freeable_hash)
2445                                         jsonObjectFree(freeable_hash);
2446                                 jsonIteratorFree(search_itr);
2447                                 return NULL;
2448                         }
2449
2450                 } else if (!field && fkey) {
2451                         // Look up the corresponding join column in the IDL.
2452                         // The link must be defined in the child table,
2453                         // and point to the right parent table.
2454                         osrfHash* left_links = left_info->links;
2455                         osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2456                         const char* reltype = NULL;
2457                         const char* other_class = NULL;
2458                         reltype = osrfHashGet( idl_link, "reltype" );
2459                         if( reltype && strcmp( reltype, "has_many" ) )
2460                                 other_class = osrfHashGet( idl_link, "class" );
2461                         if( other_class && !strcmp( other_class, class ) )
2462                                 field = osrfHashGet( idl_link, "key" );
2463                         if (!field) {
2464                                 osrfLogError(
2465                                         OSRF_LOG_MARK,
2466                                         "%s: JOIN failed.  No link defined from %s.%s to %s",
2467                                         MODULENAME,
2468                                         leftclass,
2469                                         fkey,
2470                                         class
2471                                 );
2472                                 buffer_free(join_buf);
2473                                 if(freeable_hash)
2474                                         jsonObjectFree(freeable_hash);
2475                                 jsonIteratorFree(search_itr);
2476                                 return NULL;
2477                         }
2478
2479                 } else if (!field && !fkey) {
2480                         osrfHash* left_links = left_info->links;
2481
2482                         // For each link defined for the left class:
2483                         // see if the link references the joined class
2484                         osrfHashIterator* itr = osrfNewHashIterator( left_links );
2485                         osrfHash* curr_link = NULL;
2486                         while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2487                                 const char* other_class = osrfHashGet( curr_link, "class" );
2488                                 if( other_class && !strcmp( other_class, class ) ) {
2489
2490                                         // In the IDL, the parent class doesn't know then names of the child
2491                                         // columns that are pointing to it, so don't use that end of the link
2492                                         const char* reltype = osrfHashGet( curr_link, "reltype" );
2493                                         if( reltype && strcmp( reltype, "has_many" ) ) {
2494                                                 // Found a link between the classes
2495                                                 fkey = osrfHashIteratorKey( itr );
2496                                                 field = osrfHashGet( curr_link, "key" );
2497                                                 break;
2498                                         }
2499                                 }
2500                         }
2501                         osrfHashIteratorFree( itr );
2502
2503                         if (!field || !fkey) {
2504                                 // Do another such search, with the classes reversed
2505
2506                                 // For each link defined for the joined class:
2507                                 // see if the link references the left class
2508                                 osrfHashIterator* itr = osrfNewHashIterator( links );
2509                                 osrfHash* curr_link = NULL;
2510                                 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2511                                         const char* other_class = osrfHashGet( curr_link, "class" );
2512                                         if( other_class && !strcmp( other_class, leftclass ) ) {
2513
2514                                                 // In the IDL, the parent class doesn't know then names of the child
2515                                                 // columns that are pointing to it, so don't use that end of the link
2516                                                 const char* reltype = osrfHashGet( curr_link, "reltype" );
2517                                                 if( reltype && strcmp( reltype, "has_many" ) ) {
2518                                                         // Found a link between the classes
2519                                                         field = osrfHashIteratorKey( itr );
2520                                                         fkey = osrfHashGet( curr_link, "key" );
2521                                                         break;
2522                                                 }
2523                                         }
2524                                 }
2525                                 osrfHashIteratorFree( itr );
2526                         }
2527
2528                         if (!field || !fkey) {
2529                                 osrfLogError(
2530                                         OSRF_LOG_MARK,
2531                                         "%s: JOIN failed.  No link defined between %s and %s",
2532                                         MODULENAME,
2533                                         leftclass,
2534                                         class
2535                                 );
2536                                 buffer_free(join_buf);
2537                                 if(freeable_hash)
2538                                         jsonObjectFree(freeable_hash);
2539                                 jsonIteratorFree(search_itr);
2540                                 return NULL;
2541                         }
2542
2543                 }
2544
2545                 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2546                 if (type) {
2547                         if ( !strcasecmp(type,"left") ) {
2548                                 buffer_add(join_buf, " LEFT JOIN");
2549                         } else if ( !strcasecmp(type,"right") ) {
2550                                 buffer_add(join_buf, " RIGHT JOIN");
2551                         } else if ( !strcasecmp(type,"full") ) {
2552                                 buffer_add(join_buf, " FULL JOIN");
2553                         } else {
2554                                 buffer_add(join_buf, " INNER JOIN");
2555                         }
2556                 } else {
2557                         buffer_add(join_buf, " INNER JOIN");
2558                 }
2559
2560                 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2561                                         table, right_alias, right_alias, field, left_info->alias, fkey);
2562
2563                 // Add any other join conditions as specified by "filter"
2564                 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2565                 if (filter) {
2566                         const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2567                         if ( filter_op && !strcasecmp("or",filter_op) ) {
2568                                 buffer_add( join_buf, " OR " );
2569                         } else {
2570                                 buffer_add( join_buf, " AND " );
2571                         }
2572
2573                         char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2574                         if( jpred ) {
2575                                 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2576                                 OSRF_BUFFER_ADD( join_buf, jpred );
2577                                 free(jpred);
2578                         } else {
2579                                 osrfLogError(
2580                                         OSRF_LOG_MARK,
2581                                         "%s: JOIN failed.  Invalid conditional expression.",
2582                                         MODULENAME
2583                                 );
2584                                 jsonIteratorFree( search_itr );
2585                                 buffer_free( join_buf );
2586                                 if( freeable_hash )
2587                                         jsonObjectFree( freeable_hash );
2588                                 return NULL;
2589                         }
2590                 }
2591
2592                 buffer_add(join_buf, " ) ");
2593
2594                 // Recursively add a nested join, if one is present
2595                 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2596                 if (join_filter) {
2597                         char* jpred = searchJOIN( join_filter, right_info );
2598                         if( jpred ) {
2599                                 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2600                                 OSRF_BUFFER_ADD( join_buf, jpred );
2601                                 free(jpred);
2602                         } else {
2603                                 osrfLogError(  OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2604                                 jsonIteratorFree( search_itr );
2605                                 buffer_free( join_buf );
2606                                 if( freeable_hash )
2607                                         jsonObjectFree( freeable_hash );
2608                                 return NULL;
2609                         }
2610                 }
2611         }
2612
2613         if(freeable_hash)
2614                 jsonObjectFree(freeable_hash);
2615         jsonIteratorFree(search_itr);
2616
2617         return buffer_release(join_buf);
2618 }
2619
2620 /*
2621
2622 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2623 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2624 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2625
2626 Generate code to express a set of conditions, as for a WHERE clause.  Parameters:
2627
2628 search_hash is the JSON expression of the conditions.
2629 meta is the class definition from the IDL, for the relevant table.
2630 opjoin_type indicates whether multiple conditions, if present, should be
2631         connected by AND or OR.
2632 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2633         to pass it to other functions -- and all they do with it is to use the session
2634         and request members to send error messages back to the client.
2635
2636 */
2637
2638 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
2639                 int opjoin_type, osrfMethodContext* ctx ) {
2640
2641         osrfLogDebug(
2642                 OSRF_LOG_MARK,
2643                 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2644                 MODULENAME,
2645                 search_hash,
2646                 class_info->class_def,
2647                 opjoin_type,
2648                 ctx
2649         );
2650
2651         growing_buffer* sql_buf = buffer_init(128);
2652
2653         jsonObject* node = NULL;
2654
2655         int first = 1;
2656         if ( search_hash->type == JSON_ARRAY ) {
2657                 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2658                 if( 0 == search_hash->size ) {
2659                         osrfLogError(
2660                                 OSRF_LOG_MARK,
2661                                 "%s: Invalid predicate structure: empty JSON array",
2662                                 MODULENAME
2663                         );
2664                         buffer_free( sql_buf );
2665                         return NULL;
2666                 }
2667
2668                 unsigned long i = 0;
2669                 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
2670                         if (first) {
2671                                 first = 0;
2672                         } else {
2673                                 if (opjoin_type == OR_OP_JOIN)
2674                                         buffer_add(sql_buf, " OR ");
2675                                 else
2676                                         buffer_add(sql_buf, " AND ");
2677                         }
2678
2679                         char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2680                         if( ! subpred ) {
2681                                 buffer_free( sql_buf );
2682                                 return NULL;
2683                         }
2684
2685                         buffer_fadd(sql_buf, "( %s )", subpred);
2686                         free(subpred);
2687                 }
2688
2689         } else if ( search_hash->type == JSON_HASH ) {
2690                 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2691                 jsonIterator* search_itr = jsonNewIterator( search_hash );
2692                 if( !jsonIteratorHasNext( search_itr ) ) {
2693                         osrfLogError(
2694                                 OSRF_LOG_MARK,
2695                                 "%s: Invalid predicate structure: empty JSON object",
2696                                 MODULENAME
2697                         );
2698                         jsonIteratorFree( search_itr );
2699                         buffer_free( sql_buf );
2700                         return NULL;
2701                 }
2702
2703                 while ( (node = jsonIteratorNext( search_itr )) ) {
2704
2705                         if (first) {
2706                                 first = 0;
2707                         } else {
2708                                 if (opjoin_type == OR_OP_JOIN)
2709                                         buffer_add(sql_buf, " OR ");
2710                                 else
2711                                         buffer_add(sql_buf, " AND ");
2712                         }
2713
2714                         if ( '+' == search_itr->key[ 0 ] ) {
2715
2716                                 // This plus sign prefixes a class name or other table alias;
2717                                 // make sure the table alias is in scope
2718                                 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
2719                                 if( ! alias_info ) {
2720                                         osrfLogError(
2721                                                          OSRF_LOG_MARK,
2722                                                         "%s: Invalid table alias \"%s\" in WHERE clause",
2723                                                         MODULENAME,
2724                                                         search_itr->key + 1
2725                                         );
2726                                         jsonIteratorFree( search_itr );
2727                                         buffer_free( sql_buf );
2728                                         return NULL;
2729                                 }
2730
2731                                 if ( node->type == JSON_STRING ) {
2732                                         // It's the name of a column; make sure it belongs to the class
2733                                         const char* fieldname = jsonObjectGetString( node );
2734                                         if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
2735                                                 osrfLogError(
2736                                                         OSRF_LOG_MARK,
2737                                                         "%s: Invalid column name \"%s\" in WHERE clause for table alias \"%s\"",
2738                                                         MODULENAME,
2739                                                         fieldname,
2740                                                         alias_info->alias
2741                                                 );
2742                                                 jsonIteratorFree( search_itr );
2743                                                 buffer_free( sql_buf );
2744                                                 return NULL;
2745                                         }
2746
2747                                         buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
2748                                 } else {
2749                                         // It's something more complicated
2750                                         char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
2751                                         if( ! subpred ) {
2752                                                 jsonIteratorFree( search_itr );
2753                                                 buffer_free( sql_buf );
2754                                                 return NULL;
2755                                         }
2756
2757                                         buffer_fadd(sql_buf, "( %s )", subpred);
2758                                         free(subpred);
2759                                 }
2760                         } else if ( '-' == search_itr->key[ 0 ] ) {
2761                                 if ( !strcasecmp("-or",search_itr->key) ) {
2762                                         char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
2763                                         if( ! subpred ) {
2764                                                 jsonIteratorFree( search_itr );
2765                                                 buffer_free( sql_buf );
2766                                                 return NULL;
2767                                         }
2768
2769                                         buffer_fadd(sql_buf, "( %s )", subpred);
2770                                         free( subpred );
2771                                 } else if ( !strcasecmp("-and",search_itr->key) ) {
2772                                         char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2773                                         if( ! subpred ) {
2774                                                 jsonIteratorFree( search_itr );
2775                                                 buffer_free( sql_buf );
2776                                                 return NULL;
2777                                         }
2778
2779                                         buffer_fadd(sql_buf, "( %s )", subpred);
2780                                         free( subpred );
2781                                 } else if ( !strcasecmp("-not",search_itr->key) ) {
2782                                         char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2783                                         if( ! subpred ) {
2784                                                 jsonIteratorFree( search_itr );
2785                                                 buffer_free( sql_buf );
2786                                                 return NULL;
2787                                         }
2788
2789                                         buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2790                                         free( subpred );
2791                                 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2792                                         char* subpred = buildQuery( ctx, node, SUBSELECT );
2793                                         if( ! subpred ) {
2794                                                 jsonIteratorFree( search_itr );
2795                                                 buffer_free( sql_buf );
2796                                                 return NULL;
2797                                         }
2798
2799                                         buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2800                                         free(subpred);
2801                                 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2802                                         char* subpred = buildQuery( ctx, node, SUBSELECT );
2803                                         if( ! subpred ) {
2804                                                 jsonIteratorFree( search_itr );
2805                                                 buffer_free( sql_buf );
2806                                                 return NULL;
2807                                         }
2808
2809                                         buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2810                                         free(subpred);
2811                                 } else {     // Invalid "minus" operator
2812                                         osrfLogError(
2813                                                          OSRF_LOG_MARK,
2814                                                         "%s: Invalid operator \"%s\" in WHERE clause",
2815                                                         MODULENAME,
2816                                                         search_itr->key
2817                                         );
2818                                         jsonIteratorFree( search_itr );
2819                                         buffer_free( sql_buf );
2820                                         return NULL;
2821                                 }
2822
2823                         } else {
2824
2825                                 const char* class = class_info->class_name;
2826                                 osrfHash* fields = class_info->fields;
2827                                 osrfHash* field = osrfHashGet( fields, search_itr->key );
2828
2829                                 if (!field) {
2830                                         const char* table = class_info->source_def;
2831                                         osrfLogError(
2832                                                 OSRF_LOG_MARK,
2833                                                 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2834                                                 MODULENAME,
2835                                                 search_itr->key,
2836                                                 table ? table : "?",
2837                                                 class ? class : "?"
2838                                         );
2839                                         jsonIteratorFree(search_itr);
2840                                         buffer_free(sql_buf);
2841                                         return NULL;
2842                                 }
2843
2844                                 char* subpred = searchPredicate( class_info, field, node, ctx );
2845                                 if( ! subpred ) {
2846                                         buffer_free(sql_buf);
2847                                         jsonIteratorFree(search_itr);
2848                                         return NULL;
2849                                 }
2850
2851                                 buffer_add( sql_buf, subpred );
2852                                 free(subpred);
2853                         }
2854                 }
2855                 jsonIteratorFree(search_itr);
2856
2857     } else {
2858         // ERROR ... only hash and array allowed at this level
2859         char* predicate_string = jsonObjectToJSON( search_hash );
2860         osrfLogError(
2861             OSRF_LOG_MARK,
2862             "%s: Invalid predicate structure: %s",
2863             MODULENAME,
2864             predicate_string
2865         );
2866         buffer_free(sql_buf);
2867         free(predicate_string);
2868         return NULL;
2869     }
2870
2871         return buffer_release(sql_buf);
2872 }
2873
2874 /* Build a JSON_ARRAY of field names for a given table alias
2875  */
2876 static jsonObject* defaultSelectList( const char* table_alias ) {
2877
2878         if( ! table_alias )
2879                 table_alias = "";
2880
2881         ClassInfo* class_info = search_all_alias( table_alias );
2882         if( ! class_info ) {
2883                 osrfLogError(
2884                         OSRF_LOG_MARK,
2885                         "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
2886                         MODULENAME,
2887                         table_alias
2888                 );
2889                 return NULL;
2890         }
2891
2892         jsonObject* array = jsonNewObjectType( JSON_ARRAY );
2893         osrfHash* field_def = NULL;
2894         osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
2895         while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2896                 const char* field_name = osrfHashIteratorKey( field_itr );
2897                 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2898                         jsonObjectPush( array, jsonNewObject( field_name ) );
2899                 }
2900         }
2901         osrfHashIteratorFree( field_itr );
2902
2903         return array;
2904 }
2905
2906 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
2907 // The jsonObject must be a JSON_HASH with an single entry for "union",
2908 // "intersect", or "except".  The data associated with this key must be an
2909 // array of hashes, each hash being a query.
2910 // Also allowed but currently ignored: entries for "order_by" and "alias".
2911 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
2912         // Sanity check
2913         if( ! combo || combo->type != JSON_HASH )
2914                 return NULL;      // should be impossible; validated by caller
2915
2916         const jsonObject* query_array = NULL;   // array of subordinate queries
2917         const char* op = NULL;     // name of operator, e.g. UNION
2918         const char* alias = NULL;  // alias for the query (needed for ORDER BY)
2919         int op_count = 0;          // for detecting conflicting operators
2920         int excepting = 0;         // boolean
2921         int all = 0;               // boolean
2922         jsonObject* order_obj = NULL;
2923
2924         // Identify the elements in the hash
2925         jsonIterator* query_itr = jsonNewIterator( combo );
2926         jsonObject* curr_obj = NULL;
2927         while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
2928                 if( ! strcmp( "union", query_itr->key ) ) {
2929                         ++op_count;
2930                         op = " UNION ";
2931                         query_array = curr_obj;
2932                 } else if( ! strcmp( "intersect", query_itr->key ) ) {
2933                         ++op_count;
2934                         op = " INTERSECT ";
2935                         query_array = curr_obj;
2936                 } else if( ! strcmp( "except", query_itr->key ) ) {
2937                         ++op_count;
2938                         op = " EXCEPT ";
2939                         excepting = 1;
2940                         query_array = curr_obj;
2941                 } else if( ! strcmp( "order_by", query_itr->key ) ) {
2942                         osrfLogWarning(
2943                                 OSRF_LOG_MARK,
2944                                 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
2945                                 MODULENAME
2946                         );
2947                         order_obj = curr_obj;
2948                 } else if( ! strcmp( "alias", query_itr->key ) ) {
2949                         if( curr_obj->type != JSON_STRING ) {
2950                                 jsonIteratorFree( query_itr );
2951                                 return NULL;
2952                         }
2953                         alias = jsonObjectGetString( curr_obj );
2954                 } else if( ! strcmp( "all", query_itr->key ) ) {
2955                         if( obj_is_true( curr_obj ) )
2956                                 all = 1;
2957                 } else {
2958                         if( ctx )
2959                                 osrfAppSessionStatus(
2960                                         ctx->session,
2961                                         OSRF_STATUS_INTERNALSERVERERROR,
2962                                         "osrfMethodException",
2963                                         ctx->request,
2964                                         "Malformed query; unexpected entry in query object"
2965                                 );
2966                         osrfLogError(
2967                                 OSRF_LOG_MARK,
2968                                 "%s: Unexpected entry for \"%s\" in%squery",
2969                                 MODULENAME,
2970                                 query_itr->key,
2971                                 op
2972                         );
2973                         jsonIteratorFree( query_itr );
2974                         return NULL;
2975                 }
2976         }
2977         jsonIteratorFree( query_itr );
2978
2979         // More sanity checks
2980         if( ! query_array ) {
2981                 if( ctx )
2982                         osrfAppSessionStatus(
2983                                 ctx->session,
2984                                 OSRF_STATUS_INTERNALSERVERERROR,
2985                                 "osrfMethodException",
2986                                 ctx->request,
2987                                 "Expected UNION, INTERSECT, or EXCEPT operator not found"
2988                         );
2989                 osrfLogError(
2990                         OSRF_LOG_MARK,
2991                         "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
2992                         MODULENAME
2993                 );
2994                 return NULL;        // should be impossible...
2995         } else if( op_count > 1 ) {
2996                 if( ctx )
2997                                 osrfAppSessionStatus(
2998                                 ctx->session,
2999                                 OSRF_STATUS_INTERNALSERVERERROR,
3000                                 "osrfMethodException",
3001                                 ctx->request,
3002                                 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3003                         );
3004                 osrfLogError(
3005                         OSRF_LOG_MARK,
3006                         "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3007                         MODULENAME
3008                 );
3009                 return NULL;
3010         } if( query_array->type != JSON_ARRAY ) {
3011                 if( ctx )
3012                                 osrfAppSessionStatus(
3013                                 ctx->session,
3014                                 OSRF_STATUS_INTERNALSERVERERROR,
3015                                 "osrfMethodException",
3016                                 ctx->request,
3017                                 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3018                         );
3019                 osrfLogError(
3020                         OSRF_LOG_MARK,
3021                         "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3022                         MODULENAME,
3023                         op,
3024                         json_type( query_array->type )
3025                 );
3026                 return NULL;
3027         } if( query_array->size < 2 ) {
3028                 if( ctx )
3029                         osrfAppSessionStatus(
3030                                 ctx->session,
3031                                 OSRF_STATUS_INTERNALSERVERERROR,
3032                                 "osrfMethodException",
3033                                 ctx->request,
3034                                 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3035                         );
3036                 osrfLogError(
3037                         OSRF_LOG_MARK,
3038                         "%s:%srequires multiple queries as operands",
3039                         MODULENAME,
3040                         op
3041                 );
3042                 return NULL;
3043         } else if( excepting && query_array->size > 2 ) {
3044                 if( ctx )
3045                         osrfAppSessionStatus(
3046                                 ctx->session,
3047                                 OSRF_STATUS_INTERNALSERVERERROR,
3048                                 "osrfMethodException",
3049                                 ctx->request,
3050                                 "EXCEPT operator has too many queries as operands"
3051                         );
3052                 osrfLogError(
3053                         OSRF_LOG_MARK,
3054                         "%s:EXCEPT operator has too many queries as operands",
3055                         MODULENAME
3056                 );
3057                 return NULL;
3058         } else if( order_obj && ! alias ) {
3059                 if( ctx )
3060                         osrfAppSessionStatus(
3061                                 ctx->session,
3062                                 OSRF_STATUS_INTERNALSERVERERROR,
3063                                 "osrfMethodException",
3064                                 ctx->request,
3065                                 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3066                         );
3067                 osrfLogError(
3068                         OSRF_LOG_MARK,
3069                         "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3070                         MODULENAME
3071                 );
3072                 return NULL;
3073         }
3074
3075         // So far so good.  Now build the SQL.
3076         growing_buffer* sql = buffer_init( 256 );
3077
3078         // If we nested inside another UNION, INTERSECT, or EXCEPT,
3079         // Add a layer of parentheses
3080         if( flags & SUBCOMBO )
3081                 OSRF_BUFFER_ADD( sql, "( " );
3082
3083         // Traverse the query array.  Each entry should be a hash.
3084         int first = 1;   // boolean
3085         int i = 0;
3086         jsonObject* query = NULL;
3087         while((query = jsonObjectGetIndex( query_array, i++ ) )) {
3088                 if( query->type != JSON_HASH ) {
3089                         if( ctx )
3090                                 osrfAppSessionStatus(
3091                                         ctx->session,
3092                                         OSRF_STATUS_INTERNALSERVERERROR,
3093                                         "osrfMethodException",
3094                                         ctx->request,
3095                                         "Malformed query under UNION, INTERSECT or EXCEPT"
3096                                 );
3097                         osrfLogError(
3098                                 OSRF_LOG_MARK,
3099                                 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3100                                 MODULENAME,
3101                                 op,
3102                                 json_type( query->type )
3103                         );
3104                         buffer_free( sql );
3105                         return NULL;
3106                 }
3107
3108                 if( first )
3109                         first = 0;
3110                 else {
3111                         OSRF_BUFFER_ADD( sql, op );
3112                         if( all )
3113                                 OSRF_BUFFER_ADD( sql, "ALL " );
3114                 }
3115
3116                 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3117                 if( ! query_str ) {
3118                         osrfLogError(
3119                                 OSRF_LOG_MARK,
3120                                 "%s: Error building query under%s",
3121                                 MODULENAME,
3122                                 op
3123                         );
3124                         buffer_free( sql );
3125                         return NULL;
3126                 }
3127
3128                 OSRF_BUFFER_ADD( sql, query_str );
3129         }
3130
3131         if( flags & SUBCOMBO )
3132                 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3133
3134         if ( !(flags & SUBSELECT) )
3135                 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3136
3137         return buffer_release( sql );
3138 }
3139
3140 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3141 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3142 // or "except" to indicate the type of query.
3143 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3144         // Sanity checks
3145         if( ! query ) {
3146                 if( ctx )
3147                         osrfAppSessionStatus(
3148                                 ctx->session,
3149                                 OSRF_STATUS_INTERNALSERVERERROR,
3150                                 "osrfMethodException",
3151                                 ctx->request,
3152                                 "Malformed query; no query object"
3153                         );
3154                 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", MODULENAME );
3155                 return NULL;
3156         } else if( query->type != JSON_HASH ) {
3157                 if( ctx )
3158                         osrfAppSessionStatus(
3159                                 ctx->session,
3160                                 OSRF_STATUS_INTERNALSERVERERROR,
3161                                 "osrfMethodException",
3162                                 ctx->request,
3163                                 "Malformed query object"
3164                         );
3165                 osrfLogError(
3166                         OSRF_LOG_MARK,
3167                         "%s: Query object is %s instead of JSON_HASH",
3168                         MODULENAME,
3169                         json_type( query->type )
3170                 );
3171                 return NULL;
3172         }
3173
3174         // Determine what kind of query it purports to be, and dispatch accordingly.
3175         if( jsonObjectGetKey( query, "union" ) ||
3176                 jsonObjectGetKey( query, "intersect" ) ||
3177                 jsonObjectGetKey( query, "except" ) ) {
3178                 return doCombo( ctx, query, flags );
3179         } else {
3180                 // It is presumably a SELECT query
3181
3182                 // Push a node onto the stack for the current query.  Every level of
3183                 // subquery gets its own QueryFrame on the Stack.
3184                 push_query_frame();
3185
3186                 // Build an SQL SELECT statement
3187                 char* sql = SELECT(
3188                         ctx,
3189                         jsonObjectGetKey( query, "select" ),
3190                         jsonObjectGetKey( query, "from" ),
3191                         jsonObjectGetKey( query, "where" ),
3192                         jsonObjectGetKey( query, "having" ),
3193                         jsonObjectGetKey( query, "order_by" ),
3194                         jsonObjectGetKey( query, "limit" ),
3195                         jsonObjectGetKey( query, "offset" ),
3196                         flags
3197                 );
3198                 pop_query_frame();
3199                 return sql;
3200         }
3201 }
3202
3203 char* SELECT (
3204                 /* method context */ osrfMethodContext* ctx,
3205
3206                 /* SELECT   */ jsonObject* selhash,
3207                 /* FROM     */ jsonObject* join_hash,
3208                 /* WHERE    */ jsonObject* search_hash,
3209                 /* HAVING   */ jsonObject* having_hash,
3210                 /* ORDER BY */ jsonObject* order_hash,
3211                 /* LIMIT    */ jsonObject* limit,
3212                 /* OFFSET   */ jsonObject* offset,
3213                 /* flags    */ int flags
3214 ) {
3215         const char* locale = osrf_message_get_last_locale();
3216
3217         // general tmp objects
3218         const jsonObject* tmp_const;
3219         jsonObject* selclass = NULL;
3220         jsonObject* snode = NULL;
3221         jsonObject* onode = NULL;
3222
3223         char* string = NULL;
3224         int from_function = 0;
3225         int first = 1;
3226         int gfirst = 1;
3227         //int hfirst = 1;
3228
3229         osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3230
3231         // punt if there's no FROM clause
3232         if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3233                 osrfLogError(
3234                         OSRF_LOG_MARK,
3235                         "%s: FROM clause is missing or empty",
3236                         MODULENAME
3237                 );
3238                 if( ctx )
3239                         osrfAppSessionStatus(
3240                                 ctx->session,
3241                                 OSRF_STATUS_INTERNALSERVERERROR,
3242                                 "osrfMethodException",
3243                                 ctx->request,
3244                                 "FROM clause is missing or empty in JSON query"
3245                         );
3246                 return NULL;
3247         }
3248
3249         // the core search class
3250         const char* core_class = NULL;
3251
3252         // get the core class -- the only key of the top level FROM clause, or a string
3253         if (join_hash->type == JSON_HASH) {
3254                 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3255                 snode = jsonIteratorNext( tmp_itr );
3256
3257                 // Populate the current QueryFrame with information
3258                 // about the core class
3259                 if( add_query_core( NULL, tmp_itr->key ) ) {
3260                         if( ctx )
3261                                 osrfAppSessionStatus(
3262                                         ctx->session,
3263                                         OSRF_STATUS_INTERNALSERVERERROR,
3264                                         "osrfMethodException",
3265                                         ctx->request,
3266                                         "Unable to look up core class"
3267                                 );
3268                         return NULL;
3269                 }
3270                 core_class = curr_query->core.class_name;
3271                 join_hash = snode;
3272
3273                 jsonObject* extra = jsonIteratorNext( tmp_itr );
3274
3275                 jsonIteratorFree( tmp_itr );
3276                 snode = NULL;
3277
3278                 // There shouldn't be more than one entry in join_hash
3279                 if( extra ) {
3280                         osrfLogError(
3281                                 OSRF_LOG_MARK,
3282                                 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3283                                 MODULENAME
3284                         );
3285                         if( ctx )
3286                                 osrfAppSessionStatus(
3287                                         ctx->session,
3288                                         OSRF_STATUS_INTERNALSERVERERROR,
3289                                         "osrfMethodException",
3290                                         ctx->request,
3291                                         "Malformed FROM clause in JSON query"
3292                                 );
3293                         return NULL;    // Malformed join_hash; extra entry
3294                 }
3295         } else if (join_hash->type == JSON_ARRAY) {
3296                 // We're selecting from a function, not from a table
3297                 from_function = 1;
3298                 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
3299                 selhash = NULL;
3300
3301         } else if (join_hash->type == JSON_STRING) {
3302                 // Populate the current QueryFrame with information
3303                 // about the core class
3304                 core_class = jsonObjectGetString( join_hash );
3305                 join_hash = NULL;
3306                 if( add_query_core( NULL, core_class ) ) {
3307                         if( ctx )
3308                                 osrfAppSessionStatus(
3309                                         ctx->session,
3310                                         OSRF_STATUS_INTERNALSERVERERROR,
3311                                         "osrfMethodException",
3312                                         ctx->request,
3313                                         "Unable to look up core class"
3314                                 );
3315                         return NULL;
3316                 }
3317         }
3318         else {
3319                 osrfLogError(
3320                         OSRF_LOG_MARK,
3321                         "%s: FROM clause is unexpected JSON type: %s",
3322                         MODULENAME,
3323                         json_type( join_hash->type )
3324                 );
3325                 if( ctx )
3326                         osrfAppSessionStatus(
3327                                 ctx->session,
3328                                 OSRF_STATUS_INTERNALSERVERERROR,
3329                                 "osrfMethodException",
3330                                 ctx->request,
3331                                 "Ill-formed FROM clause in JSON query"
3332                         );
3333                 return NULL;
3334         }
3335
3336         // Build the join clause, if any, while filling out the list
3337         // of joined classes in the current QueryFrame.
3338         char* join_clause = NULL;
3339         if( join_hash && ! from_function ) {
3340
3341                 join_clause = searchJOIN( join_hash, &curr_query->core );
3342                 if( ! join_clause ) {
3343                         if (ctx)
3344                                 osrfAppSessionStatus(
3345                                         ctx->session,
3346                                         OSRF_STATUS_INTERNALSERVERERROR,
3347                                         "osrfMethodException",
3348                                         ctx->request,
3349                                         "Unable to construct JOIN clause(s)"
3350                                 );
3351                         return NULL;
3352                 }
3353         }
3354
3355         // For in case we don't get a select list
3356         jsonObject* defaultselhash = NULL;
3357
3358         // if there is no select list, build a default select list ...
3359         if (!selhash && !from_function) {
3360                 jsonObject* default_list = defaultSelectList( core_class );
3361                 if( ! default_list ) {
3362                         if (ctx) {
3363                                 osrfAppSessionStatus(
3364                                         ctx->session,
3365                                         OSRF_STATUS_INTERNALSERVERERROR,
3366                                         "osrfMethodException",
3367                                         ctx->request,
3368                                         "Unable to build default SELECT clause in JSON query"
3369                                 );
3370                                 free( join_clause );
3371                                 return NULL;
3372                         }
3373                 }
3374
3375                 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
3376                 jsonObjectSetKey( selhash, core_class, default_list );
3377         }
3378
3379         // The SELECT clause can be encoded only by a hash
3380         if( !from_function && selhash->type != JSON_HASH ) {
3381                 osrfLogError(
3382                         OSRF_LOG_MARK,
3383                         "%s: Expected JSON_HASH for SELECT clause; found %s",
3384                         MODULENAME,
3385                         json_type( selhash->type )
3386                 );
3387
3388                 if (ctx)
3389                         osrfAppSessionStatus(
3390                                 ctx->session,
3391                                 OSRF_STATUS_INTERNALSERVERERROR,
3392                                 "osrfMethodException",
3393                                 ctx->request,
3394                                 "Malformed SELECT clause in JSON query"
3395                         );
3396                 free( join_clause );
3397                 return NULL;
3398         }
3399
3400         // If you see a null or wild card specifier for the core class, or an
3401         // empty array, replace it with a default SELECT list
3402         tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3403         if ( tmp_const ) {
3404                 int default_needed = 0;   // boolean
3405                 if( JSON_STRING == tmp_const->type
3406                         && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3407                                 default_needed = 1;
3408                 else if( JSON_NULL == tmp_const->type )
3409                         default_needed = 1;
3410
3411                 if( default_needed ) {
3412                         // Build a default SELECT list
3413                         jsonObject* default_list = defaultSelectList( core_class );
3414                         if( ! default_list ) {
3415                                 if (ctx) {
3416                                         osrfAppSessionStatus(
3417                                                 ctx->session,
3418                                                 OSRF_STATUS_INTERNALSERVERERROR,
3419                                                 "osrfMethodException",
3420                                                 ctx->request,
3421                                                 "Can't build default SELECT clause in JSON query"
3422                                         );
3423                                         free( join_clause );
3424                                         return NULL;
3425                                 }
3426                         }
3427
3428                         jsonObjectSetKey( selhash, core_class, default_list );
3429                 }
3430         }
3431
3432         // temp buffers for the SELECT list and GROUP BY clause
3433         growing_buffer* select_buf = buffer_init(128);
3434         growing_buffer* group_buf = buffer_init(128);
3435
3436         int aggregate_found = 0;     // boolean
3437
3438         // Build a select list
3439         if(from_function)   // From a function we select everything
3440                 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3441         else {
3442
3443                 // Build the SELECT list as SQL
3444             int sel_pos = 1;
3445             first = 1;
3446             gfirst = 1;
3447             jsonIterator* selclass_itr = jsonNewIterator( selhash );
3448             while ( (selclass = jsonIteratorNext( selclass_itr )) ) {    // For each class
3449
3450                         const char* cname = selclass_itr->key;
3451
3452                         // Make sure the target relation is in the FROM clause.
3453
3454                         // At this point join_hash is a step down from the join_hash we
3455                         // received as a parameter.  If the original was a JSON_STRING,
3456                         // then json_hash is now NULL.  If the original was a JSON_HASH,
3457                         // then json_hash is now the first (and only) entry in it,
3458                         // denoting the core class.  We've already excluded the
3459                         // possibility that the original was a JSON_ARRAY, because in
3460                         // that case from_function would be non-NULL, and we wouldn't
3461                         // be here.
3462
3463                         // If the current table alias isn't in scope, bail out
3464                         ClassInfo* class_info = search_alias( cname );
3465                         if( ! class_info ) {
3466                                 osrfLogError(
3467                                         OSRF_LOG_MARK,
3468                                         "%s: SELECT clause references class not in FROM clause: \"%s\"",
3469                                         MODULENAME,
3470                                         cname
3471                                 );
3472                                 if( ctx )
3473                                         osrfAppSessionStatus(
3474                                                 ctx->session,
3475                                                 OSRF_STATUS_INTERNALSERVERERROR,
3476                                                 "osrfMethodException",
3477                                                 ctx->request,
3478                                                 "Selected class not in FROM clause in JSON query"
3479                                         );
3480                                 jsonIteratorFree( selclass_itr );
3481                                 buffer_free( select_buf );
3482                                 buffer_free( group_buf );
3483                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3484                                 free( join_clause );
3485                                 return NULL;
3486                         }
3487
3488                         if( selclass->type != JSON_ARRAY ) {
3489                                 osrfLogError(
3490                                         OSRF_LOG_MARK,
3491                                         "%s: Malformed SELECT list for class \"%s\"; not an array",
3492                                         MODULENAME,
3493                                         cname
3494                                 );
3495                                 if( ctx )
3496                                         osrfAppSessionStatus(
3497                                                 ctx->session,
3498                                                 OSRF_STATUS_INTERNALSERVERERROR,
3499                                                 "osrfMethodException",
3500                                                 ctx->request,
3501                                                 "Selected class not in FROM clause in JSON query"
3502                                         );
3503
3504                                 jsonIteratorFree( selclass_itr );
3505                                 buffer_free( select_buf );
3506                                 buffer_free( group_buf );
3507                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3508                                 free( join_clause );
3509                                 return NULL;
3510                         }
3511
3512                         // Look up some attributes of the current class
3513                         osrfHash* idlClass = class_info->class_def;
3514                         osrfHash* class_field_set = class_info->fields;
3515                         const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3516                         const char* class_tname = osrfHashGet( idlClass, "tablename" );
3517
3518                         if( 0 == selclass->size ) {
3519                                 osrfLogWarning(
3520                                         OSRF_LOG_MARK,
3521                                         "%s: No columns selected from \"%s\"",
3522                                         MODULENAME,
3523                                         cname
3524                                 );
3525                         }
3526
3527                         // stitch together the column list for the current table alias...
3528                         unsigned long field_idx = 0;
3529                         jsonObject* selfield = NULL;
3530                         while((selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3531
3532                                 // If we need a separator comma, add one
3533                                 if (first) {
3534                                         first = 0;
3535                                 } else {
3536                                         OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3537                                 }
3538
3539                                 // if the field specification is a string, add it to the list
3540                                 if (selfield->type == JSON_STRING) {
3541
3542                                         // Look up the field in the IDL
3543                                         const char* col_name = jsonObjectGetString( selfield );
3544                                         osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3545                                         if ( !field_def ) {
3546                                                 // No such field in current class
3547                                                 osrfLogError(
3548                                                         OSRF_LOG_MARK,
3549                                                         "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3550                                                         MODULENAME,
3551                                                         col_name,
3552                                                         cname
3553                                                 );
3554                                                 if( ctx )
3555                                                         osrfAppSessionStatus(
3556                                                                 ctx->session,
3557                                                                 OSRF_STATUS_INTERNALSERVERERROR,
3558                                                                 "osrfMethodException",
3559                                                                 ctx->request,
3560                                                                 "Selected column not defined in JSON query"
3561                                                         );
3562                                                 jsonIteratorFree( selclass_itr );
3563                                                 buffer_free( select_buf );
3564                                                 buffer_free( group_buf );
3565                                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3566                                                 free( join_clause );
3567                                                 return NULL;
3568                                         } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3569                                                 // Virtual field not allowed
3570                                                 osrfLogError(
3571                                                         OSRF_LOG_MARK,
3572                                                         "%s: Selected column \"%s\" for class \"%s\" is virtual",
3573                                                         MODULENAME,
3574                                                         col_name,
3575                                                         cname
3576                                                 );
3577                                                 if( ctx )
3578                                                         osrfAppSessionStatus(
3579                                                                 ctx->session,
3580                                                                 OSRF_STATUS_INTERNALSERVERERROR,
3581                                                                 "osrfMethodException",
3582                                                                 ctx->request,
3583                                                                 "Selected column may not be virtual in JSON query"
3584                                                         );
3585                                                 jsonIteratorFree( selclass_itr );
3586                                                 buffer_free( select_buf );
3587                                                 buffer_free( group_buf );
3588                                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3589                                                 free( join_clause );
3590                                                 return NULL;
3591                                         }
3592
3593                                         if (locale) {
3594                                                 const char* i18n;
3595                                                 if (flags & DISABLE_I18N)
3596                                                         i18n = NULL;
3597                                                 else
3598                                                         i18n = osrfHashGet(field_def, "i18n");
3599
3600                                                 if( str_is_true( i18n ) ) {
3601                             buffer_fadd( select_buf,
3602                                                                 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3603                                                                 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3604                         } else {
3605                                             buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3606                         }
3607                     } else {
3608                                         buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3609                     }
3610
3611                                 // ... but it could be an object, in which case we check for a Field Transform
3612                                 } else if (selfield->type == JSON_HASH) {
3613
3614                                         const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3615
3616                                         // Get the field definition from the IDL
3617                                         osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3618                                         if ( !field_def ) {
3619                                                 // No such field in current class
3620                                                 osrfLogError(
3621                                                         OSRF_LOG_MARK,
3622                                                         "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3623                                                         MODULENAME,
3624                                                         col_name,
3625                                                         cname
3626                                                 );
3627                                                 if( ctx )
3628                                                         osrfAppSessionStatus(
3629                                                                 ctx->session,
3630                                                                 OSRF_STATUS_INTERNALSERVERERROR,
3631                                                                 "osrfMethodException",
3632                                                                 ctx->request,
3633                                                                 "Selected column is not defined in JSON query"
3634                                                         );
3635                                                 jsonIteratorFree( selclass_itr );
3636                                                 buffer_free( select_buf );
3637                                                 buffer_free( group_buf );
3638                                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3639                                                 free( join_clause );
3640                                                 return NULL;
3641                                         } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3642                                                 // No such field in current class
3643                                                 osrfLogError(
3644                                                         OSRF_LOG_MARK,
3645                                                         "%s: Selected column \"%s\" is virtual for class \"%s\"",
3646                                                         MODULENAME,
3647                                                         col_name,
3648                                                         cname
3649                                                 );
3650                                                 if( ctx )
3651                                                         osrfAppSessionStatus(
3652                                                                 ctx->session,
3653                                                                 OSRF_STATUS_INTERNALSERVERERROR,
3654                                                                 "osrfMethodException",
3655                                                                 ctx->request,
3656                                                                 "Selected column is virtual in JSON query"
3657                                                         );
3658                                                 jsonIteratorFree( selclass_itr );
3659                                                 buffer_free( select_buf );
3660                                                 buffer_free( group_buf );
3661                                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3662                                                 free( join_clause );
3663                                                 return NULL;
3664                                         }
3665
3666                                         // Decide what to use as a column alias
3667                                         const char* _alias;
3668                                         if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3669                                                 _alias = jsonObjectGetString( tmp_const );
3670                                         } else {         // Use field name as the alias
3671                                                 _alias = col_name;
3672                                         }
3673
3674                                         if (jsonObjectGetKeyConst( selfield, "transform" )) {
3675                                                 char* transform_str = searchFieldTransform(class_info->alias, field_def, selfield);
3676                                                 if( transform_str ) {
3677                                                         buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3678                                                         free(transform_str);
3679                                                 } else {
3680                                                         if( ctx )
3681                                                                 osrfAppSessionStatus(
3682                                                                         ctx->session,
3683                                                                         OSRF_STATUS_INTERNALSERVERERROR,
3684                                                                         "osrfMethodException",
3685                                                                         ctx->request,
3686                                                                         "Unable to generate transform function in JSON query"
3687                                                                 );
3688                                                         jsonIteratorFree( selclass_itr );
3689                                                         buffer_free( select_buf );
3690                                                         buffer_free( group_buf );
3691                                                         if( defaultselhash ) jsonObjectFree( defaultselhash );
3692                                                         free( join_clause );
3693                                                         return NULL;
3694                                                 }
3695                                         } else {
3696
3697                                                 if (locale) {
3698                                                         const char* i18n;
3699                                                         if (flags & DISABLE_I18N)
3700                                                                 i18n = NULL;
3701                                                         else
3702                                                                 i18n = osrfHashGet(field_def, "i18n");
3703
3704                                                         if( str_is_true( i18n ) ) {
3705                                                                 buffer_fadd( select_buf,
3706                                                                         " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3707                                                                         class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3708                                                         } else {
3709                                                                 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3710                                                         }
3711                                                 } else {
3712                                                         buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3713                                                 }
3714                                         }
3715                                 }
3716                                 else {
3717                                         osrfLogError(
3718                                                 OSRF_LOG_MARK,
3719                                                 "%s: Selected item is unexpected JSON type: %s",
3720                                                 MODULENAME,
3721                                                 json_type( selfield->type )
3722                                         );
3723                                         if( ctx )
3724                                                 osrfAppSessionStatus(
3725                                                         ctx->session,
3726                                                         OSRF_STATUS_INTERNALSERVERERROR,
3727                                                         "osrfMethodException",
3728                                                         ctx->request,
3729                                                         "Ill-formed SELECT item in JSON query"
3730                                                 );
3731                                         jsonIteratorFree( selclass_itr );
3732                                         buffer_free( select_buf );
3733                                         buffer_free( group_buf );
3734                                         if( defaultselhash ) jsonObjectFree( defaultselhash );
3735                                         free( join_clause );
3736                                         return NULL;
3737                                 }
3738
3739                                 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3740                                 if( obj_is_true( agg_obj ) )
3741                                         aggregate_found = 1;
3742                                 else {
3743                                         // Append a comma (except for the first one)
3744                                         // and add the column to a GROUP BY clause
3745                                         if (gfirst)
3746                                                 gfirst = 0;
3747                                         else
3748                                                 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3749
3750                                         buffer_fadd(group_buf, " %d", sel_pos);
3751                                 }
3752
3753 #if 0
3754                             if (is_agg->size || (flags & SELECT_DISTINCT)) {
3755
3756                                         const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3757                                     if ( ! obj_is_true( aggregate_obj ) ) {
3758                                             if (gfirst) {
3759                                                     gfirst = 0;
3760                                             } else {
3761                                                         OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3762                                             }
3763
3764                                             buffer_fadd(group_buf, " %d", sel_pos);
3765
3766                                         /*
3767                                     } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3768                                             if (gfirst) {
3769                                                     gfirst = 0;
3770                                             } else {
3771                                                         OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3772                                             }
3773
3774                                             _column = searchFieldTransform(class_info->alias, field, selfield);
3775                                                 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3776                                                 OSRF_BUFFER_ADD(group_buf, _column);
3777                                             _column = searchFieldTransform(class_info->alias, field, selfield);
3778                                         */
3779                                     }
3780                             }
3781 #endif
3782
3783                                 sel_pos++;
3784                         } // end while -- iterating across SELECT columns
3785
3786                 } // end while -- iterating across classes
3787
3788                 jsonIteratorFree(selclass_itr);
3789         }
3790
3791
3792         char* col_list = buffer_release(select_buf);
3793
3794         // Make sure the SELECT list isn't empty.  This can happen, for example,
3795         // if we try to build a default SELECT clause from a non-core table.
3796
3797         if( ! *col_list ) {
3798                 osrfLogError(OSRF_LOG_MARK, "%s: SELECT clause is empty", MODULENAME );
3799                 if (ctx)
3800                         osrfAppSessionStatus(
3801                                 ctx->session,
3802                                 OSRF_STATUS_INTERNALSERVERERROR,
3803                                 "osrfMethodException",
3804                                 ctx->request,
3805                                 "SELECT list is empty"
3806                 );
3807                 free( col_list );
3808                 buffer_free( group_buf );
3809                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3810                 free( join_clause );
3811                 return NULL;
3812         }
3813
3814         char* table = NULL;
3815         if (from_function) table = searchValueTransform(join_hash);
3816         else table = strdup( curr_query->core.source_def );
3817
3818         if( !table ) {
3819                 if (ctx)
3820                         osrfAppSessionStatus(
3821                                 ctx->session,
3822                                 OSRF_STATUS_INTERNALSERVERERROR,
3823                                 "osrfMethodException",
3824                                 ctx->request,
3825                                 "Unable to identify table for core class"
3826                         );
3827                 free( col_list );
3828                 buffer_free( group_buf );
3829                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3830                 free( join_clause );
3831                 return NULL;
3832         }
3833
3834         // Put it all together
3835         growing_buffer* sql_buf = buffer_init(128);
3836         buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3837         free(col_list);
3838         free(table);
3839
3840         // Append the join clause, if any
3841         if( join_clause ) {
3842                 buffer_add(sql_buf, join_clause);
3843                 free(join_clause);
3844         }
3845
3846         char* order_by_list = NULL;
3847         char* having_buf = NULL;
3848
3849         if (!from_function) {
3850
3851                 // Build a WHERE clause, if there is one
3852                 if ( search_hash ) {
3853                         buffer_add(sql_buf, " WHERE ");
3854
3855                         // and it's on the WHERE clause
3856                         char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
3857                         if ( ! pred ) {
3858                                 if (ctx) {
3859                                         osrfAppSessionStatus(
3860                                                 ctx->session,
3861                                                 OSRF_STATUS_INTERNALSERVERERROR,
3862                                                 "osrfMethodException",
3863                                                 ctx->request,
3864                                                 "Severe query error in WHERE predicate -- see error log for more details"
3865                                         );
3866                                 }
3867                                 buffer_free(group_buf);
3868                                 buffer_free(sql_buf);
3869                                 if (defaultselhash) jsonObjectFree(defaultselhash);
3870                                 return NULL;
3871                         }
3872
3873                         buffer_add(sql_buf, pred);
3874                         free(pred);
3875                 }
3876
3877                 // Build a HAVING clause, if there is one
3878                 if ( having_hash ) {
3879
3880                         // and it's on the the WHERE clause
3881                         having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
3882
3883                         if( ! having_buf ) {
3884                                 if (ctx) {
3885                                                 osrfAppSessionStatus(
3886                                                 ctx->session,
3887                                                 OSRF_STATUS_INTERNALSERVERERROR,
3888                                                 "osrfMethodException",
3889                                                 ctx->request,
3890                                                 "Severe query error in HAVING predicate -- see error log for more details"
3891                                         );
3892                                 }
3893                                 buffer_free(group_buf);
3894                                 buffer_free(sql_buf);
3895                                 if (defaultselhash) jsonObjectFree(defaultselhash);
3896                                 return NULL;
3897                         }
3898                 }
3899
3900                 growing_buffer* order_buf = NULL;  // to collect ORDER BY list
3901
3902                 // Build an ORDER BY clause, if there is one
3903                 if( NULL == order_hash )
3904                         ;  // No ORDER BY? do nothing
3905                 else if( JSON_ARRAY == order_hash->type ) {
3906                         // Array of field specifications, each specification being a
3907                         // hash to define the class, field, and other details
3908                         int order_idx = 0;
3909                         jsonObject* order_spec;
3910                         while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
3911
3912                                 if( JSON_HASH != order_spec->type ) {
3913                                         osrfLogError(OSRF_LOG_MARK,
3914                                                  "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
3915                                                 MODULENAME, json_type( order_spec->type ) );
3916                                         if( ctx )
3917                                                 osrfAppSessionStatus(
3918                                                          ctx->session,
3919                                                         OSRF_STATUS_INTERNALSERVERERROR,
3920                                                         "osrfMethodException",
3921                                                         ctx->request,
3922                                                         "Malformed ORDER BY clause -- see error log for more details"
3923                                                 );
3924                                         buffer_free( order_buf );
3925                                         free(having_buf);
3926                                         buffer_free(group_buf);
3927                                         buffer_free(sql_buf);
3928                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3929                                         return NULL;
3930                                 }
3931
3932                                 const char* class_alias =
3933                                                 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
3934                                 const char* field =
3935                                                 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
3936
3937                                 if ( order_buf )
3938                                         OSRF_BUFFER_ADD(order_buf, ", ");
3939                                 else
3940                                         order_buf = buffer_init(128);
3941
3942                                 if( !field || !class_alias ) {
3943                                         osrfLogError(OSRF_LOG_MARK,
3944                                                 "%s: Missing class or field name in field specification of ORDER BY clause",
3945                                                  MODULENAME );
3946                                         if( ctx )
3947                                                 osrfAppSessionStatus(
3948                                                         ctx->session,
3949                                                         OSRF_STATUS_INTERNALSERVERERROR,
3950                                                         "osrfMethodException",
3951                                                         ctx->request,
3952                                                         "Malformed ORDER BY clause -- see error log for more details"
3953                                                 );
3954                                         buffer_free( order_buf );
3955                                         free(having_buf);
3956                                         buffer_free(group_buf);
3957                                         buffer_free(sql_buf);
3958                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3959                                         return NULL;
3960                                 }
3961
3962                                 ClassInfo* order_class_info = search_alias( class_alias );
3963                                 if( ! order_class_info ) {
3964                                         osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
3965                                                         "not in FROM clause", MODULENAME, class_alias );
3966                                         if( ctx )
3967                                                 osrfAppSessionStatus(
3968                                                         ctx->session,
3969                                                         OSRF_STATUS_INTERNALSERVERERROR,
3970                                                         "osrfMethodException",
3971                                                         ctx->request,
3972                                                         "Invalid class referenced in ORDER BY clause -- see error log for more details"
3973                                                 );
3974                                         free(having_buf);
3975                                         buffer_free(group_buf);
3976                                         buffer_free(sql_buf);
3977                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3978                                         return NULL;
3979                                 }
3980
3981                                 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
3982                                 if( !field_def ) {
3983                                         osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
3984                                                  MODULENAME, class_alias, field );
3985                                         if( ctx )
3986                                                 osrfAppSessionStatus(
3987                                                         ctx->session,
3988                                                         OSRF_STATUS_INTERNALSERVERERROR,
3989                                                         "osrfMethodException",
3990                                                         ctx->request,
3991                                                         "Invalid field referenced in ORDER BY clause -- see error log for more details"
3992                                                 );
3993                                         free(having_buf);
3994                                         buffer_free(group_buf);
3995                                         buffer_free(sql_buf);
3996                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3997                                         return NULL;
3998                                 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3999                                         osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4000                                                                  MODULENAME, field );
4001                                         if( ctx )
4002                                                 osrfAppSessionStatus(
4003                                                         ctx->session,
4004                                                         OSRF_STATUS_INTERNALSERVERERROR,
4005                                                         "osrfMethodException",
4006                                                         ctx->request,
4007                                                         "Virtual field in ORDER BY clause -- see error log for more details"
4008                                                 );
4009                                         buffer_free( order_buf );
4010                                         free(having_buf);
4011                                         buffer_free(group_buf);
4012                                         buffer_free(sql_buf);
4013                                         if (defaultselhash) jsonObjectFree(defaultselhash);
4014                                         return NULL;
4015                                 }
4016
4017                                 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4018                                         char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
4019                                         if( ! transform_str ) {
4020                                                 if( ctx )
4021                                                         osrfAppSessionStatus(
4022                                                                 ctx->session,
4023                                                                 OSRF_STATUS_INTERNALSERVERERROR,
4024                                                                 "osrfMethodException",
4025                                                                 ctx->request,
4026                                                                 "Severe query error in ORDER BY clause -- see error log for more details"
4027                                                         );
4028                                                 buffer_free( order_buf );
4029                                                 free(having_buf);
4030                                                 buffer_free(group_buf);
4031                                                 buffer_free(sql_buf);
4032                                                 if (defaultselhash) jsonObjectFree(defaultselhash);
4033                                                 return NULL;
4034                                         }
4035
4036                                         OSRF_BUFFER_ADD( order_buf, transform_str );
4037                                         free( transform_str );
4038                                 }
4039                                 else
4040                                         buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4041
4042                                 const char* direction =
4043                                                 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4044                                 if( direction ) {
4045                                         if( direction[ 0 ] || 'D' == direction[ 0 ] )
4046                                                 OSRF_BUFFER_ADD( order_buf, " DESC" );
4047                                         else
4048                                                 OSRF_BUFFER_ADD( order_buf, " ASC" );
4049                                 }
4050                         }
4051                 } else if( JSON_HASH == order_hash->type ) {
4052                         // This hash is keyed on class alias.  Each class has either
4053                         // an array of field names or a hash keyed on field name.
4054                         jsonIterator* class_itr = jsonNewIterator( order_hash );
4055                         while ( (snode = jsonIteratorNext( class_itr )) ) {
4056
4057                                 ClassInfo* order_class_info = search_alias( class_itr->key );
4058                                 if( ! order_class_info ) {
4059                                         osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4060                                                                  MODULENAME, class_itr->key );
4061                                         if( ctx )
4062                                                 osrfAppSessionStatus(
4063                                                         ctx->session,
4064                                                         OSRF_STATUS_INTERNALSERVERERROR,
4065                                                         "osrfMethodException",
4066                                                         ctx->request,
4067                                                         "Invalid class referenced in ORDER BY clause -- see error log for more details"
4068                                                 );
4069                                         jsonIteratorFree( class_itr );
4070                                         buffer_free( order_buf );
4071                                         free(having_buf);
4072                                         buffer_free(group_buf);
4073                                         buffer_free(sql_buf);
4074                                         if (defaultselhash) jsonObjectFree(defaultselhash);
4075                                         return NULL;
4076                                 }
4077
4078                                 osrfHash* field_list_def = order_class_info->fields;
4079
4080                                 if ( snode->type == JSON_HASH ) {
4081
4082                                         // Hash is keyed on field names from the current class.  For each field
4083                                         // there is another layer of hash to define the sorting details, if any,
4084                                         // or a string to indicate direction of sorting.
4085                                         jsonIterator* order_itr = jsonNewIterator( snode );
4086                                         while ( (onode = jsonIteratorNext( order_itr )) ) {
4087
4088                                                 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4089                                                 if( !field_def ) {
4090                                                         osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
4091                                                                         MODULENAME, order_itr->key );
4092                                                         if( ctx )
4093                                                                 osrfAppSessionStatus(
4094                                                                         ctx->session,
4095                                                                         OSRF_STATUS_INTERNALSERVERERROR,
4096                                                                         "osrfMethodException",
4097                                                                         ctx->request,
4098                                                                         "Invalid field in ORDER BY clause -- see error log for more details"
4099                                                                 );
4100                                                         jsonIteratorFree( order_itr );
4101                                                         jsonIteratorFree( class_itr );
4102                                                         buffer_free( order_buf );
4103                                                         free(having_buf);
4104                                                         buffer_free(group_buf);
4105                                                         buffer_free(sql_buf);
4106                                                         if (defaultselhash) jsonObjectFree(defaultselhash);
4107                                                         return NULL;
4108                                                 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4109                                                         osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4110                                                                  MODULENAME, order_itr->key );
4111                                                         if( ctx )
4112                                                                 osrfAppSessionStatus(
4113                                                                         ctx->session,
4114                                                                         OSRF_STATUS_INTERNALSERVERERROR,
4115                                                                         "osrfMethodException",
4116                                                                         ctx->request,
4117                                                                         "Virtual field in ORDER BY clause -- see error log for more details"
4118                                                         );
4119                                                         jsonIteratorFree( order_itr );
4120                                                         jsonIteratorFree( class_itr );
4121                                                         buffer_free( order_buf );
4122                                                         free(having_buf);
4123                                                         buffer_free(group_buf);
4124                                                         buffer_free(sql_buf);
4125                                                         if (defaultselhash) jsonObjectFree(defaultselhash);
4126                                                         return NULL;
4127                                                 }
4128
4129                                                 const char* direction = NULL;
4130                                                 if ( onode->type == JSON_HASH ) {
4131                                                         if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4132                                                                 string = searchFieldTransform(
4133                                                                         class_itr->key,
4134                                                                         osrfHashGet( field_list_def, order_itr->key ),
4135                                                                         onode
4136                                                                 );
4137                                                                 if( ! string ) {
4138                                                                         if( ctx ) osrfAppSessionStatus(
4139                                                                                 ctx->session,
4140                                                                                 OSRF_STATUS_INTERNALSERVERERROR,
4141                                                                                 "osrfMethodException",
4142                                                                                 ctx->request,
4143                                                                                 "Severe query error in ORDER BY clause -- see error log for more details"
4144                                                                         );
4145                                                                         jsonIteratorFree( order_itr );
4146                                                                         jsonIteratorFree( class_itr );
4147                                                                         free(having_buf);
4148                                                                         buffer_free(group_buf);
4149                                                                         buffer_free(order_buf);
4150                                                                         buffer_free(sql_buf);
4151                                                                         if (defaultselhash) jsonObjectFree(defaultselhash);
4152                                                                         return NULL;
4153                                                                 }
4154                                                         } else {
4155                                                                 growing_buffer* field_buf = buffer_init(16);
4156                                                                 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4157                                                                 string = buffer_release(field_buf);
4158                                                         }
4159
4160                                                         if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4161                                                                 const char* dir = jsonObjectGetString(tmp_const);
4162                                                                 if (!strncasecmp(dir, "d", 1)) {
4163                                                                         direction = " DESC";
4164                                                                 } else {
4165                                                                         direction = " ASC";
4166                                                                 }
4167                                                         }
4168
4169                                                 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4170                                                         osrfLogError( OSRF_LOG_MARK,
4171                                                                 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4172                                                                 MODULENAME, json_type( onode->type ) );
4173                                                         if( ctx )
4174                                                                 osrfAppSessionStatus(
4175                                                                         ctx->session,
4176                                                                         OSRF_STATUS_INTERNALSERVERERROR,
4177                                                                         "osrfMethodException",
4178                                                                         ctx->request,
4179                                                                         "Malformed ORDER BY clause -- see error log for more details"
4180                                                                 );
4181                                                         jsonIteratorFree( order_itr );
4182                                                         jsonIteratorFree( class_itr );
4183                                                         free(having_buf);
4184                                                         buffer_free(group_buf);
4185                                                         buffer_free(order_buf);
4186                                                         buffer_free(sql_buf);
4187                                                         if (defaultselhash) jsonObjectFree(defaultselhash);
4188                                                         return NULL;
4189
4190                                                 } else {
4191                                                         string = strdup(order_itr->key);
4192                                                         const char* dir = jsonObjectGetString(onode);
4193                                                         if (!strncasecmp(dir, "d", 1)) {
4194                                                                 direction = " DESC";
4195                                                         } else {
4196                                                                 direction = " ASC";
4197                                                         }
4198                                                 }
4199
4200                                                 if ( order_buf )
4201                                                         OSRF_BUFFER_ADD(order_buf, ", ");
4202                                                 else
4203                                                         order_buf = buffer_init(128);
4204
4205                                                 OSRF_BUFFER_ADD(order_buf, string);
4206                                                 free(string);
4207
4208                                                 if (direction) {
4209                                                          OSRF_BUFFER_ADD(order_buf, direction);
4210                                                 }
4211
4212                                         } // end while
4213                                         jsonIteratorFree(order_itr);
4214
4215                                 } else if ( snode->type == JSON_ARRAY ) {
4216
4217                                         // Array is a list of fields from the current class
4218                                         unsigned long order_idx = 0;
4219                                         while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4220
4221                                                 const char* _f = jsonObjectGetString( onode );
4222
4223                                                 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4224                                                 if( !field_def ) {
4225                                                         osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
4226                                                                         MODULENAME, _f );
4227                                                         if( ctx )
4228                                                                 osrfAppSessionStatus(
4229                                                                         ctx->session,
4230                                                                         OSRF_STATUS_INTERNALSERVERERROR,
4231                                                                         "osrfMethodException",
4232                                                                         ctx->request,
4233                                                                         "Invalid field in ORDER BY clause -- see error log for more details"
4234                                                                 );
4235                                                         jsonIteratorFree( class_itr );
4236                                                         buffer_free( order_buf );
4237                                                         free(having_buf);
4238                                                         buffer_free(group_buf);
4239                                                         buffer_free(sql_buf);
4240                                                         if (defaultselhash) jsonObjectFree(defaultselhash);
4241                                                         return NULL;
4242                                                 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4243                                                         osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4244                                                                         MODULENAME, _f );
4245                                                         if( ctx )
4246                                                                 osrfAppSessionStatus(
4247                                                                         ctx->session,
4248                                                                         OSRF_STATUS_INTERNALSERVERERROR,
4249                                                                         "osrfMethodException",
4250                                                                         ctx->request,
4251                                                                         "Virtual field in ORDER BY clause -- see error log for more details"
4252                                                                 );
4253                                                         jsonIteratorFree( class_itr );
4254                                                         buffer_free( order_buf );
4255                                                         free(having_buf);
4256                                                         buffer_free(group_buf);
4257                                                         buffer_free(sql_buf);
4258                                                         if (defaultselhash) jsonObjectFree(defaultselhash);
4259                                                         return NULL;
4260                                                 }
4261
4262                                                 if ( order_buf )
4263                                                         OSRF_BUFFER_ADD(order_buf, ", ");
4264                                                 else
4265                                                         order_buf = buffer_init(128);
4266
4267                                                 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
4268
4269                                         } // end while
4270
4271                                 // IT'S THE OOOOOOOOOOOLD STYLE!
4272                                 } else {
4273                                         osrfLogError(OSRF_LOG_MARK,
4274                                                         "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
4275                                         if (ctx) {
4276                                                 osrfAppSessionStatus(
4277                                                         ctx->session,
4278                                                         OSRF_STATUS_INTERNALSERVERERROR,
4279                                                         "osrfMethodException",
4280                                                         ctx->request,
4281                                                         "Severe query error -- see error log for more details"
4282                                                 );
4283                                         }
4284
4285                                         free(having_buf);
4286                                         buffer_free(group_buf);
4287                                         buffer_free(order_buf);
4288                                         buffer_free(sql_buf);
4289                                         if (defaultselhash) jsonObjectFree(defaultselhash);
4290                                         jsonIteratorFree(class_itr);
4291                                         return NULL;
4292                                 }
4293                         } // end while
4294                         jsonIteratorFree( class_itr );
4295                 } else {
4296                         osrfLogError(OSRF_LOG_MARK,
4297                                 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4298                                 MODULENAME, json_type( order_hash->type ) );
4299                         if( ctx )
4300                                 osrfAppSessionStatus(
4301                                         ctx->session,
4302                                         OSRF_STATUS_INTERNALSERVERERROR,
4303                                         "osrfMethodException",
4304                                         ctx->request,
4305                                         "Malformed ORDER BY clause -- see error log for more details"
4306                                 );
4307                         buffer_free( order_buf );
4308                         free(having_buf);
4309                         buffer_free(group_buf);
4310                         buffer_free(sql_buf);
4311                         if (defaultselhash) jsonObjectFree(defaultselhash);
4312                         return NULL;
4313                 }
4314
4315                 if( order_buf )
4316                         order_by_list = buffer_release( order_buf );
4317         }
4318
4319
4320         string = buffer_release(group_buf);
4321
4322         if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4323                 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4324                 OSRF_BUFFER_ADD( sql_buf, string );
4325         }
4326
4327         free(string);
4328
4329         if( having_buf && *having_buf ) {
4330                 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4331                 OSRF_BUFFER_ADD( sql_buf, having_buf );
4332                 free( having_buf );
4333         }
4334
4335         if( order_by_list ) {
4336
4337                 if ( *order_by_list ) {
4338                         OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4339                         OSRF_BUFFER_ADD( sql_buf, order_by_list );
4340                 }
4341
4342                 free( order_by_list );
4343         }
4344
4345         if ( limit ){
4346                 const char* str = jsonObjectGetString(limit);
4347                 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
4348         }
4349
4350         if (offset) {
4351                 const char* str = jsonObjectGetString(offset);
4352                 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
4353         }
4354
4355         if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4356
4357         if (defaultselhash) jsonObjectFree(defaultselhash);
4358
4359         return buffer_release(sql_buf);
4360
4361 } // end of SELECT()
4362
4363 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4364
4365         const char* locale = osrf_message_get_last_locale();
4366
4367         osrfHash* fields = osrfHashGet(meta, "fields");
4368         char* core_class = osrfHashGet(meta, "classname");
4369
4370         const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4371
4372         jsonObject* node = NULL;
4373         jsonObject* snode = NULL;
4374         jsonObject* onode = NULL;
4375         const jsonObject* _tmp = NULL;
4376         jsonObject* selhash = NULL;
4377         jsonObject* defaultselhash = NULL;
4378
4379         growing_buffer* sql_buf = buffer_init(128);
4380         growing_buffer* select_buf = buffer_init(128);
4381
4382         if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4383                 defaultselhash = jsonNewObjectType(JSON_HASH);
4384                 selhash = defaultselhash;
4385         }
4386
4387         // If there's no SELECT list for the core class, build one
4388         if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
4389                 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4390
4391                 // Add every non-virtual field to the field list
4392                 osrfHash* field_def = NULL;
4393                 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4394                 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4395                         if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4396                                 const char* field = osrfHashIteratorKey( field_itr );
4397                                 jsonObjectPush( field_list, jsonNewObject( field ) );
4398                         }
4399                 }
4400                 osrfHashIteratorFree( field_itr );
4401                 jsonObjectSetKey( selhash, core_class, field_list );
4402         }
4403
4404         int first = 1;
4405         jsonIterator* class_itr = jsonNewIterator( selhash );
4406         while ( (snode = jsonIteratorNext( class_itr )) ) {
4407
4408                 const char* cname = class_itr->key;
4409                 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4410                 if (!idlClass) continue;
4411
4412                 if (strcmp(core_class,class_itr->key)) {
4413                         if (!join_hash) continue;
4414
4415                         jsonObject* found =  jsonObjectFindPath(join_hash, "//%s", class_itr->key);
4416                         if (!found->size) {
4417                                 jsonObjectFree(found);
4418                                 continue;
4419                         }
4420
4421                         jsonObjectFree(found);
4422                 }
4423
4424                 jsonIterator* select_itr = jsonNewIterator( snode );
4425                 while ( (node = jsonIteratorNext( select_itr )) ) {
4426                         const char* item_str = jsonObjectGetString( node );
4427                         osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4428                         char* fname = osrfHashGet(field, "name");
4429
4430                         if (!field) continue;
4431
4432                         if (first) {
4433                                 first = 0;
4434                         } else {
4435                                 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
4436                         }
4437
4438             if (locale) {
4439                         const char* i18n;
4440                                 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4441                                 if ( obj_is_true( no_i18n_obj ) )    // Suppress internationalization?
4442                                         i18n = NULL;
4443                                 else
4444                                         i18n = osrfHashGet(field, "i18n");
4445
4446                                 if( str_is_true( i18n ) ) {
4447                         char* pkey = osrfHashGet(idlClass, "primarykey");
4448                         char* tname = osrfHashGet(idlClass, "tablename");
4449
4450                     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);
4451                 } else {
4452                                 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4453                 }
4454             } else {
4455                             buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4456             }
4457                 }
4458
4459         jsonIteratorFree(select_itr);
4460         }
4461
4462     jsonIteratorFree(class_itr);
4463
4464         char* col_list = buffer_release(select_buf);
4465         char* table = getRelation(meta);
4466         if( !table )
4467                 table = strdup( "(null)" );
4468
4469         buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4470         free(col_list);
4471         free(table);
4472
4473         // Clear the query stack (as a fail-safe precaution against possible
4474         // leftover garbage); then push the first query frame onto the stack.
4475         clear_query_stack();
4476         push_query_frame();
4477         if( add_query_core( NULL, core_class ) ) {
4478                 if( ctx )
4479                         osrfAppSessionStatus(
4480                                 ctx->session,
4481                                 OSRF_STATUS_INTERNALSERVERERROR,
4482                                 "osrfMethodException",
4483                                 ctx->request,
4484                                 "Unable to build query frame for core class"
4485                         );
4486                 return NULL;
4487         }
4488
4489         if ( join_hash ) {
4490                 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4491                 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
4492                 OSRF_BUFFER_ADD(sql_buf, join_clause);
4493                 free(join_clause);
4494         }
4495
4496         osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL =  %s",
4497                                  MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
4498
4499         OSRF_BUFFER_ADD(sql_buf, " WHERE ");
4500
4501         char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4502         if (!pred) {
4503                 osrfAppSessionStatus(
4504                         ctx->session,
4505                         OSRF_STATUS_INTERNALSERVERERROR,
4506                                 "osrfMethodException",
4507                                 ctx->request,
4508                                 "Severe query error -- see error log for more details"
4509                         );
4510                 buffer_free(sql_buf);
4511                 if(defaultselhash) jsonObjectFree(defaultselhash);
4512                 clear_query_stack();
4513                 return NULL;
4514         } else {
4515                 buffer_add(sql_buf, pred);
4516                 free(pred);
4517         }
4518
4519         if (order_hash) {
4520                 char* string = NULL;
4521                 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4522
4523                         growing_buffer* order_buf = buffer_init(128);
4524
4525                         first = 1;
4526                         jsonIterator* class_itr = jsonNewIterator( _tmp );
4527                         while ( (snode = jsonIteratorNext( class_itr )) ) {
4528
4529                                 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4530                                         continue;
4531
4532                                 if ( snode->type == JSON_HASH ) {
4533
4534                                         jsonIterator* order_itr = jsonNewIterator( snode );
4535                                         while ( (onode = jsonIteratorNext( order_itr )) ) {
4536
4537                                                 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4538                                                                 class_itr->key, order_itr->key );
4539                                                 if ( !field_def )
4540                                                         continue;
4541
4542                                                 char* direction = NULL;
4543                                                 if ( onode->type == JSON_HASH ) {
4544                                                         if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4545                                                                 string = searchFieldTransform( class_itr->key, field_def, onode );
4546                                                                 if( ! string ) {
4547                                                                         osrfAppSessionStatus(
4548                                                                                 ctx->session,
4549                                                                                 OSRF_STATUS_INTERNALSERVERERROR,
4550                                                                                 "osrfMethodException",
4551                                                                                 ctx->request,
4552                                                                                 "Severe query error in ORDER BY clause -- see error log for more details"
4553                                                                         );
4554                                                                         jsonIteratorFree( order_itr );
4555                                                                         jsonIteratorFree( class_itr );
4556                                                                         buffer_free( order_buf );
4557                                                                         buffer_free( sql_buf );
4558                                                                         if( defaultselhash ) jsonObjectFree( defaultselhash );
4559                                                                         clear_query_stack();
4560                                                                         return NULL;
4561                                                                 }
4562                                                         } else {
4563                                                                 growing_buffer* field_buf = buffer_init(16);
4564                                                                 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4565                                                                 string = buffer_release(field_buf);
4566                                                         }
4567
4568                                                         if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4569                                                                 const char* dir = jsonObjectGetString(_tmp);
4570                                                                 if (!strncasecmp(dir, "d", 1)) {
4571                                                                         direction = " DESC";
4572                                                                 } else {
4573                                                                         free(direction);
4574                                                                 }
4575                                                         }
4576
4577                                                 } else {
4578                                                         string = strdup(order_itr->key);
4579                                                         const char* dir = jsonObjectGetString(onode);
4580                                                         if (!strncasecmp(dir, "d", 1)) {
4581                                                                 direction = " DESC";
4582                                                         } else {
4583                                                                 direction = " ASC";
4584                                                         }
4585                                                 }
4586
4587                                                 if (first) {
4588                                                         first = 0;
4589                                                 } else {
4590                                                         buffer_add(order_buf, ", ");
4591                                                 }
4592
4593                                                 buffer_add(order_buf, string);
4594                                                 free(string);
4595
4596                                                 if (direction) {
4597                                                         buffer_add(order_buf, direction);
4598                                                 }
4599
4600                                         }
4601
4602                     jsonIteratorFree(order_itr);
4603
4604                                 } else {
4605                                         const char* str = jsonObjectGetString(snode);
4606                                         buffer_add(order_buf, str);
4607                                         break;
4608                                 }
4609
4610                         }
4611
4612                         jsonIteratorFree(class_itr);
4613
4614                         string = buffer_release(order_buf);
4615
4616                         if ( *string ) {
4617                                 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4618                                 OSRF_BUFFER_ADD( sql_buf, string );
4619                         }
4620
4621                         free(string);
4622                 }
4623
4624                 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4625                         const char* str = jsonObjectGetString(_tmp);
4626                         buffer_fadd(
4627                                 sql_buf,
4628                                 " LIMIT %d",
4629                                 atoi(str)
4630                         );
4631                 }
4632
4633                 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4634                 if (_tmp) {
4635                         const char* str = jsonObjectGetString(_tmp);
4636                         buffer_fadd(
4637                                 sql_buf,
4638                                 " OFFSET %d",
4639                                 atoi(str)
4640                         );
4641                 }
4642         }
4643
4644         if (defaultselhash) jsonObjectFree(defaultselhash);
4645         clear_query_stack();
4646
4647         OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4648         return buffer_release(sql_buf);
4649 }
4650
4651 int doJSONSearch ( osrfMethodContext* ctx ) {
4652         if(osrfMethodVerifyContext( ctx )) {
4653                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
4654                 return -1;
4655         }
4656
4657         osrfLogDebug(OSRF_LOG_MARK, "Received query request");
4658
4659         int err = 0;
4660
4661         // XXX for now...
4662         dbhandle = writehandle;
4663
4664         jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4665
4666         int flags = 0;
4667
4668         if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4669                 flags |= SELECT_DISTINCT;
4670
4671         if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4672                 flags |= DISABLE_I18N;
4673
4674         osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4675         clear_query_stack();       // a possibly needless precaution
4676         char* sql = buildQuery( ctx, hash, flags );
4677         clear_query_stack();
4678
4679         if (!sql) {
4680                 err = -1;
4681                 return err;
4682         }
4683
4684         osrfLogDebug(OSRF_LOG_MARK, "%s SQL =  %s", MODULENAME, sql);
4685         dbi_result result = dbi_conn_query(dbhandle, sql);
4686
4687         if(result) {
4688                 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4689
4690                 if (dbi_result_first_row(result)) {
4691                         /* JSONify the result */
4692                         osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4693
4694                         do {
4695                                 jsonObject* return_val = oilsMakeJSONFromResult( result );
4696                                 osrfAppRespond( ctx, return_val );
4697                 jsonObjectFree( return_val );
4698                         } while (dbi_result_next_row(result));
4699
4700                 } else {
4701                         osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4702                 }
4703
4704                 osrfAppRespondComplete( ctx, NULL );
4705
4706                 /* clean up the query */
4707                 dbi_result_free(result);
4708
4709         } else {
4710                 err = -1;
4711                 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4712                 osrfAppSessionStatus(
4713                         ctx->session,
4714                         OSRF_STATUS_INTERNALSERVERERROR,
4715                         "osrfMethodException",
4716                         ctx->request,
4717                         "Severe query error -- see error log for more details"
4718                 );
4719         }
4720
4721         free(sql);
4722         return err;
4723 }
4724
4725 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4726                 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4727
4728         // XXX for now...
4729         dbhandle = writehandle;
4730
4731         osrfHash* links = osrfHashGet(meta, "links");
4732         osrfHash* fields = osrfHashGet(meta, "fields");
4733         char* core_class = osrfHashGet(meta, "classname");
4734         char* pkey = osrfHashGet(meta, "primarykey");
4735
4736         const jsonObject* _tmp;
4737         jsonObject* obj;
4738
4739         char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4740         if (!sql) {
4741                 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4742                 *err = -1;
4743                 return NULL;
4744         }
4745
4746         osrfLogDebug(OSRF_LOG_MARK, "%s SQL =  %s", MODULENAME, sql);
4747
4748         dbi_result result = dbi_conn_query(dbhandle, sql);
4749         if( NULL == result ) {
4750                 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4751                         MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4752                 osrfAppSessionStatus(
4753                         ctx->session,
4754                         OSRF_STATUS_INTERNALSERVERERROR,
4755                         "osrfMethodException",
4756                         ctx->request,
4757                         "Severe query error -- see error log for more details"
4758                 );
4759                 *err = -1;
4760                 free(sql);
4761                 return jsonNULL;
4762
4763         } else {
4764                 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4765         }
4766
4767         jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
4768         osrfHash* dedup = osrfNewHash();
4769
4770         if (dbi_result_first_row(result)) {
4771                 /* JSONify the result */
4772                 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4773                 do {
4774                         obj = oilsMakeFieldmapperFromResult( result, meta );
4775                         char* pkey_val = oilsFMGetString( obj, pkey );
4776                         if ( osrfHashGet( dedup, pkey_val ) ) {
4777                                 jsonObjectFree(obj);
4778                                 free(pkey_val);
4779                         } else {
4780                                 osrfHashSet( dedup, pkey_val, pkey_val );
4781                                 jsonObjectPush(res_list, obj);
4782                         }
4783                 } while (dbi_result_next_row(result));
4784         } else {
4785                 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
4786                         MODULENAME, sql );
4787         }
4788
4789         osrfHashFree(dedup);
4790         /* clean up the query */
4791         dbi_result_free(result);
4792         free(sql);
4793
4794         if (res_list->size && query_hash) {
4795                 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
4796                 if (_tmp) {
4797                         int x = (int)jsonObjectGetNumber(_tmp);
4798                         if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
4799
4800                         const jsonObject* temp_blob;
4801                         if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
4802
4803                                 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
4804                                 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
4805
4806                                 osrfStringArray* link_fields = NULL;
4807
4808                                 if (flesh_fields) {
4809                                         if (flesh_fields->size == 1) {
4810                                                 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
4811                                                 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
4812                                         }
4813
4814                                         if (!link_fields) {
4815                                                 jsonObject* _f;
4816                                                 link_fields = osrfNewStringArray(1);
4817                                                 jsonIterator* _i = jsonNewIterator( flesh_fields );
4818                                                 while ((_f = jsonIteratorNext( _i ))) {
4819                                                         osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4820                                                 }
4821                         jsonIteratorFree(_i);
4822                                         }
4823                                 }
4824
4825                                 jsonObject* cur;
4826                                 unsigned long res_idx = 0;
4827                                 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
4828
4829                                         int i = 0;
4830                                         const char* link_field;
4831
4832                                         while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4833
4834                                                 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4835
4836                                                 osrfHash* kid_link = osrfHashGet(links, link_field);
4837                                                 if (!kid_link) continue;
4838
4839                                                 osrfHash* field = osrfHashGet(fields, link_field);
4840                                                 if (!field) continue;
4841
4842                                                 osrfHash* value_field = field;
4843
4844                                                 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4845                                                 if (!kid_idl) continue;
4846
4847                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4848                                                         value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4849                                                 }
4850
4851                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4852                                                         value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4853                                                 }
4854
4855                                                 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4856
4857                                                 if (link_map->size > 0) {
4858                                                         jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4859                                                         jsonObjectPush(
4860                                                                 _kid_key,
4861                                                                 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4862                                                         );
4863
4864                                                         jsonObjectSetKey(
4865                                                                 flesh_blob,
4866                                                                 osrfHashGet(kid_link, "class"),
4867                                                                 _kid_key
4868                                                         );
4869                                                 };
4870
4871                                                 osrfLogDebug(
4872                                                         OSRF_LOG_MARK,
4873                                                         "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4874                                                         osrfHashGet(kid_link, "field"),
4875                                                         osrfHashGet(kid_link, "class"),
4876                                                         osrfHashGet(kid_link, "key"),
4877                                                         osrfHashGet(kid_link, "reltype")
4878                                                 );
4879
4880                                                 const char* search_key = jsonObjectGetString(
4881                                                         jsonObjectGetIndex(
4882                                                                 cur,
4883                                                                 atoi( osrfHashGet(value_field, "array_position") )
4884                                                         )
4885                                                 );
4886
4887                                                 if (!search_key) {
4888                                                         osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4889                                                         continue;
4890                                                 }
4891
4892                                                 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
4893
4894                                                 // construct WHERE clause
4895                                                 jsonObject* where_clause  = jsonNewObjectType(JSON_HASH);
4896                                                 jsonObjectSetKey(
4897                                                         where_clause,
4898                                                         osrfHashGet(kid_link, "key"),
4899                                                         jsonNewObject( search_key )
4900                                                 );
4901
4902                                                 // construct the rest of the query
4903                                                 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
4904                                                 jsonObjectSetKey( rest_of_query, "flesh",
4905                                                         jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4906                                                 );
4907
4908                                                 if (flesh_blob)
4909                                                         jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
4910
4911                                                 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
4912                                                         jsonObjectSetKey( rest_of_query, "order_by",
4913                                                                 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
4914                                                         );
4915                                                 }
4916
4917                                                 if (jsonObjectGetKeyConst(query_hash, "select")) {
4918                                                         jsonObjectSetKey( rest_of_query, "select",
4919                                                                 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
4920                                                         );
4921                                                 }
4922
4923                                                 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
4924                                                         where_clause, rest_of_query, err);
4925
4926                                                 jsonObjectFree( where_clause );
4927                                                 jsonObjectFree( rest_of_query );
4928
4929                                                 if(*err) {
4930                                                         osrfStringArrayFree(link_fields);
4931                                                         jsonObjectFree(res_list);
4932                                                         jsonObjectFree(flesh_blob);
4933                                                         return jsonNULL;
4934                                                 }
4935
4936                                                 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4937
4938                                                 jsonObject* X = NULL;
4939                                                 if ( link_map->size > 0 && kids->size > 0 ) {
4940                                                         X = kids;
4941                                                         kids = jsonNewObjectType(JSON_ARRAY);
4942
4943                                                         jsonObject* _k_node;
4944                                                         unsigned long res_idx = 0;
4945                                                         while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
4946                                                                 jsonObjectPush(
4947                                                                         kids,
4948                                                                         jsonObjectClone(
4949                                                                                 jsonObjectGetIndex(
4950                                                                                         _k_node,
4951                                                                                         (unsigned long)atoi(
4952                                                                                                 osrfHashGet(
4953                                                                                                         osrfHashGet(
4954                                                                                                                 osrfHashGet(
4955                                                                                                                         osrfHashGet(
4956                                                                                                                                 oilsIDL(),
4957                                                                                                                                 osrfHashGet(kid_link, "class")
4958                                                                                                                         ),
4959                                                                                                                         "fields"
4960                                                                                                                 ),
4961                                                                                                                 osrfStringArrayGetString( link_map, 0 )
4962                                                                                                         ),
4963                                                                                                         "array_position"
4964                                                                                                 )
4965                                                                                         )
4966                                                                                 )
4967                                                                         )
4968                                                                 );
4969                                                         } // end while loop traversing X
4970                                                 }
4971
4972                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4973                                                         osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4974                                                         jsonObjectSetIndex(
4975                                                                 cur,
4976                                                                 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4977                                                                 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4978                                                         );
4979                                                 }
4980
4981                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4982                                                         osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4983                                                         jsonObjectSetIndex(
4984                                                                 cur,
4985                                                                 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4986                                                                 jsonObjectClone( kids )
4987                                                         );
4988                                                 }
4989
4990                                                 if (X) {
4991                                                         jsonObjectFree(kids);
4992                                                         kids = X;
4993                                                 }
4994
4995                                                 jsonObjectFree( kids );
4996
4997                                                 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4998                                                 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4999
5000                                         }
5001                                 } // end while loop traversing res_list
5002                                 jsonObjectFree( flesh_blob );
5003                                 osrfStringArrayFree(link_fields);
5004                         }
5005                 }
5006         }
5007
5008         return res_list;
5009 }
5010
5011
5012 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
5013
5014         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5015 #ifdef PCRUD
5016         jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
5017 #else
5018         jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
5019 #endif
5020
5021         if (!verifyObjectClass(ctx, target)) {
5022                 *err = -1;
5023                 return jsonNULL;
5024         }
5025
5026         if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
5027                 osrfAppSessionStatus(
5028                         ctx->session,
5029                         OSRF_STATUS_BADREQUEST,
5030                         "osrfMethodException",
5031                         ctx->request,
5032                         "No active transaction -- required for UPDATE"
5033                 );
5034                 *err = -1;
5035                 return jsonNULL;
5036         }
5037
5038         // The following test is harmless but redundant.  If a class is
5039         // readonly, we don't register an update method for it.
5040         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5041                 osrfAppSessionStatus(
5042                         ctx->session,
5043                         OSRF_STATUS_BADREQUEST,
5044                         "osrfMethodException",
5045                         ctx->request,
5046                         "Cannot UPDATE readonly class"
5047                 );
5048                 *err = -1;
5049                 return jsonNULL;
5050         }
5051
5052         dbhandle = writehandle;
5053
5054         char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
5055
5056         // Set the last_xact_id
5057         int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5058         if (index > -1) {
5059                 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5060                                 trans_id, target->classname, index);
5061                 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
5062         }
5063
5064         char* pkey = osrfHashGet(meta, "primarykey");
5065         osrfHash* fields = osrfHashGet(meta, "fields");
5066
5067         char* id = oilsFMGetString( target, pkey );
5068
5069         osrfLogDebug(
5070                 OSRF_LOG_MARK,
5071                 "%s updating %s object with %s = %s",
5072                 MODULENAME,
5073                 osrfHashGet(meta, "fieldmapper"),
5074                 pkey,
5075                 id
5076         );
5077
5078         growing_buffer* sql = buffer_init(128);
5079         buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
5080
5081         int first = 1;
5082         osrfHash* field_def = NULL;
5083         osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5084         while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
5085
5086                 // Skip virtual fields, and the primary key
5087                 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5088                         continue;
5089
5090                 const char* field_name = osrfHashIteratorKey( field_itr );
5091                 if( ! strcmp( field_name, pkey ) )
5092                         continue;
5093
5094                 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5095
5096                 int value_is_numeric = 0;    // boolean
5097                 char* value;
5098                 if (field_object && field_object->classname) {
5099                         value = oilsFMGetString(
5100                                 field_object,
5101                                 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
5102                         );
5103                 } else if( field_object && JSON_BOOL == field_object->type ) {
5104                         if( jsonBoolIsTrue( field_object ) )
5105                                 value = strdup( "t" );
5106                         else
5107                                 value = strdup( "f" );
5108                 } else {
5109                         value = jsonObjectToSimpleString( field_object );
5110                         if( field_object && JSON_NUMBER == field_object->type )
5111                                 value_is_numeric = 1;
5112                 }
5113
5114                 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5115                                 osrfHashGet(meta, "fieldmapper"), field_name, value);
5116
5117                 if (!field_object || field_object->type == JSON_NULL) {
5118                         if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
5119                                         && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5120                                 if (first) first = 0;
5121                                 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5122                                 buffer_fadd( sql, " %s = NULL", field_name );
5123                         }
5124
5125                 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5126                         if (first) first = 0;
5127                         else OSRF_BUFFER_ADD_CHAR(sql, ',');
5128
5129                         const char* numtype = get_datatype( field_def );
5130                         if ( !strncmp( numtype, "INT", 3 ) ) {
5131                                 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
5132                         } else if ( !strcmp( numtype, "NUMERIC" ) ) {
5133                                 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
5134                         } else {
5135                                 // Must really be intended as a string, so quote it
5136                                 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5137                                         buffer_fadd( sql, " %s = %s", field_name, value );
5138                                 } else {
5139                                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5140                                         osrfAppSessionStatus(
5141                                                 ctx->session,
5142                                                 OSRF_STATUS_INTERNALSERVERERROR,
5143                                                 "osrfMethodException",
5144                                                 ctx->request,
5145                                                 "Error quoting string -- please see the error log for more details"
5146                                         );
5147                                         free(value);
5148                                         free(id);
5149                                         osrfHashIteratorFree( field_itr );
5150                                         buffer_free(sql);
5151                                         *err = -1;
5152                                         return jsonNULL;
5153                                 }
5154                         }
5155
5156                         osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5157
5158                 } else {
5159                         if ( dbi_conn_quote_string(dbhandle, &value) ) {
5160                                 if (first) first = 0;
5161                                 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5162                                 buffer_fadd( sql, " %s = %s", field_name, value );
5163
5164                         } else {
5165                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5166                                 osrfAppSessionStatus(
5167                                         ctx->session,
5168                                         OSRF_STATUS_INTERNALSERVERERROR,
5169                                         "osrfMethodException",
5170                                         ctx->request,
5171                                         "Error quoting string -- please see the error log for more details"
5172                                 );
5173                                 free(value);
5174                                 free(id);
5175                                 osrfHashIteratorFree( field_itr );
5176                                 buffer_free(sql);
5177                                 *err = -1;
5178                                 return jsonNULL;
5179                         }
5180                 }
5181
5182                 free(value);
5183
5184         } // end while
5185
5186         osrfHashIteratorFree( field_itr );
5187
5188         jsonObject* obj = jsonNewObject(id);
5189
5190         if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5191                 dbi_conn_quote_string(dbhandle, &id);
5192
5193         buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5194
5195         char* query = buffer_release(sql);
5196         osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
5197
5198         dbi_result result = dbi_conn_query(dbhandle, query);
5199         free(query);
5200
5201         if (!result) {
5202                 jsonObjectFree(obj);
5203                 obj = jsonNewObject(NULL);
5204                 osrfLogError(
5205                         OSRF_LOG_MARK,
5206                         "%s ERROR updating %s object with %s = %s",
5207                         MODULENAME,
5208                         osrfHashGet(meta, "fieldmapper"),
5209                         pkey,
5210                         id
5211                 );
5212         }
5213
5214         free(id);
5215
5216         return obj;
5217 }
5218
5219 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
5220
5221         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5222
5223         if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
5224                 osrfAppSessionStatus(
5225                         ctx->session,
5226                         OSRF_STATUS_BADREQUEST,
5227                         "osrfMethodException",
5228                         ctx->request,
5229                         "No active transaction -- required for DELETE"
5230                 );
5231                 *err = -1;
5232                 return jsonNULL;
5233         }
5234
5235         // The following test is harmless but redundant.  If a class is
5236         // readonly, we don't register a delete method for it.
5237         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5238                 osrfAppSessionStatus(
5239                         ctx->session,
5240                         OSRF_STATUS_BADREQUEST,
5241                         "osrfMethodException",
5242                         ctx->request,
5243                         "Cannot DELETE readonly class"
5244                 );
5245                 *err = -1;
5246                 return jsonNULL;
5247         }
5248
5249         dbhandle = writehandle;
5250
5251         jsonObject* obj;
5252
5253         char* pkey = osrfHashGet(meta, "primarykey");
5254
5255         int _obj_pos = 0;
5256 #ifdef PCRUD
5257                 _obj_pos = 1;
5258 #endif
5259
5260         char* id;
5261         if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
5262                 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5263                         *err = -1;
5264                         return jsonNULL;
5265                 }
5266
5267                 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5268         } else {
5269 #ifdef PCRUD
5270         if (!verifyObjectPCRUD( ctx, NULL )) {
5271                         *err = -1;
5272                         return jsonNULL;
5273         }
5274 #endif
5275                 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
5276         }
5277
5278         osrfLogDebug(
5279                 OSRF_LOG_MARK,
5280                 "%s deleting %s object with %s = %s",
5281                 MODULENAME,
5282                 osrfHashGet(meta, "fieldmapper"),
5283                 pkey,
5284                 id
5285         );
5286
5287         obj = jsonNewObject(id);
5288
5289         if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5290                 dbi_conn_quote_string(writehandle, &id);
5291
5292         dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
5293
5294         if (!result) {
5295                 jsonObjectFree(obj);
5296                 obj = jsonNewObject(NULL);
5297                 osrfLogError(
5298                         OSRF_LOG_MARK,
5299                         "%s ERROR deleting %s object with %s = %s",
5300                         MODULENAME,
5301                         osrfHashGet(meta, "fieldmapper"),
5302                         pkey,
5303                         id
5304                 );
5305         }
5306
5307         free(id);
5308
5309         return obj;
5310
5311 }
5312
5313
5314 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5315         if(!(result && meta)) return jsonNULL;
5316
5317         jsonObject* object = jsonNewObject(NULL);
5318         jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
5319
5320         osrfHash* fields = osrfHashGet(meta, "fields");
5321
5322         osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
5323
5324         osrfHash* _f;
5325         time_t _tmp_dt;
5326         char dt_string[256];
5327         struct tm gmdt;
5328
5329         int fmIndex;
5330         int columnIndex = 1;
5331         int attr;
5332         unsigned short type;
5333         const char* columnName;
5334
5335         /* cycle through the column list */
5336         while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5337
5338                 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5339
5340                 fmIndex = -1; // reset the position
5341
5342                 /* determine the field type and storage attributes */
5343                 type = dbi_result_get_field_type_idx(result, columnIndex);
5344                 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5345
5346                 /* fetch the fieldmapper index */
5347                 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
5348
5349                         if ( str_is_true( osrfHashGet(_f, "virtual") ) )
5350                                 continue;
5351
5352                         const char* pos = (char*)osrfHashGet(_f, "array_position");
5353                         if ( !pos ) continue;
5354
5355                         fmIndex = atoi( pos );
5356                         osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
5357                 } else {
5358                         continue;
5359                 }
5360
5361                 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5362                         jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
5363                 } else {
5364
5365                         switch( type ) {
5366
5367                                 case DBI_TYPE_INTEGER :
5368
5369                                         if( attr & DBI_INTEGER_SIZE8 )
5370                                                 jsonObjectSetIndex( object, fmIndex,
5371                                                         jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
5372                                         else
5373                                                 jsonObjectSetIndex( object, fmIndex,
5374                                                         jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
5375
5376                                         break;
5377
5378                                 case DBI_TYPE_DECIMAL :
5379                                         jsonObjectSetIndex( object, fmIndex,
5380                                                         jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
5381                                         break;
5382
5383                                 case DBI_TYPE_STRING :
5384
5385
5386                                         jsonObjectSetIndex(
5387                                                 object,
5388                                                 fmIndex,
5389                                                 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
5390                                         );
5391
5392                                         break;
5393
5394                                 case DBI_TYPE_DATETIME :
5395
5396                                         memset(dt_string, '\0', sizeof(dt_string));
5397                                         memset(&gmdt, '\0', sizeof(gmdt));
5398
5399                                         _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5400
5401
5402                                         if (!(attr & DBI_DATETIME_DATE)) {
5403                                                 gmtime_r( &_tmp_dt, &gmdt );
5404                                                 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5405                                         } else if (!(attr & DBI_DATETIME_TIME)) {
5406                                                 localtime_r( &_tmp_dt, &gmdt );
5407                                                 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5408                                         } else {
5409                                                 localtime_r( &_tmp_dt, &gmdt );
5410                                                 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5411                                         }
5412
5413                                         jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
5414
5415                                         break;
5416
5417                                 case DBI_TYPE_BINARY :
5418                                         osrfLogError( OSRF_LOG_MARK,
5419                                                 "Can't do binary at column %s : index %d", columnName, columnIndex);
5420                         }
5421                 }
5422                 ++columnIndex;
5423         }
5424
5425         return object;
5426 }
5427
5428 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5429         if(!result) return jsonNULL;
5430
5431         jsonObject* object = jsonNewObject(NULL);
5432
5433         time_t _tmp_dt;
5434         char dt_string[256];
5435         struct tm gmdt;
5436
5437         int fmIndex;
5438         int columnIndex = 1;
5439         int attr;
5440         unsigned short type;
5441         const char* columnName;
5442
5443         /* cycle through the column list */
5444         while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5445
5446                 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5447
5448                 fmIndex = -1; // reset the position
5449
5450                 /* determine the field type and storage attributes */
5451                 type = dbi_result_get_field_type_idx(result, columnIndex);
5452                 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5453
5454                 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5455                         jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
5456                 } else {
5457
5458                         switch( type ) {
5459
5460                                 case DBI_TYPE_INTEGER :
5461
5462                                         if( attr & DBI_INTEGER_SIZE8 )
5463                                                 jsonObjectSetKey( object, columnName,
5464                                                                 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)) );
5465                                         else
5466                                                 jsonObjectSetKey( object, columnName,
5467                                                                 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
5468                                         break;
5469
5470                                 case DBI_TYPE_DECIMAL :
5471                                         jsonObjectSetKey( object, columnName,
5472                                                         jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
5473                                         break;
5474
5475                                 case DBI_TYPE_STRING :
5476                                         jsonObjectSetKey( object, columnName,
5477                                                         jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
5478                                         break;
5479
5480                                 case DBI_TYPE_DATETIME :
5481
5482                                         memset(dt_string, '\0', sizeof(dt_string));
5483                                         memset(&gmdt, '\0', sizeof(gmdt));
5484
5485                                         _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5486
5487
5488                                         if (!(attr & DBI_DATETIME_DATE)) {
5489                                                 gmtime_r( &_tmp_dt, &gmdt );
5490                                                 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5491                                         } else if (!(attr & DBI_DATETIME_TIME)) {
5492                                                 localtime_r( &_tmp_dt, &gmdt );
5493                                                 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5494                                         } else {
5495                                                 localtime_r( &_tmp_dt, &gmdt );
5496                                                 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5497                                         }
5498
5499                                         jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
5500                                         break;
5501
5502                                 case DBI_TYPE_BINARY :
5503                                         osrfLogError( OSRF_LOG_MARK,
5504                                                 "Can't do binary at column %s : index %d", columnName, columnIndex );
5505                         }
5506                 }
5507                 ++columnIndex;
5508         } // end while loop traversing result
5509
5510         return object;
5511 }
5512
5513 // Interpret a string as true or false
5514 static int str_is_true( const char* str ) {
5515         if( NULL == str || strcasecmp( str, "true" ) )
5516                 return 0;
5517         else
5518                 return 1;
5519 }
5520
5521 // Interpret a jsonObject as true or false
5522 static int obj_is_true( const jsonObject* obj ) {
5523         if( !obj )
5524                 return 0;
5525         else switch( obj->type )
5526         {
5527                 case JSON_BOOL :
5528                         if( obj->value.b )
5529                                 return 1;
5530                         else
5531                                 return 0;
5532                 case JSON_STRING :
5533                         if( strcasecmp( obj->value.s, "true" ) )
5534                                 return 0;
5535                         else
5536                                 return 1;
5537                 case JSON_NUMBER :          // Support 1/0 for perl's sake
5538                         if( jsonObjectGetNumber( obj ) == 1.0 )
5539                                 return 1;
5540                         else
5541                                 return 0;
5542                 default :
5543                         return 0;
5544         }
5545 }
5546
5547 // Translate a numeric code into a text string identifying a type of
5548 // jsonObject.  To be used for building error messages.
5549 static const char* json_type( int code ) {
5550         switch ( code )
5551         {
5552                 case 0 :
5553                         return "JSON_HASH";
5554                 case 1 :
5555                         return "JSON_ARRAY";
5556                 case 2 :
5557                         return "JSON_STRING";
5558                 case 3 :
5559                         return "JSON_NUMBER";
5560                 case 4 :
5561                         return "JSON_NULL";
5562                 case 5 :
5563                         return "JSON_BOOL";
5564                 default :
5565                         return "(unrecognized)";
5566         }
5567 }
5568
5569 // Extract the "primitive" attribute from an IDL field definition.
5570 // If we haven't initialized the app, then we must be running in
5571 // some kind of testbed.  In that case, default to "string".
5572 static const char* get_primitive( osrfHash* field ) {
5573         const char* s = osrfHashGet( field, "primitive" );
5574         if( !s ) {
5575                 if( child_initialized )
5576                         osrfLogError(
5577                                 OSRF_LOG_MARK,
5578                                 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5579                                 MODULENAME,
5580                                 osrfHashGet( field, "name" )
5581                         );
5582                 else
5583                         s = "string";
5584         }
5585         return s;
5586 }
5587
5588 // Extract the "datatype" attribute from an IDL field definition.
5589 // If we haven't initialized the app, then we must be running in
5590 // some kind of testbed.  In that case, default to to NUMERIC,
5591 // since we look at the datatype only for numbers.
5592 static const char* get_datatype( osrfHash* field ) {
5593         const char* s = osrfHashGet( field, "datatype" );
5594         if( !s ) {
5595                 if( child_initialized )
5596                         osrfLogError(
5597                                 OSRF_LOG_MARK,
5598                                 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5599                                 MODULENAME,
5600                                 osrfHashGet( field, "name" )
5601                         );
5602                 else
5603                         s = "NUMERIC";
5604         }
5605         return s;
5606 }
5607
5608 /*
5609 If the input string is potentially a valid SQL identifier, return 1.
5610 Otherwise return 0.
5611
5612 Purpose: to prevent certain kinds of SQL injection.  To that end we
5613 don't necessarily need to follow all the rules exactly, such as requiring
5614 that the first character not be a digit.
5615
5616 We allow leading and trailing white space.  In between, we do not allow
5617 punctuation (except for underscores and dollar signs), control
5618 characters, or embedded white space.
5619
5620 More pedantically we should allow quoted identifiers containing arbitrary
5621 characters, but for the foreseeable future such quoted identifiers are not
5622 likely to be an issue.
5623 */
5624 static int is_identifier( const char* s) {
5625         if( !s )
5626                 return 0;
5627
5628         // Skip leading white space
5629         while( isspace( (unsigned char) *s ) )
5630                 ++s;
5631
5632         if( !s )
5633                 return 0;   // Nothing but white space?  Not okay.
5634
5635         // Check each character until we reach white space or
5636         // end-of-string.  Letters, digits, underscores, and
5637         // dollar signs are okay. With the exception of periods
5638         // (as in schema.identifier), control characters and other
5639         // punctuation characters are not okay.  Anything else
5640         // is okay -- it could for example be part of a multibyte
5641         // UTF8 character such as a letter with diacritical marks,
5642         // and those are allowed.
5643         do {
5644                 if( isalnum( (unsigned char) *s )
5645                         || '.' == *s
5646                         || '_' == *s
5647                         || '$' == *s )
5648                         ;  // Fine; keep going
5649                 else if(   ispunct( (unsigned char) *s )
5650                                 || iscntrl( (unsigned char) *s ) )
5651                         return 0;
5652                         ++s;
5653         } while( *s && ! isspace( (unsigned char) *s ) );
5654
5655         // If we found any white space in the above loop,
5656         // the rest had better be all white space.
5657
5658         while( isspace( (unsigned char) *s ) )
5659                 ++s;
5660
5661         if( *s )
5662                 return 0;   // White space was embedded within non-white space
5663
5664         return 1;
5665 }
5666
5667 /*
5668 Determine whether to accept a character string as a comparison operator.
5669 Return 1 if it's good, or 0 if it's bad.
5670
5671 We don't validate it for real.  We just make sure that it doesn't contain
5672 any semicolons or white space (with special exceptions for a few specific
5673 operators).   The idea is to block certain kinds of SQL injection.  If it
5674 has no semicolons or white space but it's still not a valid operator, then
5675 the database will complain.
5676
5677 Another approach would be to compare the string against a short list of
5678 approved operators.  We don't do that because we want to allow custom
5679 operators like ">100*", which would be difficult or impossible to
5680 express otherwise in a JSON query.
5681 */
5682 static int is_good_operator( const char* op ) {
5683         if( !op ) return 0;   // Sanity check
5684
5685         const char* s = op;
5686         while( *s ) {
5687                 if( isspace( (unsigned char) *s ) ) {
5688                         // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
5689                         // and IS NOT DISTINCT FROM.
5690                         if( !strcasecmp( op, "similar to" ) )
5691                                 return 1;
5692                         else if( !strcasecmp( op, "is distinct from" ) )
5693                                 return 1;
5694                         else if( !strcasecmp( op, "is not distinct from" ) )
5695                                 return 1;
5696                         else
5697                                 return 0;
5698                 }
5699                 else if( ';' == *s )
5700                         return 0;
5701                 ++s;
5702         }
5703         return 1;
5704 }
5705
5706 /* ----------------------------------------------------------------------------------
5707 The following machinery supports a stack of query frames for use by SELECT().
5708
5709 A query frame caches information about one level of a SELECT query.  When we enter
5710 a subquery, we push another query frame onto the stack, and pop it off when we leave.
5711
5712 The query frame stores information about the core class, and about any joined classes
5713 in the FROM clause.
5714
5715 The main purpose is to map table aliases to classes and tables, so that a query can
5716 join to the same table more than once.  A secondary goal is to reduce the number of
5717 lookups in the IDL by caching the results.
5718  ----------------------------------------------------------------------------------*/
5719
5720 #define STATIC_CLASS_INFO_COUNT 3
5721
5722 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
5723
5724 /* ---------------------------------------------------------------------------
5725  Allocate a ClassInfo as raw memory.  Except for the in_use flag, we don't
5726  initialize it here.
5727  ---------------------------------------------------------------------------*/
5728 static ClassInfo* allocate_class_info( void ) {
5729         // In order to reduce the number of mallocs and frees, we return a static
5730         // instance of ClassInfo, if we can find one that we're not already using.
5731         // We rely on the fact that the compiler will implicitly initialize the
5732         // static instances so that in_use == 0.
5733
5734         int i;
5735         for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5736                 if( ! static_class_info[ i ].in_use ) {
5737                         static_class_info[ i ].in_use = 1;
5738                         return static_class_info + i;
5739                 }
5740         }
5741
5742         // The static ones are all in use.  Malloc one.
5743
5744         return safe_malloc( sizeof( ClassInfo ) );
5745 }
5746
5747 /* --------------------------------------------------------------------------
5748  Free any malloc'd memory owned by a ClassInfo; return it to a pristine state
5749 ---------------------------------------------------------------------------*/
5750 static void clear_class_info( ClassInfo* info ) {
5751         // Sanity check
5752         if( ! info )
5753                 return;
5754
5755         // Free any malloc'd strings
5756
5757         if( info->alias != info->alias_store )
5758                 free( info->alias );
5759
5760         if( info->class_name != info->class_name_store )
5761                 free( info->class_name );
5762
5763         free( info->source_def );
5764
5765         info->alias = info->class_name = info->source_def = NULL;
5766         info->next = NULL;
5767 }
5768
5769 /* --------------------------------------------------------------------------
5770  Deallocate a ClassInfo and everything it owns
5771 ---------------------------------------------------------------------------*/
5772 static void free_class_info( ClassInfo* info ) {
5773         // Sanity check
5774         if( ! info )
5775                 return;
5776
5777         clear_class_info( info );
5778
5779         // If it's one of the static instances, just mark it as not in use
5780
5781         int i;
5782         for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5783                 if( info == static_class_info + i ) {
5784                         static_class_info[ i ].in_use = 0;
5785                         return;
5786                 }
5787         }
5788
5789         // Otherwise it must have been malloc'd, so free it
5790
5791         free( info );
5792 }
5793
5794 /* --------------------------------------------------------------------------
5795  Populate an already-allocated ClassInfo.  Return 0 if successful, 1 if not.
5796 ---------------------------------------------------------------------------*/
5797 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
5798         // Sanity checks
5799         if( ! info ){
5800                 osrfLogError( OSRF_LOG_MARK,
5801                                           "%s ERROR: No ClassInfo available to populate", MODULENAME );
5802                 info->alias = info->class_name = info->source_def = NULL;
5803                 info->class_def = info->fields = info->links = NULL;
5804                 return 1;
5805         }
5806
5807         if( ! class ) {
5808                 osrfLogError( OSRF_LOG_MARK,
5809                                           "%s ERROR: No class name provided for lookup", MODULENAME );
5810                 info->alias = info->class_name = info->source_def = NULL;
5811                 info->class_def = info->fields = info->links = NULL;
5812                 return 1;
5813         }
5814
5815         // Alias defaults to class name if not supplied
5816         if( ! alias || ! alias[ 0 ] )
5817                 alias = class;
5818
5819         // Look up class info in the IDL
5820         osrfHash* class_def = osrfHashGet( oilsIDL(), class );
5821         if( ! class_def ) {
5822                 osrfLogError( OSRF_LOG_MARK,
5823                                           "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
5824                 info->alias = info->class_name = info->source_def = NULL;
5825                 info->class_def = info->fields = info->links = NULL;
5826                 return 1;
5827         } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
5828                 osrfLogError( OSRF_LOG_MARK,
5829                                           "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
5830                 info->alias = info->class_name = info->source_def = NULL;
5831                 info->class_def = info->fields = info->links = NULL;
5832                 return 1;
5833         }
5834
5835         osrfHash* links = osrfHashGet( class_def, "links" );
5836         if( ! links ) {
5837                 osrfLogError( OSRF_LOG_MARK,
5838                                           "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
5839                 info->alias = info->class_name = info->source_def = NULL;
5840                 info->class_def = info->fields = info->links = NULL;
5841                 return 1;
5842         }
5843
5844         osrfHash* fields = osrfHashGet( class_def, "fields" );
5845         if( ! fields ) {
5846                 osrfLogError( OSRF_LOG_MARK,
5847                                           "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
5848                 info->alias = info->class_name = info->source_def = NULL;
5849                 info->class_def = info->fields = info->links = NULL;
5850                 return 1;
5851         }
5852
5853         char* source_def = getRelation( class_def );
5854         if( ! source_def )
5855                 return 1;
5856
5857         // We got everything we need, so populate the ClassInfo
5858         if( strlen( alias ) > ALIAS_STORE_SIZE )
5859                 info->alias = strdup( alias );
5860         else {
5861                 strcpy( info->alias_store, alias );
5862                 info->alias = info->alias_store;
5863         }
5864
5865         if( strlen( class ) > CLASS_NAME_STORE_SIZE )
5866                 info->class_name = strdup( class );
5867         else {
5868                 strcpy( info->class_name_store, class );
5869                 info->class_name = info->class_name_store;
5870         }
5871
5872         info->source_def = source_def;
5873
5874         info->class_def = class_def;
5875         info->links     = links;
5876         info->fields    = fields;
5877
5878         return 0;
5879 }
5880
5881 #define STATIC_FRAME_COUNT 3
5882
5883 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
5884
5885 /* ---------------------------------------------------------------------------
5886  Allocate a ClassInfo as raw memory.  Except for the in_use flag, we don't
5887  initialize it here.
5888  ---------------------------------------------------------------------------*/
5889 static QueryFrame* allocate_frame( void ) {
5890         // In order to reduce the number of mallocs and frees, we return a static
5891         // instance of QueryFrame, if we can find one that we're not already using.
5892         // We rely on the fact that the compiler will implicitly initialize the
5893         // static instances so that in_use == 0.
5894
5895         int i;
5896         for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5897                 if( ! static_frame[ i ].in_use ) {
5898                         static_frame[ i ].in_use = 1;
5899                         return static_frame + i;
5900                 }
5901         }
5902
5903         // The static ones are all in use.  Malloc one.
5904
5905         return safe_malloc( sizeof( QueryFrame ) );
5906 }
5907
5908 /* --------------------------------------------------------------------------
5909  Free a QueryFrame, and all the memory it owns.
5910 ---------------------------------------------------------------------------*/
5911 static void free_query_frame( QueryFrame* frame ) {
5912         // Sanity check
5913         if( ! frame )
5914                 return;
5915
5916         clear_class_info( &frame->core );
5917
5918         // Free the join list
5919         ClassInfo* temp;
5920         ClassInfo* info = frame->join_list;
5921         while( info ) {
5922                 temp = info->next;
5923                 free_class_info( info );
5924                 info = temp;
5925         }
5926
5927         frame->join_list = NULL;
5928         frame->next = NULL;
5929
5930         // If the frame is a static instance, just mark it as unused
5931         int i;
5932         for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5933                 if( frame == static_frame + i ) {
5934                         static_frame[ i ].in_use = 0;
5935                         return;
5936                 }
5937         }
5938
5939         // Otherwise it must have been malloc'd, so free it
5940
5941         free( frame );
5942 }
5943
5944 /* --------------------------------------------------------------------------
5945  Search a given QueryFrame for a specified alias.  If you find it, return
5946  a pointer to the corresponding ClassInfo.  Otherwise return NULL.
5947 ---------------------------------------------------------------------------*/
5948 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
5949         if( ! frame || ! target ) {
5950                 return NULL;
5951         }
5952
5953         ClassInfo* found_class = NULL;
5954
5955         if( !strcmp( target, frame->core.alias ) )
5956                 return &(frame->core);
5957         else {
5958                 ClassInfo* curr_class = frame->join_list;
5959                 while( curr_class ) {
5960                         if( strcmp( target, curr_class->alias ) )
5961                                 curr_class = curr_class->next;
5962                         else {
5963                                 found_class = curr_class;
5964                                 break;
5965                         }
5966                 }
5967         }
5968
5969         return found_class;
5970 }
5971
5972 /* --------------------------------------------------------------------------
5973  Push a new (blank) QueryFrame onto the stack.
5974 ---------------------------------------------------------------------------*/
5975 static void push_query_frame( void ) {
5976         QueryFrame* frame = allocate_frame();
5977         frame->join_list = NULL;
5978         frame->next = curr_query;
5979
5980         // Initialize the ClassInfo for the core class
5981         ClassInfo* core = &frame->core;
5982         core->alias = core->class_name = core->source_def = NULL;
5983         core->class_def = core->fields = core->links = NULL;
5984
5985         curr_query = frame;
5986 }
5987
5988 /* --------------------------------------------------------------------------
5989  Pop a QueryFrame off the stack and destroy it
5990 ---------------------------------------------------------------------------*/
5991 static void pop_query_frame( void ) {
5992         // Sanity check
5993         if( ! curr_query )
5994                 return;
5995
5996         QueryFrame* popped = curr_query;
5997         curr_query = popped->next;
5998
5999         free_query_frame( popped );
6000 }
6001
6002 /* --------------------------------------------------------------------------
6003  Populate the ClassInfo for the core class.  Return 0 if successful, 1 if not.
6004 ---------------------------------------------------------------------------*/
6005 static int add_query_core( const char* alias, const char* class_name ) {
6006
6007         // Sanity checks
6008         if( ! curr_query ) {
6009                 osrfLogError( OSRF_LOG_MARK,
6010                                           "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
6011                 return 1;
6012         } else if( curr_query->core.alias ) {
6013                 osrfLogError( OSRF_LOG_MARK,
6014                                           "%s ERROR: Core class %s already populated as %s",
6015                                           MODULENAME, curr_query->core.class_name, curr_query->core.alias );
6016                 return 1;
6017         }
6018
6019         build_class_info( &curr_query->core, alias, class_name );
6020         if( curr_query->core.alias )
6021                 return 0;
6022         else {
6023                 osrfLogError( OSRF_LOG_MARK,
6024                                           "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
6025                 return 1;
6026         }
6027 }
6028
6029 /* --------------------------------------------------------------------------
6030  Search the current QueryFrame for a specified alias.  If you find it,
6031  return a pointer to the corresponding ClassInfo.  Otherwise return NULL.
6032 ---------------------------------------------------------------------------*/
6033 static ClassInfo* search_alias( const char* target ) {
6034         return search_alias_in_frame( curr_query, target );
6035 }
6036
6037 /* --------------------------------------------------------------------------
6038  Search all levels of query for a specified alias, starting with the
6039  current query.  If you find it, return a pointer to the corresponding
6040  ClassInfo.  Otherwise return NULL.
6041 ---------------------------------------------------------------------------*/
6042 static ClassInfo* search_all_alias( const char* target ) {
6043         ClassInfo* found_class = NULL;
6044         QueryFrame* curr_frame = curr_query;
6045
6046         while( curr_frame ) {
6047                 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6048                         break;
6049                 else
6050                         curr_frame = curr_frame->next;
6051         }
6052
6053         return found_class;
6054 }
6055
6056 /* --------------------------------------------------------------------------
6057  Add a class to the list of classes joined to the current query.
6058 ---------------------------------------------------------------------------*/
6059 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6060
6061         if( ! classname || ! *classname ) {    // sanity check
6062                 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6063                 return NULL;
6064         }
6065
6066         if( ! alias )
6067                 alias = classname;
6068
6069         const ClassInfo* conflict = search_alias( alias );
6070         if( conflict ) {
6071                 osrfLogError( OSRF_LOG_MARK,
6072                                           "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6073                                           MODULENAME, alias, conflict->class_name );
6074                 return NULL;
6075         }
6076
6077         ClassInfo* info = allocate_class_info();
6078
6079         if( build_class_info( info, alias, classname ) ) {
6080                 free_class_info( info );
6081                 return NULL;
6082         }
6083
6084         // Add the new ClassInfo to the join list of the current QueryFrame
6085         info->next = curr_query->join_list;
6086         curr_query->join_list = info;
6087
6088         return info;
6089 }
6090
6091 /* --------------------------------------------------------------------------
6092  Destroy all nodes on the query stack.
6093 ---------------------------------------------------------------------------*/
6094 static void clear_query_stack( void ) {
6095         while( curr_query )
6096                 pop_query_frame();
6097 }