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