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