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