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