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