]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/c-apps/oils_cstore.c
1. Changed search_alias() to an inline function, since it's a trivial wrapper.
[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* class_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 inline 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* class_meta,
5083                 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5084
5085         // XXX for now...
5086         dbhandle = writehandle;
5087
5088         char* core_class = osrfHashGet( class_meta, "classname" );
5089         char* pkey = osrfHashGet( class_meta, "primarykey" );
5090
5091         const jsonObject* _tmp;
5092
5093         char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5094         if (!sql) {
5095                 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
5096                 *err = -1;
5097                 return NULL;
5098         }
5099
5100         osrfLogDebug(OSRF_LOG_MARK, "%s SQL =  %s", MODULENAME, sql);
5101
5102         dbi_result result = dbi_conn_query(dbhandle, sql);
5103         if( NULL == result ) {
5104                 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
5105                         MODULENAME, osrfHashGet( class_meta, "fieldmapper" ), sql);
5106                 osrfAppSessionStatus(
5107                         ctx->session,
5108                         OSRF_STATUS_INTERNALSERVERERROR,
5109                         "osrfMethodException",
5110                         ctx->request,
5111                         "Severe query error -- see error log for more details"
5112                 );
5113                 *err = -1;
5114                 free(sql);
5115                 return jsonNULL;
5116
5117         } else {
5118                 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
5119         }
5120
5121         jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
5122         jsonObject* row_obj = NULL;
5123
5124         if (dbi_result_first_row(result)) {
5125
5126                 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5127                 // in a JSON_ARRAY of rows.  If two or more rows have the same key value, then
5128                 // eliminate the duplicates.
5129                 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
5130                 osrfHash* dedup = osrfNewHash();
5131                 do {
5132                         row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5133                         char* pkey_val = oilsFMGetString( row_obj, pkey );
5134                         if ( osrfHashGet( dedup, pkey_val ) ) {
5135                                 jsonObjectFree( row_obj );
5136                                 free( pkey_val );
5137                         } else {
5138                                 osrfHashSet( dedup, pkey_val, pkey_val );
5139                                 jsonObjectPush( res_list, row_obj );
5140                         }
5141                 } while (dbi_result_next_row(result));
5142                 osrfHashFree(dedup);
5143
5144         } else {
5145                 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
5146                         MODULENAME, sql );
5147         }
5148
5149         /* clean up the query */
5150         dbi_result_free(result);
5151         free(sql);
5152
5153         // If we're asked to flesh, and there's anything to flesh, then flesh.
5154         if (res_list->size && query_hash) {
5155                 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5156                 if (_tmp) {
5157                         // Get the flesh depth
5158                         int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5159                         if ( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5160                                 flesh_depth = max_flesh_depth;
5161
5162                         // We need a non-zero flesh depth, and a list of fields to flesh
5163                         const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5164                         if ( temp_blob && flesh_depth > 0 ) {
5165
5166                                 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5167                                 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5168
5169                                 osrfStringArray* link_fields = NULL;
5170                                 osrfHash* links = osrfHashGet( class_meta, "links" );
5171
5172                                 // Make an osrfStringArray of the names of fields to be fleshed
5173                                 if (flesh_fields) {
5174                                         if (flesh_fields->size == 1) {
5175                                                 const char* _t = jsonObjectGetString(
5176                                                         jsonObjectGetIndex( flesh_fields, 0 ) );
5177                                                 if (!strcmp(_t,"*"))
5178                                                         link_fields = osrfHashKeys( links );
5179                                         }
5180
5181                                         if (!link_fields) {
5182                                                 jsonObject* _f;
5183                                                 link_fields = osrfNewStringArray(1);
5184                                                 jsonIterator* _i = jsonNewIterator( flesh_fields );
5185                                                 while ((_f = jsonIteratorNext( _i ))) {
5186                                                         osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5187                                                 }
5188                                                 jsonIteratorFree(_i);
5189                                         }
5190                                 }
5191
5192                                 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5193
5194                                 // Iterate over the JSON_ARRAY of rows
5195                                 jsonObject* cur;
5196                                 unsigned long res_idx = 0;
5197                                 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5198
5199                                         int i = 0;
5200                                         const char* link_field;
5201
5202                                         // Iterate over the list of fleshable fields
5203                                         while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5204
5205                                                 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
5206
5207                                                 osrfHash* kid_link = osrfHashGet(links, link_field);
5208                                                 if (!kid_link)
5209                                                         continue;     // Not a link field; skip it
5210
5211                                                 osrfHash* field = osrfHashGet(fields, link_field);
5212                                                 if (!field)
5213                                                         continue;     // Not a field at all; skip it (IDL is ill-formed)
5214
5215                                                 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
5216                                                 if (!kid_idl)
5217                                                         continue;   // The class it links to doesn't exist; skip it
5218
5219                                                 const char* reltype = osrfHashGet( kid_link, "reltype" );
5220                                                 if( !reltype )
5221                                                         continue;   // No reltype; skip it (IDL is ill-formed)
5222
5223                                                 osrfHash* value_field = field;
5224
5225                                                 if (   !strcmp( reltype, "has_many" )
5226                                                         || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5227                                                         value_field = osrfHashGet(
5228                                                                 fields, osrfHashGet( class_meta, "primarykey" ) );
5229                                                 }
5230
5231                                                 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5232
5233                                                 if (link_map->size > 0) {
5234                                                         jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
5235                                                         jsonObjectPush(
5236                                                                 _kid_key,
5237                                                                 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5238                                                         );
5239
5240                                                         jsonObjectSetKey(
5241                                                                 flesh_blob,
5242                                                                 osrfHashGet(kid_link, "class"),
5243                                                                 _kid_key
5244                                                         );
5245                                                 };
5246
5247                                                 osrfLogDebug(
5248                                                         OSRF_LOG_MARK,
5249                                                         "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5250                                                         osrfHashGet(kid_link, "field"),
5251                                                         osrfHashGet(kid_link, "class"),
5252                                                         osrfHashGet(kid_link, "key"),
5253                                                         osrfHashGet(kid_link, "reltype")
5254                                                 );
5255
5256                                                 const char* search_key = jsonObjectGetString(
5257                                                         jsonObjectGetIndex( cur,
5258                                                                 atoi( osrfHashGet(value_field, "array_position") )
5259                                                         )
5260                                                 );
5261
5262                                                 if (!search_key) {
5263                                                         osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
5264                                                         continue;
5265                                                 }
5266
5267                                                 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
5268
5269                                                 // construct WHERE clause
5270                                                 jsonObject* where_clause  = jsonNewObjectType(JSON_HASH);
5271                                                 jsonObjectSetKey(
5272                                                         where_clause,
5273                                                         osrfHashGet(kid_link, "key"),
5274                                                         jsonNewObject( search_key )
5275                                                 );
5276
5277                                                 // construct the rest of the query, mostly
5278                                                 // by copying pieces of the previous level of query
5279                                                 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
5280                                                 jsonObjectSetKey( rest_of_query, "flesh",
5281                                                         jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5282                                                 );
5283
5284                                                 if (flesh_blob)
5285                                                         jsonObjectSetKey( rest_of_query, "flesh_fields",
5286                                                                 jsonObjectClone(flesh_blob) );
5287
5288                                                 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
5289                                                         jsonObjectSetKey( rest_of_query, "order_by",
5290                                                                 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
5291                                                         );
5292                                                 }
5293
5294                                                 if (jsonObjectGetKeyConst(query_hash, "select")) {
5295                                                         jsonObjectSetKey( rest_of_query, "select",
5296                                                                 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
5297                                                         );
5298                                                 }
5299
5300                                                 // do the query, recursively, to expand the fleshable field
5301                                                 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5302                                                         where_clause, rest_of_query, err);
5303
5304                                                 jsonObjectFree( where_clause );
5305                                                 jsonObjectFree( rest_of_query );
5306
5307                                                 if(*err) {
5308                                                         osrfStringArrayFree(link_fields);
5309                                                         jsonObjectFree(res_list);
5310                                                         jsonObjectFree(flesh_blob);
5311                                                         return jsonNULL;
5312                                                 }
5313
5314                                                 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects",
5315                                                         osrfHashGet(kid_link, "class"), kids->size);
5316
5317                                                 // Traverse the result set
5318                                                 jsonObject* X = NULL;
5319                                                 if ( link_map->size > 0 && kids->size > 0 ) {
5320                                                         X = kids;
5321                                                         kids = jsonNewObjectType(JSON_ARRAY);
5322
5323                                                         jsonObject* _k_node;
5324                                                         unsigned long res_idx = 0;
5325                                                         while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5326                                                                 jsonObjectPush(
5327                                                                         kids,
5328                                                                         jsonObjectClone(
5329                                                                                 jsonObjectGetIndex(
5330                                                                                         _k_node,
5331                                                                                         (unsigned long)atoi(
5332                                                                                                 osrfHashGet(
5333                                                                                                         osrfHashGet(
5334                                                                                                                 osrfHashGet(
5335                                                                                                                         osrfHashGet(
5336                                                                                                                                 oilsIDL(),
5337                                                                                                                                 osrfHashGet(kid_link, "class")
5338                                                                                                                         ),
5339                                                                                                                         "fields"
5340                                                                                                                 ),
5341                                                                                                                 osrfStringArrayGetString( link_map, 0 )
5342                                                                                                         ),
5343                                                                                                         "array_position"
5344                                                                                                 )
5345                                                                                         )
5346                                                                                 )
5347                                                                         )
5348                                                                 );
5349                                                         } // end while loop traversing X
5350                                                 }
5351
5352                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" ))
5353                                                         || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
5354                                                         osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5355                                                                 osrfHashGet(kid_link, "field"));
5356                                                         jsonObjectSetIndex(
5357                                                                 cur,
5358                                                                 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5359                                                                 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
5360                                                         );
5361                                                 }
5362
5363                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) {
5364                                                         // has_many
5365                                                         osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5366                                                                 osrfHashGet( kid_link, "field" ) );
5367                                                         jsonObjectSetIndex(
5368                                                                 cur,
5369                                                                 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5370                                                                 jsonObjectClone( kids )
5371                                                         );
5372                                                 }
5373
5374                                                 if (X) {
5375                                                         jsonObjectFree(kids);
5376                                                         kids = X;
5377                                                 }
5378
5379                                                 jsonObjectFree( kids );
5380
5381                                                 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5382                                                         osrfHashGet( kid_link, "field" ) );
5383                                                 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
5384
5385                                         } // end while loop traversing list of fleshable fields
5386                                 } // end while loop traversing res_list
5387                                 jsonObjectFree( flesh_blob );
5388                                 osrfStringArrayFree(link_fields);
5389                         }
5390                 }
5391         }
5392
5393         return res_list;
5394 }
5395
5396
5397 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
5398
5399         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5400 #ifdef PCRUD
5401         jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
5402 #else
5403         jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
5404 #endif
5405
5406         if (!verifyObjectClass(ctx, target)) {
5407                 *err = -1;
5408                 return jsonNULL;
5409         }
5410
5411         if( getXactId( ctx ) == NULL ) {
5412                 osrfAppSessionStatus(
5413                         ctx->session,
5414                         OSRF_STATUS_BADREQUEST,
5415                         "osrfMethodException",
5416                         ctx->request,
5417                         "No active transaction -- required for UPDATE"
5418                 );
5419                 *err = -1;
5420                 return jsonNULL;
5421         }
5422
5423         // The following test is harmless but redundant.  If a class is
5424         // readonly, we don't register an update method for it.
5425         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5426                 osrfAppSessionStatus(
5427                         ctx->session,
5428                         OSRF_STATUS_BADREQUEST,
5429                         "osrfMethodException",
5430                         ctx->request,
5431                         "Cannot UPDATE readonly class"
5432                 );
5433                 *err = -1;
5434                 return jsonNULL;
5435         }
5436
5437         dbhandle = writehandle;
5438         const char* trans_id = getXactId( ctx );
5439
5440         // Set the last_xact_id
5441         int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5442         if (index > -1) {
5443                 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5444                                 trans_id, target->classname, index);
5445                 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
5446         }
5447
5448         char* pkey = osrfHashGet(meta, "primarykey");
5449         osrfHash* fields = osrfHashGet(meta, "fields");
5450
5451         char* id = oilsFMGetString( target, pkey );
5452
5453         osrfLogDebug(
5454                 OSRF_LOG_MARK,
5455                 "%s updating %s object with %s = %s",
5456                 MODULENAME,
5457                 osrfHashGet(meta, "fieldmapper"),
5458                 pkey,
5459                 id
5460         );
5461
5462         growing_buffer* sql = buffer_init(128);
5463         buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
5464
5465         int first = 1;
5466         osrfHash* field_def = NULL;
5467         osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5468         while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
5469
5470                 // Skip virtual fields, and the primary key
5471                 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5472                         continue;
5473
5474                 const char* field_name = osrfHashIteratorKey( field_itr );
5475                 if( ! strcmp( field_name, pkey ) )
5476                         continue;
5477
5478                 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5479
5480                 int value_is_numeric = 0;    // boolean
5481                 char* value;
5482                 if (field_object && field_object->classname) {
5483                         value = oilsFMGetString(
5484                                 field_object,
5485                                 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
5486                         );
5487                 } else if( field_object && JSON_BOOL == field_object->type ) {
5488                         if( jsonBoolIsTrue( field_object ) )
5489                                 value = strdup( "t" );
5490                         else
5491                                 value = strdup( "f" );
5492                 } else {
5493                         value = jsonObjectToSimpleString( field_object );
5494                         if( field_object && JSON_NUMBER == field_object->type )
5495                                 value_is_numeric = 1;
5496                 }
5497
5498                 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5499                                 osrfHashGet(meta, "fieldmapper"), field_name, value);
5500
5501                 if (!field_object || field_object->type == JSON_NULL) {
5502                         if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
5503                                         && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5504                                 if (first) first = 0;
5505                                 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5506                                 buffer_fadd( sql, " %s = NULL", field_name );
5507                         }
5508
5509                 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5510                         if (first) first = 0;
5511                         else OSRF_BUFFER_ADD_CHAR(sql, ',');
5512
5513                         const char* numtype = get_datatype( field_def );
5514                         if ( !strncmp( numtype, "INT", 3 ) ) {
5515                                 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
5516                         } else if ( !strcmp( numtype, "NUMERIC" ) ) {
5517                                 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
5518                         } else {
5519                                 // Must really be intended as a string, so quote it
5520                                 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5521                                         buffer_fadd( sql, " %s = %s", field_name, value );
5522                                 } else {
5523                                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5524                                                 MODULENAME, value);
5525                                         osrfAppSessionStatus(
5526                                                 ctx->session,
5527                                                 OSRF_STATUS_INTERNALSERVERERROR,
5528                                                 "osrfMethodException",
5529                                                 ctx->request,
5530                                                 "Error quoting string -- please see the error log for more details"
5531                                         );
5532                                         free(value);
5533                                         free(id);
5534                                         osrfHashIteratorFree( field_itr );
5535                                         buffer_free(sql);
5536                                         *err = -1;
5537                                         return jsonNULL;
5538                                 }
5539                         }
5540
5541                         osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5542
5543                 } else {
5544                         if ( dbi_conn_quote_string(dbhandle, &value) ) {
5545                                 if (first) first = 0;
5546                                 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5547                                 buffer_fadd( sql, " %s = %s", field_name, value );
5548
5549                         } else {
5550                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5551                                 osrfAppSessionStatus(
5552                                         ctx->session,
5553                                         OSRF_STATUS_INTERNALSERVERERROR,
5554                                         "osrfMethodException",
5555                                         ctx->request,
5556                                         "Error quoting string -- please see the error log for more details"
5557                                 );
5558                                 free(value);
5559                                 free(id);
5560                                 osrfHashIteratorFree( field_itr );
5561                                 buffer_free(sql);
5562                                 *err = -1;
5563                                 return jsonNULL;
5564                         }
5565                 }
5566
5567                 free(value);
5568
5569         } // end while
5570
5571         osrfHashIteratorFree( field_itr );
5572
5573         jsonObject* obj = jsonNewObject(id);
5574
5575         if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5576                 dbi_conn_quote_string(dbhandle, &id);
5577
5578         buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5579
5580         char* query = buffer_release(sql);
5581         osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
5582
5583         dbi_result result = dbi_conn_query(dbhandle, query);
5584         free(query);
5585
5586         if (!result) {
5587                 jsonObjectFree(obj);
5588                 obj = jsonNewObject(NULL);
5589                 osrfLogError(
5590                         OSRF_LOG_MARK,
5591                         "%s ERROR updating %s object with %s = %s",
5592                         MODULENAME,
5593                         osrfHashGet(meta, "fieldmapper"),
5594                         pkey,
5595                         id
5596                 );
5597         }
5598
5599         free(id);
5600
5601         return obj;
5602 }
5603
5604 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
5605
5606         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5607
5608         if( getXactId( ctx ) == NULL ) {
5609                 osrfAppSessionStatus(
5610                         ctx->session,
5611                         OSRF_STATUS_BADREQUEST,
5612                         "osrfMethodException",
5613                         ctx->request,
5614                         "No active transaction -- required for DELETE"
5615                 );
5616                 *err = -1;
5617                 return jsonNULL;
5618         }
5619
5620         // The following test is harmless but redundant.  If a class is
5621         // readonly, we don't register a delete method for it.
5622         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5623                 osrfAppSessionStatus(
5624                         ctx->session,
5625                         OSRF_STATUS_BADREQUEST,
5626                         "osrfMethodException",
5627                         ctx->request,
5628                         "Cannot DELETE readonly class"
5629                 );
5630                 *err = -1;
5631                 return jsonNULL;
5632         }
5633
5634         dbhandle = writehandle;
5635
5636         jsonObject* obj;
5637
5638         char* pkey = osrfHashGet(meta, "primarykey");
5639
5640         int _obj_pos = 0;
5641 #ifdef PCRUD
5642                 _obj_pos = 1;
5643 #endif
5644
5645         char* id;
5646         if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
5647                 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5648                         *err = -1;
5649                         return jsonNULL;
5650                 }
5651
5652                 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5653         } else {
5654 #ifdef PCRUD
5655                 if (!verifyObjectPCRUD( ctx, NULL )) {
5656                         *err = -1;
5657                         return jsonNULL;
5658                 }
5659 #endif
5660                 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
5661         }
5662
5663         osrfLogDebug(
5664                 OSRF_LOG_MARK,
5665                 "%s deleting %s object with %s = %s",
5666                 MODULENAME,
5667                 osrfHashGet(meta, "fieldmapper"),
5668                 pkey,
5669                 id
5670         );
5671
5672         obj = jsonNewObject(id);
5673
5674         if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5675                 dbi_conn_quote_string(writehandle, &id);
5676
5677         dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
5678
5679         if (!result) {
5680                 jsonObjectFree(obj);
5681                 obj = jsonNewObject(NULL);
5682                 osrfLogError(
5683                         OSRF_LOG_MARK,
5684                         "%s ERROR deleting %s object with %s = %s",
5685                         MODULENAME,
5686                         osrfHashGet(meta, "fieldmapper"),
5687                         pkey,
5688                         id
5689                 );
5690         }
5691
5692         free(id);
5693
5694         return obj;
5695 }
5696
5697 /**
5698         @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5699         @param result An iterator for a result set; we only look at the current row.
5700         @param @meta Pointer to the class metadata for the core class.
5701         @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5702
5703         If a column is not defined in the IDL, or if it has no array_position defined for it in
5704         the IDL, or if it is defined as virtual, ignore it.
5705
5706         Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5707         or JSON_STRING.  Then insert this jsonObject into the JSON_ARRAY according to its
5708         array_position in the IDL.
5709
5710         A field defined in the IDL but not represented in the returned row will leave a hole
5711         in the JSON_ARRAY.  In effect it will be treated as a null value.
5712
5713         In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5714         regardless of their sequence in the SELECT statement.  The JSON_ARRAY is assigned the
5715         classname corresponding to the @a meta argument.
5716
5717         The calling code is responsible for freeing the the resulting jsonObject by calling
5718         jsonObjectFree().
5719 */
5720 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5721         if(!(result && meta)) return jsonNULL;
5722
5723         jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5724         jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
5725         osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
5726
5727         osrfHash* fields = osrfHashGet(meta, "fields");
5728
5729         int columnIndex = 1;
5730         const char* columnName;
5731
5732         /* cycle through the columns in the row returned from the database */
5733         while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5734
5735                 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5736
5737                 int fmIndex = -1;  // Will be set to the IDL's sequence number for this field
5738
5739                 /* determine the field type and storage attributes */
5740                 unsigned short type = dbi_result_get_field_type_idx(result, columnIndex);
5741                 int attr            = dbi_result_get_field_attribs_idx(result, columnIndex);
5742
5743                 // Fetch the IDL's sequence number for the field.  If the field isn't in the IDL,
5744                 // or if it has no sequence number there, or if it's virtual, skip it.
5745                 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
5746                 if( _f ) {
5747
5748                         if ( str_is_true( osrfHashGet(_f, "virtual") ) )
5749                                 continue;   // skip this column: IDL says it's virtual
5750
5751                         const char* pos = (char*)osrfHashGet(_f, "array_position");
5752                         if ( !pos )      // IDL has no sequence number for it.  This shouldn't happen,
5753                                 continue;    // since we assign sequence numbers dynamically as we load the IDL.
5754
5755                         fmIndex = atoi( pos );
5756                         osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
5757                 } else {
5758                         continue;     // This field is not defined in the IDL
5759                 }
5760
5761                 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
5762                 // sequence number from the IDL (which is likely to be different from the sequence
5763                 // of columns in the SELECT clause).
5764                 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5765                         jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
5766                 } else {
5767
5768                         switch( type ) {
5769
5770                                 case DBI_TYPE_INTEGER :
5771
5772                                         if( attr & DBI_INTEGER_SIZE8 )
5773                                                 jsonObjectSetIndex( object, fmIndex,
5774                                                         jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
5775                                         else
5776                                                 jsonObjectSetIndex( object, fmIndex,
5777                                                         jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
5778
5779                                         break;
5780
5781                                 case DBI_TYPE_DECIMAL :
5782                                         jsonObjectSetIndex( object, fmIndex,
5783                                                         jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
5784                                         break;
5785
5786                                 case DBI_TYPE_STRING :
5787
5788                                         jsonObjectSetIndex(
5789                                                 object,
5790                                                 fmIndex,
5791                                                 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
5792                                         );
5793
5794                                         break;
5795
5796                                 case DBI_TYPE_DATETIME : {
5797
5798                                         char dt_string[256] = "";
5799                                         struct tm gmdt;
5800
5801                                         // Fetch the date column as a time_t
5802                                         time_t _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5803
5804                                         // Translate the time_t to a human-readable string
5805                                         if (!(attr & DBI_DATETIME_DATE)) {
5806                                                 gmtime_r( &_tmp_dt, &gmdt );
5807                                                 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5808                                         } else if (!(attr & DBI_DATETIME_TIME)) {
5809                                                 localtime_r( &_tmp_dt, &gmdt );
5810                                                 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5811                                         } else {
5812                                                 localtime_r( &_tmp_dt, &gmdt );
5813                                                 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5814                                         }
5815
5816                                         jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
5817
5818                                         break;
5819                                 }
5820                                 case DBI_TYPE_BINARY :
5821                                         osrfLogError( OSRF_LOG_MARK,
5822                                                 "Can't do binary at column %s : index %d", columnName, columnIndex);
5823                         } // End switch
5824                 }
5825                 ++columnIndex;
5826         } // End while
5827
5828         return object;
5829 }
5830
5831 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5832         if(!result) return jsonNULL;
5833
5834         jsonObject* object = jsonNewObject(NULL);
5835
5836         time_t _tmp_dt;
5837         char dt_string[256];
5838         struct tm gmdt;
5839
5840         int fmIndex;
5841         int columnIndex = 1;
5842         int attr;
5843         unsigned short type;
5844         const char* columnName;
5845
5846         /* cycle through the column list */
5847         while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5848
5849                 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5850
5851                 fmIndex = -1; // reset the position
5852
5853                 /* determine the field type and storage attributes */
5854                 type = dbi_result_get_field_type_idx(result, columnIndex);
5855                 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5856
5857                 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5858                         jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
5859                 } else {
5860
5861                         switch( type ) {
5862
5863                                 case DBI_TYPE_INTEGER :
5864
5865                                         if( attr & DBI_INTEGER_SIZE8 )
5866                                                 jsonObjectSetKey( object, columnName,
5867                                                                 jsonNewNumberObject(dbi_result_get_longlong_idx(
5868                                                                                 result, columnIndex)) );
5869                                         else
5870                                                 jsonObjectSetKey( object, columnName,
5871                                                                 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
5872                                         break;
5873
5874                                 case DBI_TYPE_DECIMAL :
5875                                         jsonObjectSetKey( object, columnName,
5876                                                         jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
5877                                         break;
5878
5879                                 case DBI_TYPE_STRING :
5880                                         jsonObjectSetKey( object, columnName,
5881                                                         jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
5882                                         break;
5883
5884                                 case DBI_TYPE_DATETIME :
5885
5886                                         memset(dt_string, '\0', sizeof(dt_string));
5887                                         memset(&gmdt, '\0', sizeof(gmdt));
5888
5889                                         _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5890
5891
5892                                         if (!(attr & DBI_DATETIME_DATE)) {
5893                                                 gmtime_r( &_tmp_dt, &gmdt );
5894                                                 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5895                                         } else if (!(attr & DBI_DATETIME_TIME)) {
5896                                                 localtime_r( &_tmp_dt, &gmdt );
5897                                                 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5898                                         } else {
5899                                                 localtime_r( &_tmp_dt, &gmdt );
5900                                                 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5901                                         }
5902
5903                                         jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
5904                                         break;
5905
5906                                 case DBI_TYPE_BINARY :
5907                                         osrfLogError( OSRF_LOG_MARK,
5908                                                 "Can't do binary at column %s : index %d", columnName, columnIndex );
5909                         }
5910                 }
5911                 ++columnIndex;
5912         } // end while loop traversing result
5913
5914         return object;
5915 }
5916
5917 // Interpret a string as true or false
5918 static int str_is_true( const char* str ) {
5919         if( NULL == str || strcasecmp( str, "true" ) )
5920                 return 0;
5921         else
5922                 return 1;
5923 }
5924
5925 // Interpret a jsonObject as true or false
5926 static int obj_is_true( const jsonObject* obj ) {
5927         if( !obj )
5928                 return 0;
5929         else switch( obj->type )
5930         {
5931                 case JSON_BOOL :
5932                         if( obj->value.b )
5933                                 return 1;
5934                         else
5935                                 return 0;
5936                 case JSON_STRING :
5937                         if( strcasecmp( obj->value.s, "true" ) )
5938                                 return 0;
5939                         else
5940                                 return 1;
5941                 case JSON_NUMBER :          // Support 1/0 for perl's sake
5942                         if( jsonObjectGetNumber( obj ) == 1.0 )
5943                                 return 1;
5944                         else
5945                                 return 0;
5946                 default :
5947                         return 0;
5948         }
5949 }
5950
5951 // Translate a numeric code into a text string identifying a type of
5952 // jsonObject.  To be used for building error messages.
5953 static const char* json_type( int code ) {
5954         switch ( code )
5955         {
5956                 case 0 :
5957                         return "JSON_HASH";
5958                 case 1 :
5959                         return "JSON_ARRAY";
5960                 case 2 :
5961                         return "JSON_STRING";
5962                 case 3 :
5963                         return "JSON_NUMBER";
5964                 case 4 :
5965                         return "JSON_NULL";
5966                 case 5 :
5967                         return "JSON_BOOL";
5968                 default :
5969                         return "(unrecognized)";
5970         }
5971 }
5972
5973 // Extract the "primitive" attribute from an IDL field definition.
5974 // If we haven't initialized the app, then we must be running in
5975 // some kind of testbed.  In that case, default to "string".
5976 static const char* get_primitive( osrfHash* field ) {
5977         const char* s = osrfHashGet( field, "primitive" );
5978         if( !s ) {
5979                 if( child_initialized )
5980                         osrfLogError(
5981                                 OSRF_LOG_MARK,
5982                                 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5983                                 MODULENAME,
5984                                 osrfHashGet( field, "name" )
5985                         );
5986
5987                 s = "string";
5988         }
5989         return s;
5990 }
5991
5992 // Extract the "datatype" attribute from an IDL field definition.
5993 // If we haven't initialized the app, then we must be running in
5994 // some kind of testbed.  In that case, default to to NUMERIC,
5995 // since we look at the datatype only for numbers.
5996 static const char* get_datatype( osrfHash* field ) {
5997         const char* s = osrfHashGet( field, "datatype" );
5998         if( !s ) {
5999                 if( child_initialized )
6000                         osrfLogError(
6001                                 OSRF_LOG_MARK,
6002                                 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6003                                 MODULENAME,
6004                                 osrfHashGet( field, "name" )
6005                         );
6006                 else
6007                         s = "NUMERIC";
6008         }
6009         return s;
6010 }
6011
6012 /*
6013 If the input string is potentially a valid SQL identifier, return 1.
6014 Otherwise return 0.
6015
6016 Purpose: to prevent certain kinds of SQL injection.  To that end we
6017 don't necessarily need to follow all the rules exactly, such as requiring
6018 that the first character not be a digit.
6019
6020 We allow leading and trailing white space.  In between, we do not allow
6021 punctuation (except for underscores and dollar signs), control
6022 characters, or embedded white space.
6023
6024 More pedantically we should allow quoted identifiers containing arbitrary
6025 characters, but for the foreseeable future such quoted identifiers are not
6026 likely to be an issue.
6027 */
6028 static int is_identifier( const char* s) {
6029         if( !s )
6030                 return 0;
6031
6032         // Skip leading white space
6033         while( isspace( (unsigned char) *s ) )
6034                 ++s;
6035
6036         if( !s )
6037                 return 0;   // Nothing but white space?  Not okay.
6038
6039         // Check each character until we reach white space or
6040         // end-of-string.  Letters, digits, underscores, and
6041         // dollar signs are okay. With the exception of periods
6042         // (as in schema.identifier), control characters and other
6043         // punctuation characters are not okay.  Anything else
6044         // is okay -- it could for example be part of a multibyte
6045         // UTF8 character such as a letter with diacritical marks,
6046         // and those are allowed.
6047         do {
6048                 if( isalnum( (unsigned char) *s )
6049                         || '.' == *s
6050                         || '_' == *s
6051                         || '$' == *s )
6052                         ;  // Fine; keep going
6053                 else if(   ispunct( (unsigned char) *s )
6054                                 || iscntrl( (unsigned char) *s ) )
6055                         return 0;
6056                         ++s;
6057         } while( *s && ! isspace( (unsigned char) *s ) );
6058
6059         // If we found any white space in the above loop,
6060         // the rest had better be all white space.
6061
6062         while( isspace( (unsigned char) *s ) )
6063                 ++s;
6064
6065         if( *s )
6066                 return 0;   // White space was embedded within non-white space
6067
6068         return 1;
6069 }
6070
6071 /*
6072 Determine whether to accept a character string as a comparison operator.
6073 Return 1 if it's good, or 0 if it's bad.
6074
6075 We don't validate it for real.  We just make sure that it doesn't contain
6076 any semicolons or white space (with special exceptions for a few specific
6077 operators).   The idea is to block certain kinds of SQL injection.  If it
6078 has no semicolons or white space but it's still not a valid operator, then
6079 the database will complain.
6080
6081 Another approach would be to compare the string against a short list of
6082 approved operators.  We don't do that because we want to allow custom
6083 operators like ">100*", which would be difficult or impossible to
6084 express otherwise in a JSON query.
6085 */
6086 static int is_good_operator( const char* op ) {
6087         if( !op ) return 0;   // Sanity check
6088
6089         const char* s = op;
6090         while( *s ) {
6091                 if( isspace( (unsigned char) *s ) ) {
6092                         // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6093                         // and IS NOT DISTINCT FROM.
6094                         if( !strcasecmp( op, "similar to" ) )
6095                                 return 1;
6096                         else if( !strcasecmp( op, "is distinct from" ) )
6097                                 return 1;
6098                         else if( !strcasecmp( op, "is not distinct from" ) )
6099                                 return 1;
6100                         else
6101                                 return 0;
6102                 }
6103                 else if( ';' == *s )
6104                         return 0;
6105                 ++s;
6106         }
6107         return 1;
6108 }
6109
6110 /**
6111         @name Query Frame Management
6112
6113         The following machinery supports a stack of query frames for use by SELECT().
6114
6115         A query frame caches information about one level of a SELECT query.  When we enter
6116         a subquery, we push another query frame onto the stack, and pop it off when we leave.
6117
6118         The query frame stores information about the core class, and about any joined classes
6119         in the FROM clause.
6120
6121         The main purpose is to map table aliases to classes and tables, so that a query can
6122         join to the same table more than once.  A secondary goal is to reduce the number of
6123         lookups in the IDL by caching the results.
6124 */
6125 /*@{*/
6126
6127 #define STATIC_CLASS_INFO_COUNT 3
6128
6129 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6130
6131 /**
6132         @brief Allocate a ClassInfo as raw memory.
6133         @return Pointer to the newly allocated ClassInfo.
6134
6135         Except for the in_use flag, which is used only by the allocation and deallocation
6136         logic, we don't initialize the ClassInfo here.
6137 */
6138 static ClassInfo* allocate_class_info( void ) {
6139         // In order to reduce the number of mallocs and frees, we return a static
6140         // instance of ClassInfo, if we can find one that we're not already using.
6141         // We rely on the fact that the compiler will implicitly initialize the
6142         // static instances so that in_use == 0.
6143
6144         int i;
6145         for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6146                 if( ! static_class_info[ i ].in_use ) {
6147                         static_class_info[ i ].in_use = 1;
6148                         return static_class_info + i;
6149                 }
6150         }
6151
6152         // The static ones are all in use.  Malloc one.
6153
6154         return safe_malloc( sizeof( ClassInfo ) );
6155 }
6156
6157 /**
6158         @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6159         @param info Pointer to the ClassInfo to be cleared.
6160 */
6161 static void clear_class_info( ClassInfo* info ) {
6162         // Sanity check
6163         if( ! info )
6164                 return;
6165
6166         // Free any malloc'd strings
6167
6168         if( info->alias != info->alias_store )
6169                 free( info->alias );
6170
6171         if( info->class_name != info->class_name_store )
6172                 free( info->class_name );
6173
6174         free( info->source_def );
6175
6176         info->alias = info->class_name = info->source_def = NULL;
6177         info->next = NULL;
6178 }
6179
6180 /**
6181         @brief Free a ClassInfo and everything it owns.
6182         @param info Pointer to the ClassInfo to be freed.
6183 */
6184 static void free_class_info( ClassInfo* info ) {
6185         // Sanity check
6186         if( ! info )
6187                 return;
6188
6189         clear_class_info( info );
6190
6191         // If it's one of the static instances, just mark it as not in use
6192
6193         int i;
6194         for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6195                 if( info == static_class_info + i ) {
6196                         static_class_info[ i ].in_use = 0;
6197                         return;
6198                 }
6199         }
6200
6201         // Otherwise it must have been malloc'd, so free it
6202
6203         free( info );
6204 }
6205
6206 /**
6207         @brief Populate an already-allocated ClassInfo.
6208         @param info Pointer to the ClassInfo to be populated.
6209         @param alias Alias for the class.  If it is NULL, or an empty string, use the class
6210         name for an alias.
6211         @param class Name of the class.
6212         @return Zero if successful, or 1 if not.
6213
6214         Populate the ClassInfo with copies of the alias and class name, and with pointers to
6215         the relevant portions of the IDL for the specified class.
6216 */
6217 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6218         // Sanity checks
6219         if( ! info ){
6220                 osrfLogError( OSRF_LOG_MARK,
6221                                           "%s ERROR: No ClassInfo available to populate", MODULENAME );
6222                 info->alias = info->class_name = info->source_def = NULL;
6223                 info->class_def = info->fields = info->links = NULL;
6224                 return 1;
6225         }
6226
6227         if( ! class ) {
6228                 osrfLogError( OSRF_LOG_MARK,
6229                                           "%s ERROR: No class name provided for lookup", MODULENAME );
6230                 info->alias = info->class_name = info->source_def = NULL;
6231                 info->class_def = info->fields = info->links = NULL;
6232                 return 1;
6233         }
6234
6235         // Alias defaults to class name if not supplied
6236         if( ! alias || ! alias[ 0 ] )
6237                 alias = class;
6238
6239         // Look up class info in the IDL
6240         osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6241         if( ! class_def ) {
6242                 osrfLogError( OSRF_LOG_MARK,
6243                                           "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
6244                 info->alias = info->class_name = info->source_def = NULL;
6245                 info->class_def = info->fields = info->links = NULL;
6246                 return 1;
6247         } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6248                 osrfLogError( OSRF_LOG_MARK,
6249                                           "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
6250                 info->alias = info->class_name = info->source_def = NULL;
6251                 info->class_def = info->fields = info->links = NULL;
6252                 return 1;
6253         }
6254
6255         osrfHash* links = osrfHashGet( class_def, "links" );
6256         if( ! links ) {
6257                 osrfLogError( OSRF_LOG_MARK,
6258                                           "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
6259                 info->alias = info->class_name = info->source_def = NULL;
6260                 info->class_def = info->fields = info->links = NULL;
6261                 return 1;
6262         }
6263
6264         osrfHash* fields = osrfHashGet( class_def, "fields" );
6265         if( ! fields ) {
6266                 osrfLogError( OSRF_LOG_MARK,
6267                                           "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
6268                 info->alias = info->class_name = info->source_def = NULL;
6269                 info->class_def = info->fields = info->links = NULL;
6270                 return 1;
6271         }
6272
6273         char* source_def = getRelation( class_def );
6274         if( ! source_def )
6275                 return 1;
6276
6277         // We got everything we need, so populate the ClassInfo
6278         if( strlen( alias ) > ALIAS_STORE_SIZE )
6279                 info->alias = strdup( alias );
6280         else {
6281                 strcpy( info->alias_store, alias );
6282                 info->alias = info->alias_store;
6283         }
6284
6285         if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6286                 info->class_name = strdup( class );
6287         else {
6288                 strcpy( info->class_name_store, class );
6289                 info->class_name = info->class_name_store;
6290         }
6291
6292         info->source_def = source_def;
6293
6294         info->class_def = class_def;
6295         info->links     = links;
6296         info->fields    = fields;
6297
6298         return 0;
6299 }
6300
6301 #define STATIC_FRAME_COUNT 3
6302
6303 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6304
6305 /**
6306         @brief Allocate a QueryFrame as raw memory.
6307         @return Pointer to the newly allocated QueryFrame.
6308
6309         Except for the in_use flag, which is used only by the allocation and deallocation
6310         logic, we don't initialize the QueryFrame here.
6311 */
6312 static QueryFrame* allocate_frame( void ) {
6313         // In order to reduce the number of mallocs and frees, we return a static
6314         // instance of QueryFrame, if we can find one that we're not already using.
6315         // We rely on the fact that the compiler will implicitly initialize the
6316         // static instances so that in_use == 0.
6317
6318         int i;
6319         for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6320                 if( ! static_frame[ i ].in_use ) {
6321                         static_frame[ i ].in_use = 1;
6322                         return static_frame + i;
6323                 }
6324         }
6325
6326         // The static ones are all in use.  Malloc one.
6327
6328         return safe_malloc( sizeof( QueryFrame ) );
6329 }
6330
6331 /**
6332         @brief Free a QueryFrame, and all the memory it owns.
6333         @param frame Pointer to the QueryFrame to be freed.
6334 */
6335 static void free_query_frame( QueryFrame* frame ) {
6336         // Sanity check
6337         if( ! frame )
6338                 return;
6339
6340         clear_class_info( &frame->core );
6341
6342         // Free the join list
6343         ClassInfo* temp;
6344         ClassInfo* info = frame->join_list;
6345         while( info ) {
6346                 temp = info->next;
6347                 free_class_info( info );
6348                 info = temp;
6349         }
6350
6351         frame->join_list = NULL;
6352         frame->next = NULL;
6353
6354         // If the frame is a static instance, just mark it as unused
6355         int i;
6356         for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6357                 if( frame == static_frame + i ) {
6358                         static_frame[ i ].in_use = 0;
6359                         return;
6360                 }
6361         }
6362
6363         // Otherwise it must have been malloc'd, so free it
6364
6365         free( frame );
6366 }
6367
6368 /**
6369         @brief Search a given QueryFrame for a specified alias.
6370         @param frame Pointer to the QueryFrame to be searched.
6371         @param target The alias for which to search.
6372         @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6373 */
6374 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6375         if( ! frame || ! target ) {
6376                 return NULL;
6377         }
6378
6379         ClassInfo* found_class = NULL;
6380
6381         if( !strcmp( target, frame->core.alias ) )
6382                 return &(frame->core);
6383         else {
6384                 ClassInfo* curr_class = frame->join_list;
6385                 while( curr_class ) {
6386                         if( strcmp( target, curr_class->alias ) )
6387                                 curr_class = curr_class->next;
6388                         else {
6389                                 found_class = curr_class;
6390                                 break;
6391                         }
6392                 }
6393         }
6394
6395         return found_class;
6396 }
6397
6398 /**
6399         @brief Push a new (blank) QueryFrame onto the stack.
6400 */
6401 static void push_query_frame( void ) {
6402         QueryFrame* frame = allocate_frame();
6403         frame->join_list = NULL;
6404         frame->next = curr_query;
6405
6406         // Initialize the ClassInfo for the core class
6407         ClassInfo* core = &frame->core;
6408         core->alias = core->class_name = core->source_def = NULL;
6409         core->class_def = core->fields = core->links = NULL;
6410
6411         curr_query = frame;
6412 }
6413
6414 /**
6415         @brief Pop a QueryFrame off the stack and destroy it.
6416 */
6417 static void pop_query_frame( void ) {
6418         // Sanity check
6419         if( ! curr_query )
6420                 return;
6421
6422         QueryFrame* popped = curr_query;
6423         curr_query = popped->next;
6424
6425         free_query_frame( popped );
6426 }
6427
6428 /**
6429         @brief Populate the ClassInfo for the core class.
6430         @param alias Alias for the core class.  If it is NULL or an empty string, we use the
6431         class name as an alias.
6432         @param class_name Name of the core class.
6433         @return Zero if successful, or 1 if not.
6434
6435         Populate the ClassInfo of the core class with copies of the alias and class name, and
6436         with pointers to the relevant portions of the IDL for the core class.
6437 */
6438 static int add_query_core( const char* alias, const char* class_name ) {
6439
6440         // Sanity checks
6441         if( ! curr_query ) {
6442                 osrfLogError( OSRF_LOG_MARK,
6443                                           "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
6444                 return 1;
6445         } else if( curr_query->core.alias ) {
6446                 osrfLogError( OSRF_LOG_MARK,
6447                                           "%s ERROR: Core class %s already populated as %s",
6448                                           MODULENAME, curr_query->core.class_name, curr_query->core.alias );
6449                 return 1;
6450         }
6451
6452         build_class_info( &curr_query->core, alias, class_name );
6453         if( curr_query->core.alias )
6454                 return 0;
6455         else {
6456                 osrfLogError( OSRF_LOG_MARK,
6457                                           "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
6458                 return 1;
6459         }
6460 }
6461
6462 /**
6463         @brief Search the current QueryFrame for a specified alias.
6464         @param target The alias for which to search.
6465         @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6466 */
6467 static inline ClassInfo* search_alias( const char* target ) {
6468         return search_alias_in_frame( curr_query, target );
6469 }
6470
6471 /**
6472         @brief Search all levels of query for a specified alias, starting with the current query.
6473         @param target The alias for which to search.
6474         @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6475 */
6476 static ClassInfo* search_all_alias( const char* target ) {
6477         ClassInfo* found_class = NULL;
6478         QueryFrame* curr_frame = curr_query;
6479
6480         while( curr_frame ) {
6481                 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6482                         break;
6483                 else
6484                         curr_frame = curr_frame->next;
6485         }
6486
6487         return found_class;
6488 }
6489
6490 /**
6491         @brief Add a class to the list of classes joined to the current query.
6492         @param alias Alias of the class to be added.  If it is NULL or an empty string, we use
6493         the class name as an alias.
6494         @param classname The name of the class to be added.
6495         @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6496 */
6497 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6498
6499         if( ! classname || ! *classname ) {    // sanity check
6500                 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6501                 return NULL;
6502         }
6503
6504         if( ! alias )
6505                 alias = classname;
6506
6507         const ClassInfo* conflict = search_alias( alias );
6508         if( conflict ) {
6509                 osrfLogError( OSRF_LOG_MARK,
6510                                           "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6511                                           MODULENAME, alias, conflict->class_name );
6512                 return NULL;
6513         }
6514
6515         ClassInfo* info = allocate_class_info();
6516
6517         if( build_class_info( info, alias, classname ) ) {
6518                 free_class_info( info );
6519                 return NULL;
6520         }
6521
6522         // Add the new ClassInfo to the join list of the current QueryFrame
6523         info->next = curr_query->join_list;
6524         curr_query->join_list = info;
6525
6526         return info;
6527 }
6528
6529 /**
6530         @brief Destroy all nodes on the query stack.
6531 */
6532 static void clear_query_stack( void ) {
6533         while( curr_query )
6534                 pop_query_frame();
6535 }
6536
6537 /*@}*/