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