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