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