e9ae4af8e89c48d1a6d812ae94f42ab6a5d55560
[working/Evergreen.git] / Open-ILS / src / c-apps / oils_cstore.c
1 /**
2         @file oils_cstore.c
3         @brief As a server, perform database operations at the request of clients.
4 */
5
6 #include <ctype.h>
7 #include "opensrf/osrf_application.h"
8 #include "opensrf/osrf_settings.h"
9 #include "opensrf/osrf_message.h"
10 #include "opensrf/utils.h"
11 #include "opensrf/osrf_json.h"
12 #include "opensrf/log.h"
13 #include "openils/oils_utils.h"
14 #include <dbi/dbi.h>
15
16 #include <time.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <unistd.h>
20
21 #ifdef RSTORE
22 #  define MODULENAME "open-ils.reporter-store"
23 #else
24 #  ifdef PCRUD
25 #    define MODULENAME "open-ils.pcrud"
26 #  else
27 #    define MODULENAME "open-ils.cstore"
28 #  endif
29 #endif
30
31 // The next four macros are OR'd together as needed to form a set
32 // of bitflags.  SUBCOMBO enables an extra pair of parentheses when
33 // nesting one UNION, INTERSECT or EXCEPT inside another.
34 // SUBSELECT tells us we're in a subquery, so don't add the
35 // terminal semicolon yet.
36 #define SUBCOMBO    8
37 #define SUBSELECT   4
38 #define DISABLE_I18N    2
39 #define SELECT_DISTINCT 1
40
41 #define AND_OP_JOIN     0
42 #define OR_OP_JOIN      1
43
44 struct ClassInfoStruct;
45 typedef struct ClassInfoStruct ClassInfo;
46
47 #define ALIAS_STORE_SIZE 16
48 #define CLASS_NAME_STORE_SIZE 16
49
50 struct ClassInfoStruct {
51         char* alias;
52         char* class_name;
53         char* source_def;
54         osrfHash* class_def;      // Points into IDL
55         osrfHash* fields;         // Points into IDL
56         osrfHash* links;          // Points into IDL
57
58         // The remaining members are private and internal.  Client code should not
59         // access them directly.
60
61         ClassInfo* next;          // Supports linked list of joined classes
62         int in_use;               // boolean
63
64         // We usually store the alias and class name in the following arrays, and
65         // point the corresponding pointers at them.  When the string is too big
66         // for the array (which will probably never happen in practice), we strdup it.
67
68         char alias_store[ ALIAS_STORE_SIZE + 1 ];
69         char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
70 };
71
72 struct QueryFrameStruct;
73 typedef struct QueryFrameStruct QueryFrame;
74
75 struct QueryFrameStruct {
76         ClassInfo core;
77         ClassInfo* join_list;  // linked list of classes joined to the core class
78         QueryFrame* next;      // implements stack as linked list
79         int in_use;            // boolean
80 };
81
82 int osrfAppChildInit();
83 int osrfAppInitialize();
84 void osrfAppChildExit();
85
86 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
87
88 static void setXactId( osrfMethodContext* ctx );
89 static inline const char* getXactId( osrfMethodContext* ctx );
90 static inline void clearXactId( osrfMethodContext* ctx );
91
92 int beginTransaction ( osrfMethodContext* );
93 int commitTransaction ( osrfMethodContext* );
94 int rollbackTransaction ( osrfMethodContext* );
95
96 int setSavepoint ( osrfMethodContext* );
97 int releaseSavepoint ( osrfMethodContext* );
98 int rollbackSavepoint ( osrfMethodContext* );
99
100 int doJSONSearch ( osrfMethodContext* );
101
102 int dispatchCRUDMethod ( osrfMethodContext* );
103 static jsonObject* doCreate ( osrfMethodContext*, int* );
104 static jsonObject* doRetrieve ( osrfMethodContext*, int* );
105 static jsonObject* doUpdate ( osrfMethodContext*, int* );
106 static jsonObject* doDelete ( osrfMethodContext*, int* );
107 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
108                 jsonObject* where_hash, jsonObject* query_hash, int* err );
109 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
110 static jsonObject* oilsMakeJSONFromResult( dbi_result );
111
112 static char* searchSimplePredicate ( const char* op, const char* class_alias,
113                                 osrfHash* field, const jsonObject* node );
114 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
115 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject*);
116 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*, const char* );
117 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
118 static char* searchINPredicate ( const char*, osrfHash*,
119                                                                  jsonObject*, const char*, osrfMethodContext* );
120 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
121 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
122 static char* searchWHERE ( const jsonObject*, const ClassInfo*, int, osrfMethodContext* );
123 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
124 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
125
126 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
127
128 void userDataFree( void* );
129 static void sessionDataFree( char*, void* );
130 static char* getRelation( osrfHash* );
131 static int str_is_true( const char* str );
132 static int obj_is_true( const jsonObject* obj );
133 static const char* json_type( int code );
134 static const char* get_primitive( osrfHash* field );
135 static const char* get_datatype( osrfHash* field );
136 static int is_identifier( const char* s);
137 static int is_good_operator( const char* op );
138 static void pop_query_frame( void );
139 static void push_query_frame( void );
140 static int add_query_core( const char* alias, const char* class_name );
141 static ClassInfo* search_alias( const char* target );
142 static ClassInfo* search_all_alias( const char* target );
143 static ClassInfo* add_joined_class( const char* alias, const char* classname );
144 static void clear_query_stack( void );
145
146 #ifdef PCRUD
147 static jsonObject* verifyUserPCRUD( osrfMethodContext* );
148 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
149 static char* org_tree_root( osrfMethodContext* ctx );
150 static jsonObject* single_hash( const char* key, const char* value );
151 #endif
152
153 static int child_initialized = 0;   /* boolean */
154
155 static dbi_conn writehandle; /* our MASTER db connection */
156 static dbi_conn dbhandle; /* our CURRENT db connection */
157 //static osrfHash * readHandles;
158 static jsonObject* const jsonNULL = NULL; //
159 static int max_flesh_depth = 100;
160
161 // The following points to the top of a stack of QueryFrames.  It's a little
162 // confusing because the top level of the query is at the bottom of the stack.
163 static QueryFrame* curr_query = NULL;
164
165 /**
166         @brief Disconnect from the database.
167
168         This function is called when the server drone is about to terminate.
169 */
170 void osrfAppChildExit() {
171         osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
172
173         int same = 0;
174         if (writehandle == dbhandle) same = 1;
175         if (writehandle) {
176                 dbi_conn_query(writehandle, "ROLLBACK;");
177                 dbi_conn_close(writehandle);
178                 writehandle = NULL;
179         }
180         if (dbhandle && !same)
181         dbi_conn_close(dbhandle);
182
183         // XXX add cleanup of readHandles whenever that gets used
184
185         return;
186 }
187
188 /**
189         @brief Initialize the application.
190         @return Zero if successful, or non-zero if not.
191
192         Load the IDL file into an internal data structure for future reference.  Each non-virtual
193         class in the IDL corresponds to a table or view in the database, or to a subquery defined
194         in the IDL.  Ignore all virtual tables and virtual fields.
195
196         Register a number of methods, some of them general-purpose and others specific for
197         particular classes.
198
199         The name of the application is given by the MODULENAME macro, whose value depends on
200         conditional compilation.  The method names also incorporate MODULENAME, followed by a
201         dot, as a prefix.  Some methods are registered or not registered depending on whether
202         the PCRUD macro is defined, and on whether the IDL includes permacrud entries for the
203         class and method.
204
205         The general-purpose methods are as follows (minus their MODULENAME prefixes):
206
207         - json_query (not registered for PCRUD)
208         - transaction.begin
209         - transaction.commit
210         - transaction.rollback
211         - savepoint.set
212         - savepoint.release
213         - savepoint.rollback
214
215         For each non-virtual class, create up to eight class-specific methods:
216
217         - create    (not for readonly classes)
218         - retrieve
219         - update    (not for readonly classes)
220         - delete    (not for readonly classes
221         - search    (atomic and non-atomic versions)
222         - id_list   (atomic and non-atomic versions)
223
224         For PCRUD, the full method names follow the pattern "MODULENAME.method_type.classname".
225         Otherwise they follow the pattern "MODULENAME.direct.XXX.method_type", where XXX is the
226         fieldmapper name from the IDL, with every run of one or more consecutive colons replaced
227         by a period.  In addition, the names of atomic methods have a suffix of ".atomic".
228
229         This function is called when the registering the application, and is executed by the
230         listener before spawning the drones.
231 */
232 int osrfAppInitialize() {
233
234         osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
235         osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
236
237         if (!oilsIDLInit( osrf_settings_host_value("/IDL") ))
238                 return 1; /* return non-zero to indicate error */
239
240         growing_buffer* method_name = buffer_init(64);
241 #ifndef PCRUD
242         // Generic search thingy
243         buffer_add(method_name, MODULENAME);
244         buffer_add(method_name, ".json_query");
245         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
246                         "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
247 #endif
248
249         // first we register all the transaction and savepoint methods
250         buffer_reset(method_name);
251         OSRF_BUFFER_ADD(method_name, MODULENAME);
252         OSRF_BUFFER_ADD(method_name, ".transaction.begin");
253         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
254                         "beginTransaction", "", 0, 0 );
255
256         buffer_reset(method_name);
257         OSRF_BUFFER_ADD(method_name, MODULENAME);
258         OSRF_BUFFER_ADD(method_name, ".transaction.commit");
259         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
260                         "commitTransaction", "", 0, 0 );
261
262         buffer_reset(method_name);
263         OSRF_BUFFER_ADD(method_name, MODULENAME);
264         OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
265         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
266                         "rollbackTransaction", "", 0, 0 );
267
268         buffer_reset(method_name);
269         OSRF_BUFFER_ADD(method_name, MODULENAME);
270         OSRF_BUFFER_ADD(method_name, ".savepoint.set");
271         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
272                         "setSavepoint", "", 1, 0 );
273
274         buffer_reset(method_name);
275         OSRF_BUFFER_ADD(method_name, MODULENAME);
276         OSRF_BUFFER_ADD(method_name, ".savepoint.release");
277         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
278                         "releaseSavepoint", "", 1, 0 );
279
280         buffer_reset(method_name);
281         OSRF_BUFFER_ADD(method_name, MODULENAME);
282         OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
283         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
284                         "rollbackSavepoint", "", 1, 0 );
285
286         static const char* global_method[] = {
287                 "create",
288                 "retrieve",
289                 "update",
290                 "delete",
291                 "search",
292                 "id_list"
293         };
294         const int global_method_count
295                 = sizeof( global_method ) / sizeof ( global_method[0] );
296
297         unsigned long class_count = osrfHashGetCount( oilsIDL() );
298         osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
299         osrfLogDebug(OSRF_LOG_MARK,
300                 "At most %lu methods will be generated",
301                 (unsigned long) (class_count * global_method_count) );
302
303         osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
304         osrfHash* idlClass = NULL;
305
306         // For each class in the IDL...
307         while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
308
309                 const char* classname = osrfHashIteratorKey( class_itr );
310                 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
311
312                 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), MODULENAME )) {
313                         osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on",
314                                         MODULENAME, classname);
315                         continue;
316                 }
317
318                 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
319                         osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
320                         continue;
321                 }
322
323                 // Look up some other attributes of the current class
324                 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
325                 if( !idlClass_fieldmapper ) {
326                         osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL",
327                                         classname );
328                         continue;
329                 }
330
331 #ifdef PCRUD
332                 // For PCRUD, ignore classes with no permacrud attribute
333                 osrfHash* idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
334                 if (!idlClass_permacrud) {
335                         osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no permacrud in IDL", classname );
336                         continue;
337                 }
338 #endif
339                 const char* readonly = osrfHashGet(idlClass, "readonly");
340
341                 int i;
342                 for( i = 0; i < global_method_count; ++i ) {  // for each global method
343                         const char* method_type = global_method[ i ];
344                         osrfLogDebug(OSRF_LOG_MARK,
345                                 "Using files to build %s class methods for %s", method_type, classname);
346
347 #ifdef PCRUD
348                         // Treat "id_list" or "search" as forms of "retrieve"
349                         const char* tmp_method = method_type;
350                         if ( *tmp_method == 'i' || *tmp_method == 's') {  // "id_list" or "search"
351                                 tmp_method = "retrieve";
352                         }
353                         // Skip this method if there is no permacrud entry for it
354                         if (!osrfHashGet( idlClass_permacrud, tmp_method ))
355                                 continue;
356 #endif
357
358                         // No create, update, or delete methods for a readonly class
359                         if ( str_is_true( readonly )
360                                 && ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd') )
361                                 continue;
362
363                         buffer_reset( method_name );
364
365                         // Build the method name
366 #ifdef PCRUD
367                         // For PCRUD: MODULENAME.method_type.classname
368                         buffer_fadd(method_name, "%s.%s.%s", MODULENAME, method_type, classname);
369 #else
370                         // For non-PCRUD: MODULENAME.MODULENAME.direct.XXX.method_type
371                         // where XXX is the fieldmapper name from the IDL, with every run of
372                         // one or more consecutive colons replaced by a period.
373                         char* st_tmp = NULL;
374                         char* part = NULL;
375                         char* _fm = strdup( idlClass_fieldmapper );
376                         part = strtok_r(_fm, ":", &st_tmp);
377
378                         buffer_fadd(method_name, "%s.direct.%s", MODULENAME, part);
379
380                         while ((part = strtok_r(NULL, ":", &st_tmp))) {
381                                 OSRF_BUFFER_ADD_CHAR(method_name, '.');
382                                 OSRF_BUFFER_ADD(method_name, part);
383                         }
384                         OSRF_BUFFER_ADD_CHAR(method_name, '.');
385                         OSRF_BUFFER_ADD(method_name, method_type);
386                         free(_fm);
387 #endif
388
389                         // For an id_list or search method we specify the OSRF_METHOD_STREAMING option.
390                         // The consequence is that we implicitly create an atomic method in addition to
391                         // the usual non-atomic method.
392                         int flags = 0;
393                         if (*method_type == 'i' || *method_type == 's') {  // id_list or search
394                                 flags = flags | OSRF_METHOD_STREAMING;
395                         }
396
397                         osrfHash* method_meta = osrfNewHash();
398                         osrfHashSet( method_meta, idlClass, "class");
399                         osrfHashSet( method_meta, buffer_data( method_name ), "methodname" );
400                         osrfHashSet( method_meta, strdup(method_type), "methodtype" );
401
402                         // Register the method, with a pointer to an osrfHash to tell the method
403                         // its name, type, and class.
404                         osrfAppRegisterExtendedMethod(
405                                 MODULENAME,
406                                 OSRF_BUFFER_C_STR( method_name ),
407                                 "dispatchCRUDMethod",
408                                 "",
409                                 1,
410                                 flags,
411                                 (void*)method_meta
412                         );
413
414                 } // end for each global method
415         } // end for each class in IDL
416
417         buffer_free( method_name );
418         osrfHashIteratorFree( class_itr );
419
420         return 0;
421 }
422
423 /**
424         @brief Get a table name, view name, or subquery for use in a FROM clause.
425         @param class Pointer to the IDL class entry.
426         @return A table name, a view name, or a subquery in parentheses.
427
428         In some cases the IDL defines a class, not with a table name or a view name, but with
429         a SELECT statement, which may be used as a subquery.
430 */
431 static char* getRelation( osrfHash* class ) {
432
433         char* source_def = NULL;
434         const char* tabledef = osrfHashGet(class, "tablename");
435
436         if ( tabledef ) {
437                 source_def = strdup( tabledef );   // Return the name of a table or view
438         } else {
439                 tabledef = osrfHashGet( class, "source_definition" );
440                 if( tabledef ) {
441                         // Return a subquery, enclosed in parentheses
442                         source_def = safe_malloc( strlen( tabledef ) + 3 );
443                         source_def[ 0 ] = '(';
444                         strcpy( source_def + 1, tabledef );
445                         strcat( source_def, ")" );
446                 } else {
447                         // Not found: return an error
448                         const char* classname = osrfHashGet( class, "classname" );
449                         if( !classname )
450                                 classname = "???";
451                         osrfLogError(
452                                 OSRF_LOG_MARK,
453                                 "%s ERROR No tablename or source_definition for class \"%s\"",
454                                 MODULENAME,
455                                 classname
456                         );
457                 }
458         }
459
460         return source_def;
461 }
462
463 /**
464         @brief Initialize a server drone.
465         @return Zero if successful, -1 if not.
466
467         Connect to the database.  For each non-virtual class in the IDL, execute a dummy "SELECT * "
468         query to get the datatype of each column.  Record the datatypes in the loaded IDL.
469
470         This function is called by a server drone shortly after it is spawned by the listener.
471 */
472 int osrfAppChildInit() {
473
474         osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
475         dbi_initialize(NULL);
476         osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
477
478         char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
479         char* user   = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
480         char* host   = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
481         char* port   = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
482         char* db     = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
483         char* pw     = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
484         char* md     = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion",
485                         MODULENAME);
486
487         osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
488         writehandle = dbi_conn_new(driver);
489
490         if(!writehandle) {
491                 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
492                 return -1;
493         }
494         osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
495
496         osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database.  host=%s, "
497                         "port=%s, user=%s, pw=%s, db=%s", MODULENAME, host, port, user, pw, db );
498
499         if(host) dbi_conn_set_option(writehandle, "host", host );
500         if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
501         if(user) dbi_conn_set_option(writehandle, "username", user);
502         if(pw)   dbi_conn_set_option(writehandle, "password", pw );
503         if(db)   dbi_conn_set_option(writehandle, "dbname", db );
504
505         if(md)                     max_flesh_depth = atoi(md);
506         if(max_flesh_depth < 0)    max_flesh_depth = 1;
507         if(max_flesh_depth > 1000) max_flesh_depth = 1000;
508
509         free(user);
510         free(host);
511         free(port);
512         free(db);
513         free(pw);
514
515         const char* err;
516         if (dbi_conn_connect(writehandle) < 0) {
517                 sleep(1);
518                 if (dbi_conn_connect(writehandle) < 0) {
519                         dbi_conn_error(writehandle, &err);
520                         osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
521                         return -1;
522                 }
523         }
524
525         osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
526
527         osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
528         osrfHash* class = NULL;
529         growing_buffer* query_buf = buffer_init( 64 );
530
531         // For each class in the IDL...
532         while( (class = osrfHashIteratorNext( class_itr ) ) ) {
533                 const char* classname = osrfHashIteratorKey( class_itr );
534                 osrfHash* fields = osrfHashGet( class, "fields" );
535
536                 // If the class is virtual, ignore it
537                 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
538                         osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
539                         continue;
540                 }
541
542                 char* tabledef = getRelation(class);
543                 if( !tabledef )
544                         continue;   // No such relation -- a query of it would be doomed to failure
545
546                 buffer_reset( query_buf );
547                 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
548
549                 free(tabledef);
550
551                 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
552                                 MODULENAME, OSRF_BUFFER_C_STR( query_buf ) );
553
554                 dbi_result result = dbi_conn_query( writehandle, OSRF_BUFFER_C_STR( query_buf ) );
555                 if (result) {
556
557                         int columnIndex = 1;
558                         const char* columnName;
559                         osrfHash* _f;
560                         while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
561
562                                 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
563                                                 columnName );
564
565                                 /* fetch the fieldmapper index */
566                                 if( (_f = osrfHashGet(fields, columnName)) ) {
567
568                                         osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
569
570                                         /* determine the field type and storage attributes */
571
572                                         switch( dbi_result_get_field_type_idx(result, columnIndex) ) {
573
574                                                 case DBI_TYPE_INTEGER : {
575
576                                                         if ( !osrfHashGet(_f, "primitive") )
577                                                                 osrfHashSet(_f, "number", "primitive");
578
579                                                         int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
580                                                         if( attr & DBI_INTEGER_SIZE8 )
581                                                                 osrfHashSet(_f, "INT8", "datatype");
582                                                         else
583                                                                 osrfHashSet(_f, "INT", "datatype");
584                                                         break;
585                                                 }
586                                                 case DBI_TYPE_DECIMAL :
587                                                         if ( !osrfHashGet(_f, "primitive") )
588                                                                 osrfHashSet(_f, "number", "primitive");
589
590                                                         osrfHashSet(_f,"NUMERIC", "datatype");
591                                                         break;
592
593                                                 case DBI_TYPE_STRING :
594                                                         if ( !osrfHashGet(_f, "primitive") )
595                                                                 osrfHashSet(_f,"string", "primitive");
596
597                                                         osrfHashSet(_f,"TEXT", "datatype");
598                                                         break;
599
600                                                 case DBI_TYPE_DATETIME :
601                                                         if ( !osrfHashGet(_f, "primitive") )
602                                                                 osrfHashSet(_f,"string", "primitive");
603
604                                                         osrfHashSet(_f,"TIMESTAMP", "datatype");
605                                                         break;
606
607                                                 case DBI_TYPE_BINARY :
608                                                         if ( !osrfHashGet(_f, "primitive") )
609                                                                 osrfHashSet(_f,"string", "primitive");
610
611                                                         osrfHashSet(_f,"BYTEA", "datatype");
612                                         }
613
614                                         osrfLogDebug(
615                                                 OSRF_LOG_MARK,
616                                                 "Setting [%s] to primitive [%s] and datatype [%s]...",
617                                                 columnName,
618                                                 osrfHashGet(_f, "primitive"),
619                                                 osrfHashGet(_f, "datatype")
620                                         );
621                                 }
622                                 ++columnIndex;
623                         } // end while loop for traversing columns of result
624                         dbi_result_free(result);
625                 } else {
626                         osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", classname);
627                 }
628         } // end for each class in IDL
629
630         buffer_free( query_buf );
631         osrfHashIteratorFree( class_itr );
632         child_initialized = 1;
633         return 0;
634 }
635
636 /**
637         @brief Install a database driver.
638         @param conn Pointer to a database driver.
639
640         The driver is used to process quoted strings correctly.
641
642         This function is a sleazy hack, intended @em only for testing and debugging without
643         actually connecting to a database. Any real server process should initialize the
644         database connection by calling osrfAppChildInit().
645 */
646 void set_cstore_dbi_conn( dbi_conn conn ) {
647         dbhandle = writehandle = conn;
648 }
649
650 /**
651         @brief Free an osrfHash that stores a transaction ID.
652         @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
653
654         This function is a callback, to be called by the application session when it ends.
655         The application session stores the osrfHash via an opaque pointer.
656
657         If the osrfHash contains an entry for the key "xact_id", it means that an
658         uncommitted transaction is pending.  Roll it back.
659 */
660 void userDataFree( void* blob ) {
661         osrfHash* hash = (osrfHash*) blob;
662         if( osrfHashGet( hash, "xact_id" ) && writehandle )
663                 dbi_conn_query( writehandle, "ROLLBACK;" );
664
665         osrfHashFree( hash );
666 }
667
668 /**
669         @name Managing session data
670         @brief Maintain data stored via the userData pointer of the application session.
671
672         Currently, session-level data is stored in an osrfHash.  Other arrangements are
673         possible, and some would be more efficient.  The application session calls a
674         callback function to free userData before terminating.
675
676         Currently, the only data we store at the session level is the transaction id.  By this
677         means we can ensure that any pending transactions are rolled back before the application
678         session terminates.
679 */
680 /*@{*/
681
682 /**
683         @brief Free an item in the application session's userData.
684         @param key The name of a key for an osrfHash.
685         @param item An opaque pointer to the item associated with the key.
686
687         We store an osrfHash as userData with the application session, and arrange (by
688         installing userDataFree() as a different callback) for the session to free that
689         osrfHash before terminating.
690
691         This function is a callback for freeing items in the osrfHash.  If the item has a key
692         of "xact_id", the item is a transaction id for a transaction that is still pending.
693         It is just a character string, so we free it.
694
695         Currently the transaction ID is the only thing that we store in this osrfHash.  If we
696         ever store anything else in it, we will need to revisit this function so that it will
697         free whatever else needs freeing (which may or may not be just a character string).
698 */
699 static void sessionDataFree( char* key, void* item ) {
700         if ( !strcmp(key,"xact_id") ) {
701                 free( item );
702         }
703 }
704
705 /**
706         @brief Save a transaction id.
707         @param ctx Pointer to the method context.
708
709         Save the session_id of the current application session as a transaction id.
710 */
711 static void setXactId( osrfMethodContext* ctx ) {
712         if( ctx && ctx->session ) {
713                 osrfAppSession* session = ctx->session;
714
715                 // If the session doesn't already have a hash, create one.  Make sure
716                 // that the application session frees the hash when it terminates.
717                 if( NULL == session->userData ) {
718                         session->userData = osrfNewHash();
719                         osrfHashSetCallback( (osrfHash*) session->userData, &sessionDataFree );
720                         ctx->session->userDataFree = &userDataFree;
721                 }
722
723                 // Save the transaction id in the hash, with the key "xact_id"
724                 osrfHashSet( (osrfHash*) session->userData, strdup( session->session_id ),
725                                           "xact_id" );
726         }
727 }
728
729 /**
730         @brief Get the transaction ID for the current transaction, if any.
731         @param ctx Pointer to the method context.
732         @return Pointer to the transaction ID.
733
734         The return value points to an internal buffer, and will become invalid upon issuing
735         a commit or rollback.
736 */
737 static inline const char* getXactId( osrfMethodContext* ctx ) {
738         if( ctx && ctx->session && ctx->session->userData )
739                 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
740         else
741                 return NULL;
742 }
743
744 /**
745         @brief Clear the current transaction id.
746         @param ctx Pointer to the method context.
747 */
748 static inline void clearXactId( osrfMethodContext* ctx ) {
749         if( ctx && ctx->session && ctx->session->userData )
750                 osrfHashRemove( ctx->session->userData, "xact_id" );
751 }
752 /*@}*/
753
754 /**
755         @brief Implement the transaction.begin method.
756         @param ctx Pointer to the method context.
757         @return Zero if successful, or -1 upon error.
758
759         Start a transaction.  Save a transaction ID for future reference.
760
761         Method parameters:
762         - authkey (PCRUD only)
763
764         Return to client: Transaction ID
765 */
766 int beginTransaction ( osrfMethodContext* ctx ) {
767         if(osrfMethodVerifyContext( ctx )) {
768                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
769                 return -1;
770         }
771
772 #ifdef PCRUD
773         jsonObject* user = verifyUserPCRUD( ctx );
774         if (!user)
775                 return -1;
776         jsonObjectFree(user);
777 #endif
778
779         dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
780         if (!result) {
781                 osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
782                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
783                                 "osrfMethodException", ctx->request, "Error starting transaction" );
784                 return -1;
785         } else {
786                 setXactId( ctx );
787                 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
788                 osrfAppRespondComplete( ctx, ret );
789                 jsonObjectFree(ret);
790         }
791         return 0;
792 }
793
794 /**
795         @brief Implement the savepoint.set method.
796         @param ctx Pointer to the method context.
797         @return Zero if successful, or -1 if not.
798
799         Issue a SAVEPOINT to the database server.
800
801         Method parameters:
802         - authkey (PCRUD only)
803         - savepoint name
804
805         Return to client: Savepoint name
806 */
807 int setSavepoint ( osrfMethodContext* ctx ) {
808         if(osrfMethodVerifyContext( ctx )) {
809                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
810                 return -1;
811         }
812
813         int spNamePos = 0;
814 #ifdef PCRUD
815         spNamePos = 1;
816         jsonObject* user = verifyUserPCRUD( ctx );
817         if (!user)
818                 return -1;
819         jsonObjectFree(user);
820 #endif
821
822         // Verify that a transaction is pending
823         const char* trans_id = getXactId( ctx );
824         if( NULL == trans_id ) {
825                 osrfAppSessionStatus(
826                         ctx->session,
827                         OSRF_STATUS_INTERNALSERVERERROR,
828                         "osrfMethodException",
829                         ctx->request,
830                         "No active transaction -- required for savepoints"
831                 );
832                 return -1;
833         }
834
835         // Get the savepoint name from the method params
836         const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
837
838         dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
839         if (!result) {
840                 osrfLogError(
841                         OSRF_LOG_MARK,
842                         "%s: Error creating savepoint %s in transaction %s",
843                         MODULENAME,
844                         spName,
845                         trans_id
846                 );
847                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
848                                 "osrfMethodException", ctx->request, "Error creating savepoint" );
849                 return -1;
850         } else {
851                 jsonObject* ret = jsonNewObject(spName);
852                 osrfAppRespondComplete( ctx, ret );
853                 jsonObjectFree(ret);
854         }
855         return 0;
856 }
857
858 /**
859         @brief Implement the savepoint.release method.
860         @param ctx Pointer to the method context.
861         @return Zero if successful, or -1 if not.
862
863         Issue a RELEASE SAVEPOINT to the database server.
864
865         Method parameters:
866         - authkey (PCRUD only)
867         - savepoint name
868
869         Return to client: Savepoint name
870 */
871 int releaseSavepoint ( osrfMethodContext* ctx ) {
872         if(osrfMethodVerifyContext( ctx )) {
873                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
874                 return -1;
875         }
876
877         int spNamePos = 0;
878 #ifdef PCRUD
879         spNamePos = 1;
880         jsonObject* user = verifyUserPCRUD( ctx );
881         if (!user)
882                 return -1;
883         jsonObjectFree(user);
884 #endif
885
886         // Verify that a transaction is pending
887         const char* trans_id = getXactId( ctx );
888         if( NULL == trans_id ) {
889                 osrfAppSessionStatus(
890                         ctx->session,
891                         OSRF_STATUS_INTERNALSERVERERROR,
892                         "osrfMethodException",
893                         ctx->request,
894                         "No active transaction -- required for savepoints"
895                 );
896                 return -1;
897         }
898
899         // Get the savepoint name from the method params
900         const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
901
902         dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
903         if (!result) {
904                 osrfLogError(
905                         OSRF_LOG_MARK,
906                         "%s: Error releasing savepoint %s in transaction %s",
907                         MODULENAME,
908                         spName,
909                         trans_id
910                 );
911                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
912                                 "osrfMethodException", ctx->request, "Error releasing savepoint" );
913                 return -1;
914         } else {
915                 jsonObject* ret = jsonNewObject(spName);
916                 osrfAppRespondComplete( ctx, ret );
917                 jsonObjectFree(ret);
918         }
919         return 0;
920 }
921
922 /**
923         @brief Implement the savepoint.rollback method.
924         @param ctx Pointer to the method context.
925         @return Zero if successful, or -1 if not.
926
927         Issue a ROLLBACK TO SAVEPOINT to the database server.
928
929         Method parameters:
930         - authkey (PCRUD only)
931         - savepoint name
932
933         Return to client: Savepoint name
934 */
935 int rollbackSavepoint ( osrfMethodContext* ctx ) {
936         if(osrfMethodVerifyContext( ctx )) {
937                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
938                 return -1;
939         }
940
941         int spNamePos = 0;
942 #ifdef PCRUD
943         spNamePos = 1;
944         jsonObject* user = verifyUserPCRUD( ctx );
945         if (!user)
946                 return -1;
947         jsonObjectFree(user);
948 #endif
949
950         // Verify that a transaction is pending
951         const char* trans_id = getXactId( ctx );
952         if( NULL == trans_id ) {
953                 osrfAppSessionStatus(
954                         ctx->session,
955                         OSRF_STATUS_INTERNALSERVERERROR,
956                         "osrfMethodException",
957                         ctx->request,
958                         "No active transaction -- required for savepoints"
959                 );
960                 return -1;
961         }
962
963         // Get the savepoint name from the method params
964         const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
965
966         dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
967         if (!result) {
968                 osrfLogError(
969                         OSRF_LOG_MARK,
970                         "%s: Error rolling back savepoint %s in transaction %s",
971                         MODULENAME,
972                         spName,
973                         trans_id
974                 );
975                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
976                                 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
977                 return -1;
978         } else {
979                 jsonObject* ret = jsonNewObject(spName);
980                 osrfAppRespondComplete( ctx, ret );
981                 jsonObjectFree(ret);
982         }
983         return 0;
984 }
985
986 /**
987         @brief Implement the transaction.commit method.
988         @param ctx Pointer to the method context.
989         @return Zero if successful, or -1 if not.
990
991         Issue a COMMIT to the database server.
992
993         Method parameters:
994         - authkey (PCRUD only)
995
996         Return to client: Transaction ID.
997 */
998 int commitTransaction ( osrfMethodContext* ctx ) {
999         if(osrfMethodVerifyContext( ctx )) {
1000                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1001                 return -1;
1002         }
1003
1004 #ifdef PCRUD
1005         jsonObject* user = verifyUserPCRUD( ctx );
1006         if (!user)
1007                 return -1;
1008         jsonObjectFree(user);
1009 #endif
1010
1011         // Verify that a transaction is pending
1012         const char* trans_id = getXactId( ctx );
1013         if( NULL == trans_id ) {
1014                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1015                                 "osrfMethodException", ctx->request, "No active transaction to commit" );
1016                 return -1;
1017         }
1018
1019         dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
1020         if (!result) {
1021                 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
1022                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1023                                 "osrfMethodException", ctx->request, "Error committing transaction" );
1024                 return -1;
1025         } else {
1026                 jsonObject* ret = jsonNewObject( trans_id );
1027                 osrfAppRespondComplete( ctx, ret );
1028                 jsonObjectFree(ret);
1029                 clearXactId( ctx );
1030         }
1031         return 0;
1032 }
1033
1034 /**
1035         @brief Implement the transaction.rollback method.
1036         @param ctx Pointer to the method context.
1037         @return Zero if successful, or -1 if not.
1038
1039         Issue a ROLLBACK to the database server.
1040
1041         Method parameters:
1042         - authkey (PCRUD only)
1043
1044         Return to client: Transaction ID
1045 */
1046 int rollbackTransaction ( osrfMethodContext* ctx ) {
1047         if(osrfMethodVerifyContext( ctx )) {
1048                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
1049                 return -1;
1050         }
1051
1052 #ifdef PCRUD
1053         jsonObject* user = verifyUserPCRUD( ctx );
1054         if (!user)
1055                 return -1;
1056         jsonObjectFree(user);
1057 #endif
1058
1059         // Verify that a transaction is pending
1060         const char* trans_id = getXactId( ctx );
1061         if( NULL == trans_id ) {
1062                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1063                                 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1064                 return -1;
1065         }
1066
1067         dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
1068         if (!result) {
1069                 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
1070                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1071                                 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1072                 return -1;
1073         } else {
1074                 jsonObject* ret = jsonNewObject( trans_id );
1075                 osrfAppRespondComplete( ctx, ret );
1076                 jsonObjectFree(ret);
1077                 clearXactId( ctx );
1078         }
1079         return 0;
1080 }
1081
1082 /**
1083         @brief Implement the class-specific methods.
1084         @param ctx Pointer to the method context.
1085         @return Zero if successful, or -1 if not.
1086
1087         Branch on the method type: create, retrieve, update, delete, search, and id_list.
1088
1089         The method parameters and the type of value returned to the client depend on the method
1090         type.  However, for PCRUD methods, the first method parameter should always be an
1091         authkey.
1092 */
1093 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
1094         if(osrfMethodVerifyContext( ctx )) {
1095                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
1096                 return -1;
1097         }
1098
1099         int err = 0;                // to be returned to caller
1100         jsonObject * obj = NULL;    // to be returned to client
1101
1102         // Get the method type so that we can branch on it
1103         osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1104         const char* methodtype = osrfHashGet( method_meta, "methodtype" );
1105
1106         if (!strcmp(methodtype, "create")) {
1107                 obj = doCreate(ctx, &err);
1108                 osrfAppRespondComplete( ctx, obj );
1109         }
1110         else if (!strcmp(methodtype, "retrieve")) {
1111                 obj = doRetrieve(ctx, &err);
1112                 osrfAppRespondComplete( ctx, obj );
1113         }
1114         else if (!strcmp(methodtype, "update")) {
1115                 obj = doUpdate(ctx, &err);
1116                 osrfAppRespondComplete( ctx, obj );
1117         }
1118         else if (!strcmp(methodtype, "delete")) {
1119                 obj = doDelete(ctx, &err);
1120                 osrfAppRespondComplete( ctx, obj );
1121         }
1122         else if (!strcmp(methodtype, "search")) {
1123
1124                 // Implement search method: return rows of the specified class that satisfy
1125                 // a specified WHERE clause.
1126
1127                 // Method parameters:
1128                 // - authkey (PCRUD only)
1129                 // - WHERE clause, as jsonObject
1130                 // - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1131
1132                 jsonObject* where_clause;
1133                 jsonObject* rest_of_query;
1134
1135 #ifdef PCRUD
1136                 where_clause  = jsonObjectGetIndex( ctx->params, 1 );
1137                 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1138 #else
1139                 where_clause  = jsonObjectGetIndex( ctx->params, 0 );
1140                 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1141 #endif
1142
1143                 // Do the query
1144                 osrfHash* class_obj = osrfHashGet( method_meta, "class" );
1145                 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
1146
1147                 if(err)
1148                         return err;
1149
1150                 // Return each row to the client (except that some may be suppressed by PCRUD)
1151                 jsonObject* cur = 0;
1152                 unsigned long res_idx = 0;
1153                 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1154 #ifdef PCRUD
1155                         if(!verifyObjectPCRUD(ctx, cur))
1156                                 continue;
1157 #endif
1158                         osrfAppRespond( ctx, cur );
1159                 }
1160                 osrfAppRespondComplete( ctx, NULL );
1161
1162         } else if (!strcmp(methodtype, "id_list")) {
1163
1164                 // Implement the id_list method.  Return the primary key values for all rows of the
1165                 // relevant class that satisfy a specified WHERE clause.  This method relies on the
1166                 // assumption that every class has a primary key consisting of a single column.
1167
1168                 // Method parameters:
1169                 // - authkey (PCRUD only)
1170                 // - WHERE clause, as jsonObject
1171                 // - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1172
1173                 jsonObject* where_clause;
1174                 jsonObject* rest_of_query;
1175
1176                 // We use the where clause without change.  But we need to massage the rest of the
1177                 // query, so we work with a copy of it instead of modifying the original.
1178
1179 #ifdef PCRUD
1180                 where_clause  = jsonObjectGetIndex( ctx->params, 1 );
1181                 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1182 #else
1183                 where_clause  = jsonObjectGetIndex( ctx->params, 0 );
1184                 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1185 #endif
1186
1187                 // Eliminate certain SQL clauses, if present
1188                 if ( rest_of_query ) {
1189                         jsonObjectRemoveKey( rest_of_query, "select" );
1190                         jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1191                         jsonObjectRemoveKey( rest_of_query, "flesh" );
1192                         jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
1193                 } else {
1194                         rest_of_query = jsonNewObjectType( JSON_HASH );
1195                 }
1196
1197                 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1198
1199                 // Get the class metadata
1200                 osrfHash* class_obj = osrfHashGet( method_meta, "class" );
1201
1202                 // Build a SELECT list containing just the primary key,
1203                 // i.e. like { "classname":["keyname"] }
1204                 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1205                 jsonObjectPush( col_list_obj,     // Load array with name of primary key
1206                         jsonNewObject( osrfHashGet( class_obj, "primarykey" ) ) );
1207                 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1208                 jsonObjectSetKey( select_clause, osrfHashGet( class_obj, "classname" ), col_list_obj );
1209
1210                 jsonObjectSetKey( rest_of_query, "select", select_clause );
1211
1212                 // Do the query
1213                 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
1214
1215                 jsonObjectFree( rest_of_query );
1216                 if(err)
1217                         return err;
1218
1219                 // Return each primary key value to the client
1220                 jsonObject* cur;
1221                 unsigned long res_idx = 0;
1222                 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1223 #ifdef PCRUD
1224                         if(!verifyObjectPCRUD(ctx, cur))
1225                                 continue;
1226 #endif
1227                         osrfAppRespond( ctx,
1228                                 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
1229                         );
1230                 }
1231                 osrfAppRespondComplete( ctx, NULL );
1232
1233         } else {
1234                 osrfAppRespondComplete( ctx, obj );      // should be unreachable...
1235         }
1236
1237         jsonObjectFree(obj);
1238
1239         return err;
1240 }
1241
1242 /**
1243         @brief Verify that we have a valid class reference.
1244         @param ctx Pointer to the method context.
1245         @param param Pointer to the method parameters.
1246         @return 1 if the class reference is valid, or zero if it isn't.
1247
1248         The class of the method params must match the class to which the method id devoted.
1249         For PCRUD there are additional restrictions.
1250 */
1251 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1252
1253         osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1254         osrfHash* class = osrfHashGet( method_meta, "class" );
1255
1256         // Compare the method's class to the parameters' class
1257         if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1258
1259                 // Oops -- they don't match.  Complain.
1260                 growing_buffer* msg = buffer_init(128);
1261                 buffer_fadd(
1262                         msg,
1263                         "%s: %s method for type %s was passed a %s",
1264                         MODULENAME,
1265                         osrfHashGet(method_meta, "methodtype"),
1266                         osrfHashGet(class, "classname"),
1267                         param->classname ? param->classname : "(null)"
1268                 );
1269
1270                 char* m = buffer_release(msg);
1271                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1272                                 ctx->request, m );
1273                 free(m);
1274
1275                 return 0;
1276         }
1277
1278         int ret = 1;
1279 #ifdef PCRUD
1280         ret = verifyObjectPCRUD( ctx, param );
1281 #endif
1282
1283         return ret;
1284 }
1285
1286 #ifdef PCRUD
1287
1288 /**
1289         @brief (PCRUD only) Verify that the user is properly logged in.
1290         @param ctx Pointer to the method context.
1291         @return If the user is logged in, a pointer to the user object from the authentication
1292         server; otherwise NULL.
1293 */
1294 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1295
1296         // Get the authkey (the first method parameter)
1297         const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1298         jsonObject* auth_object = jsonNewObject(auth);
1299
1300         // Fetch the user object from the authentication server
1301         jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1302                         auth_object );
1303         jsonObjectFree(auth_object);
1304
1305         if (!user->classname || strcmp(user->classname, "au")) {
1306
1307                 growing_buffer* msg = buffer_init(128);
1308                 buffer_fadd(
1309                         msg,
1310                         "%s: permacrud received a bad auth token: %s",
1311                         MODULENAME,
1312                         auth
1313                 );
1314
1315                 char* m = buffer_release(msg);
1316                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1317                                 ctx->request, m );
1318
1319                 free(m);
1320                 jsonObjectFree(user);
1321                 user = NULL;
1322         }
1323
1324         return user;
1325 }
1326
1327 static int verifyObjectPCRUD (  osrfMethodContext* ctx, const jsonObject* obj ) {
1328
1329         dbhandle = writehandle;
1330
1331         osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1332         osrfHash* class = osrfHashGet( method_metadata, "class" );
1333         const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1334         int fetch = 0;
1335
1336         if ( ( *method_type == 's' || *method_type == 'i' ) ) {
1337                 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1338         } else if ( *method_type == 'u' || *method_type == 'd' ) {
1339                 fetch = 1; // MUST go to the db for the object for update and delete
1340         }
1341
1342         osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
1343
1344         if (!pcrud) {
1345                 // No permacrud for this method type on this class
1346
1347                 growing_buffer* msg = buffer_init(128);
1348                 buffer_fadd(
1349                         msg,
1350                         "%s: %s on class %s has no permacrud IDL entry",
1351                         MODULENAME,
1352                         osrfHashGet(method_metadata, "methodtype"),
1353                         osrfHashGet(class, "classname")
1354                 );
1355
1356                 char* m = buffer_release(msg);
1357                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1358                                 "osrfMethodException", ctx->request, m );
1359
1360                 free(m);
1361
1362                 return 0;
1363         }
1364
1365         jsonObject* user = verifyUserPCRUD( ctx );
1366         if (!user)
1367                 return 0;
1368
1369         int userid = atoi( oilsFMGetString( user, "id" ) );
1370         jsonObjectFree(user);
1371
1372         osrfStringArray* permission = osrfHashGet(pcrud, "permission");
1373         osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
1374         osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
1375
1376         osrfStringArray* context_org_array = osrfNewStringArray(1);
1377
1378         int err = 0;
1379         char* pkey_value = NULL;
1380         if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1381                 osrfLogDebug( OSRF_LOG_MARK,
1382                                 "global-level permissions required, fetching top of the org tree" );
1383
1384                 // check for perm at top of org tree
1385                 char* org_tree_root_id = org_tree_root( ctx );
1386                 if( org_tree_root_id ) {
1387                         osrfStringArrayAdd( context_org_array, org_tree_root_id );
1388                         osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1389                 } else  {
1390                         osrfStringArrayFree( context_org_array );
1391                         return 0;
1392                 }
1393
1394         } else {
1395             osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1396                                 "fetching context org ids" );
1397             const char* pkey = osrfHashGet(class, "primarykey");
1398                 jsonObject *param = NULL;
1399
1400                 if (obj->classname) {
1401                         pkey_value = oilsFMGetString( obj, pkey );
1402                         if (!fetch)
1403                                 param = jsonObjectClone(obj);
1404                         osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1405                                         pkey_value );
1406                 } else {
1407                         pkey_value = jsonObjectToSimpleString( obj );
1408                         fetch = 1;
1409                         osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1410                                         "of %s and retrieving from the database", pkey_value );
1411                 }
1412
1413                 if (fetch) {
1414                         jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1415                         jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1416                         jsonObjectFree(_tmp_params);
1417
1418                         param = jsonObjectExtractIndex(_list, 0);
1419                         jsonObjectFree(_list);
1420                 }
1421
1422                 if (!param) {
1423                         osrfLogDebug( OSRF_LOG_MARK,
1424                                         "Object not found in the database with primary key %s of %s",
1425                                         pkey, pkey_value );
1426
1427                         growing_buffer* msg = buffer_init(128);
1428                         buffer_fadd(
1429                                 msg,
1430                                 "%s: no object found with primary key %s of %s",
1431                                 MODULENAME,
1432                                 pkey,
1433                                 pkey_value
1434                         );
1435
1436                         char* m = buffer_release(msg);
1437                         osrfAppSessionStatus(
1438                                 ctx->session,
1439                                 OSRF_STATUS_INTERNALSERVERERROR,
1440                                 "osrfMethodException",
1441                                 ctx->request,
1442                                 m
1443                         );
1444
1445                         free(m);
1446                         if (pkey_value) free(pkey_value);
1447
1448                         return 0;
1449                 }
1450
1451                 if (local_context->size > 0) {
1452                         osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1453                                         local_context->size);
1454                         int i = 0;
1455                         const char* lcontext = NULL;
1456                         while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1457                                 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1458                                 osrfLogDebug(
1459                                         OSRF_LOG_MARK,
1460                                         "adding class-local field %s (value: %s) to the context org list",
1461                                         lcontext,
1462                                         osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1463                                 );
1464                         }
1465                 }
1466
1467                 if (foreign_context) {
1468                         unsigned long class_count = osrfHashGetCount( foreign_context );
1469                         osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count);
1470
1471                         if (class_count > 0) {
1472
1473                                 osrfHash* fcontext = NULL;
1474                                 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1475                                 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1476                                         const char* class_name = osrfHashIteratorKey( class_itr );
1477                                         osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1478
1479                                         osrfLogDebug(
1480                                                 OSRF_LOG_MARK,
1481                                                 "%d foreign context fields(s) specified for class %s",
1482                                                 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1483                                                 class_name
1484                                         );
1485
1486                                         char* foreign_pkey = osrfHashGet(fcontext, "field");
1487                                         char* foreign_pkey_value =
1488                                                         oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1489
1490                                         jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1491
1492                                         jsonObject* _list = doFieldmapperSearch(
1493                                                 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1494
1495                                         jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1496                                         jsonObjectFree(_tmp_params);
1497                                         jsonObjectFree(_list);
1498
1499                                         osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1500
1501                                         if (_fparam && jump_list) {
1502                                                 const char* flink = NULL;
1503                                                 int k = 0;
1504                                                 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1505                                                         free(foreign_pkey_value);
1506
1507                                                         osrfHash* foreign_link_hash =
1508                                                                         oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1509
1510                                                         foreign_pkey_value = oilsFMGetString(_fparam, flink);
1511                                                         foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1512
1513                                                         _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1514
1515                                                         _list = doFieldmapperSearch(
1516                                                                 ctx,
1517                                                                 osrfHashGet( oilsIDL(),
1518                                                                                 osrfHashGet( foreign_link_hash, "class" ) ),
1519                                                                 _tmp_params,
1520                                                                 NULL,
1521                                                                 &err
1522                                                         );
1523
1524                                                         _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1525                                                         jsonObjectFree(_tmp_params);
1526                                                         jsonObjectFree(_list);
1527                                                 }
1528                                         }
1529
1530                                         if (!_fparam) {
1531
1532                                                 growing_buffer* msg = buffer_init(128);
1533                                                 buffer_fadd(
1534                                                         msg,
1535                                                         "%s: no object found with primary key %s of %s",
1536                                                         MODULENAME,
1537                                                         foreign_pkey,
1538                                                         foreign_pkey_value
1539                                                 );
1540
1541                                                 char* m = buffer_release(msg);
1542                                                 osrfAppSessionStatus(
1543                                                         ctx->session,
1544                                                         OSRF_STATUS_INTERNALSERVERERROR,
1545                                                         "osrfMethodException",
1546                                                         ctx->request,
1547                                                         m
1548                                                 );
1549
1550                                                 free(m);
1551                                                 osrfHashIteratorFree(class_itr);
1552                                                 free(foreign_pkey_value);
1553                                                 jsonObjectFree(param);
1554
1555                                                 return 0;
1556                                         }
1557
1558                                         free(foreign_pkey_value);
1559
1560                                         int j = 0;
1561                                         const char* foreign_field = NULL;
1562                                         while ( (foreign_field = osrfStringArrayGetString(
1563                                                          osrfHashGet(fcontext,"context" ), j++)) ) {
1564                                                 osrfStringArrayAdd( context_org_array,
1565                                                                 oilsFMGetString( _fparam, foreign_field ) );
1566                                                 osrfLogDebug(
1567                                                         OSRF_LOG_MARK,
1568                                                         "adding foreign class %s field %s (value: %s) to the context org list",
1569                                                         class_name,
1570                                                         foreign_field,
1571                                                         osrfStringArrayGetString(
1572                                                                         context_org_array, context_org_array->size - 1)
1573                                                 );
1574                                         }
1575
1576                                         jsonObjectFree(_fparam);
1577                                 }
1578
1579                                 osrfHashIteratorFree( class_itr );
1580                         }
1581                 }
1582
1583                 jsonObjectFree(param);
1584         }
1585
1586         const char* context_org = NULL;
1587         const char* perm = NULL;
1588         int OK = 0;
1589
1590         if (permission->size == 0) {
1591             osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1592                 OK = 1;
1593         }
1594
1595         int i = 0;
1596         while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1597                 int j = 0;
1598                 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1599                         dbi_result result;
1600
1601                         if (pkey_value) {
1602                                 osrfLogDebug(
1603                                         OSRF_LOG_MARK,
1604                                         "Checking object permission [%s] for user %d "
1605                                                         "on object %s (class %s) at org %d",
1606                                         perm,
1607                                         userid,
1608                                         pkey_value,
1609                                         osrfHashGet(class, "classname"),
1610                                         atoi(context_org)
1611                                 );
1612
1613                                 result = dbi_conn_queryf(
1614                                         writehandle,
1615                                         "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1616                                         userid,
1617                                         perm,
1618                                         osrfHashGet(class, "classname"),
1619                                         pkey_value,
1620                                         atoi(context_org)
1621                                 );
1622
1623                                 if (result) {
1624                                         osrfLogDebug(
1625                                                 OSRF_LOG_MARK,
1626                                                 "Received a result for object permission [%s] "
1627                                                                 "for user %d on object %s (class %s) at org %d",
1628                                                 perm,
1629                                                 userid,
1630                                                 pkey_value,
1631                                                 osrfHashGet(class, "classname"),
1632                                                 atoi(context_org)
1633                                         );
1634
1635                                         if (dbi_result_first_row(result)) {
1636                                                 jsonObject* return_val = oilsMakeJSONFromResult( result );
1637                                                 const char* has_perm = jsonObjectGetString(
1638                                                                 jsonObjectGetKeyConst(return_val, "has_perm") );
1639
1640                                                 osrfLogDebug(
1641                                                         OSRF_LOG_MARK,
1642                                                         "Status of object permission [%s] for user %d "
1643                                                                         "on object %s (class %s) at org %d is %s",
1644                                                         perm,
1645                                                         userid,
1646                                                         pkey_value,
1647                                                         osrfHashGet(class, "classname"),
1648                                                         atoi(context_org),
1649                                                         has_perm
1650                                                 );
1651
1652                                                 if ( *has_perm == 't' ) OK = 1;
1653                                                 jsonObjectFree(return_val);
1654                                         }
1655
1656                                         dbi_result_free(result);
1657                                         if (OK)
1658                                                 break;
1659                                 }
1660                         }
1661
1662                         osrfLogDebug( OSRF_LOG_MARK,
1663                                         "Checking non-object permission [%s] for user %d at org %d",
1664                                         perm, userid, atoi(context_org) );
1665                         result = dbi_conn_queryf(
1666                                 writehandle,
1667                                 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1668                                 userid,
1669                                 perm,
1670                                 atoi(context_org)
1671                         );
1672
1673                         if (result) {
1674                                 osrfLogDebug( OSRF_LOG_MARK,
1675                                                 "Received a result for permission [%s] for user %d at org %d",
1676                                                 perm, userid, atoi(context_org) );
1677                                 if ( dbi_result_first_row(result) ) {
1678                                         jsonObject* return_val = oilsMakeJSONFromResult( result );
1679                                         const char* has_perm = jsonObjectGetString(
1680                                                         jsonObjectGetKeyConst(return_val, "has_perm") );
1681                                         osrfLogDebug( OSRF_LOG_MARK,
1682                                                         "Status of permission [%s] for user %d at org %d is [%s]",
1683                                                         perm, userid, atoi(context_org), has_perm );
1684                                         if ( *has_perm == 't' )
1685                                                 OK = 1;
1686                                         jsonObjectFree(return_val);
1687                                 }
1688
1689                                 dbi_result_free(result);
1690                                 if (OK) break;
1691                         }
1692
1693                 }
1694                 if (OK)
1695                         break;
1696         }
1697
1698         if (pkey_value) free(pkey_value);
1699         osrfStringArrayFree(context_org_array);
1700
1701         return OK;
1702 }
1703
1704 /**
1705         @brief Look up the root of the org_unit tree.
1706         @param ctx Pointer to the method context.
1707         @return The id of the root org unit, as a character string.
1708
1709         Query actor.org_unit where parent_ou is null, and return the id as a string.
1710
1711         This function assumes that there is only one root org unit, i.e. that we
1712         have a single tree, not a forest.
1713
1714         The calling code is responsible for freeing the returned string.
1715 */
1716 static char* org_tree_root( osrfMethodContext* ctx ) {
1717
1718         static char cached_root_id[ 32 ] = "";  // extravagantly large buffer
1719         static time_t last_lookup_time = 0;
1720         time_t current_time = time( NULL );
1721
1722         if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1723                 // We successfully looked this up less than an hour ago.
1724                 // It's not likely to have changed since then.
1725                 return strdup( cached_root_id );
1726         }
1727         last_lookup_time = current_time;
1728
1729         int err = 0;
1730         jsonObject* where_clause = single_hash( "parent_ou", NULL );
1731         jsonObject* result = doFieldmapperSearch(
1732                 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1733         jsonObjectFree( where_clause );
1734
1735         jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1736
1737         if (! tree_top) {
1738                 jsonObjectFree( result );
1739
1740                 growing_buffer* msg = buffer_init(128);
1741                 OSRF_BUFFER_ADD( msg, MODULENAME );
1742                 OSRF_BUFFER_ADD( msg,
1743                                 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1744
1745                 char* m = buffer_release(msg);
1746                 osrfAppSessionStatus( ctx->session,
1747                                 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1748                 free(m);
1749
1750                 cached_root_id[ 0 ] = '\0';
1751                 return NULL;
1752         }
1753
1754         char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1755         osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1756
1757         jsonObjectFree( result );
1758
1759         strcpy( cached_root_id, root_org_unit_id );
1760         return root_org_unit_id;
1761 }
1762
1763 /**
1764         @brief Create a JSON_HASH with a single key/value pair.
1765         @param key The key of the key/value pair.
1766         @param value the value of the key/value pair.
1767         @return Pointer to a newly created jsonObject of type JSON_HASH.
1768
1769         The value of the key/value is either a string or (if @a value is NULL) a null.
1770 */
1771 static jsonObject* single_hash( const char* key, const char* value ) {
1772         // Sanity check
1773         if( ! key ) key = "";
1774
1775         jsonObject* hash = jsonNewObjectType( JSON_HASH );
1776         jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1777         return hash;
1778 }
1779 #endif
1780
1781
1782 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1783
1784         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1785 #ifdef PCRUD
1786         jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1787         jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1788 #else
1789         jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1790         jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1791 #endif
1792
1793         if (!verifyObjectClass(ctx, target)) {
1794                 *err = -1;
1795                 return jsonNULL;
1796         }
1797
1798         osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1799
1800         const char* trans_id = getXactId( ctx );
1801         if ( !trans_id ) {
1802                 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1803
1804                 osrfAppSessionStatus(
1805                         ctx->session,
1806                         OSRF_STATUS_BADREQUEST,
1807                         "osrfMethodException",
1808                         ctx->request,
1809                         "No active transaction -- required for CREATE"
1810                 );
1811                 *err = -1;
1812                 return jsonNULL;
1813         }
1814
1815         // The following test is harmless but redundant.  If a class is
1816         // readonly, we don't register a create method for it.
1817         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1818                 osrfAppSessionStatus(
1819                         ctx->session,
1820                         OSRF_STATUS_BADREQUEST,
1821                         "osrfMethodException",
1822                         ctx->request,
1823                         "Cannot INSERT readonly class"
1824                 );
1825                 *err = -1;
1826                 return jsonNULL;
1827         }
1828
1829         // Set the last_xact_id
1830         int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1831         if (index > -1) {
1832                 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1833                         trans_id, target->classname, index );
1834                 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1835         }
1836
1837         osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1838
1839         dbhandle = writehandle;
1840
1841         osrfHash* fields = osrfHashGet(meta, "fields");
1842         char* pkey = osrfHashGet(meta, "primarykey");
1843         char* seq = osrfHashGet(meta, "sequence");
1844
1845         growing_buffer* table_buf = buffer_init(128);
1846         growing_buffer* col_buf = buffer_init(128);
1847         growing_buffer* val_buf = buffer_init(128);
1848
1849         OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1850         OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1851         OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1852         buffer_add(val_buf,"VALUES (");
1853
1854
1855         int first = 1;
1856         osrfHash* field = NULL;
1857         osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1858         while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1859
1860                 const char* field_name = osrfHashIteratorKey( field_itr );
1861
1862                 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1863                         continue;
1864
1865                 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1866
1867                 char* value;
1868                 if (field_object && field_object->classname) {
1869                         value = oilsFMGetString(
1870                                 field_object,
1871                                 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1872                         );
1873                 } else if( field_object && JSON_BOOL == field_object->type ) {
1874                         if( jsonBoolIsTrue( field_object ) )
1875                                 value = strdup( "t" );
1876                         else
1877                                 value = strdup( "f" );
1878                 } else {
1879                         value = jsonObjectToSimpleString( field_object );
1880                 }
1881
1882                 if (first) {
1883                         first = 0;
1884                 } else {
1885                         OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1886                         OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1887                 }
1888
1889                 buffer_add(col_buf, field_name);
1890
1891                 if (!field_object || field_object->type == JSON_NULL) {
1892                         buffer_add( val_buf, "DEFAULT" );
1893
1894                 } else if ( !strcmp(get_primitive( field ), "number") ) {
1895                         const char* numtype = get_datatype( field );
1896                         if ( !strcmp( numtype, "INT8") ) {
1897                                 buffer_fadd( val_buf, "%lld", atoll(value) );
1898
1899                         } else if ( !strcmp( numtype, "INT") ) {
1900                                 buffer_fadd( val_buf, "%d", atoi(value) );
1901
1902                         } else if ( !strcmp( numtype, "NUMERIC") ) {
1903                                 buffer_fadd( val_buf, "%f", atof(value) );
1904                         }
1905                 } else {
1906                         if ( dbi_conn_quote_string(writehandle, &value) ) {
1907                                 OSRF_BUFFER_ADD( val_buf, value );
1908
1909                         } else {
1910                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1911                                 osrfAppSessionStatus(
1912                                         ctx->session,
1913                                         OSRF_STATUS_INTERNALSERVERERROR,
1914                                         "osrfMethodException",
1915                                         ctx->request,
1916                                         "Error quoting string -- please see the error log for more details"
1917                                 );
1918                                 free(value);
1919                                 buffer_free(table_buf);
1920                                 buffer_free(col_buf);
1921                                 buffer_free(val_buf);
1922                                 *err = -1;
1923                                 return jsonNULL;
1924                         }
1925                 }
1926
1927                 free(value);
1928
1929         }
1930
1931         osrfHashIteratorFree( field_itr );
1932
1933         OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1934         OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1935
1936         char* table_str = buffer_release(table_buf);
1937         char* col_str   = buffer_release(col_buf);
1938         char* val_str   = buffer_release(val_buf);
1939         growing_buffer* sql = buffer_init(128);
1940         buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1941         free(table_str);
1942         free(col_str);
1943         free(val_str);
1944
1945         char* query = buffer_release(sql);
1946
1947         osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1948
1949
1950         dbi_result result = dbi_conn_query(writehandle, query);
1951
1952         jsonObject* obj = NULL;
1953
1954         if (!result) {
1955                 obj = jsonNewObject(NULL);
1956                 osrfLogError(
1957                         OSRF_LOG_MARK,
1958                         "%s ERROR inserting %s object using query [%s]",
1959                         MODULENAME,
1960                         osrfHashGet(meta, "fieldmapper"),
1961                         query
1962                 );
1963                 osrfAppSessionStatus(
1964                         ctx->session,
1965                         OSRF_STATUS_INTERNALSERVERERROR,
1966                         "osrfMethodException",
1967                         ctx->request,
1968                         "INSERT error -- please see the error log for more details"
1969                 );
1970                 *err = -1;
1971         } else {
1972
1973                 char* id = oilsFMGetString(target, pkey);
1974                 if (!id) {
1975                         unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1976                         growing_buffer* _id = buffer_init(10);
1977                         buffer_fadd(_id, "%lld", new_id);
1978                         id = buffer_release(_id);
1979                 }
1980
1981                 // Find quietness specification, if present
1982                 const char* quiet_str = NULL;
1983                 if ( options ) {
1984                         const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1985                         if( quiet_obj )
1986                                 quiet_str = jsonObjectGetString( quiet_obj );
1987                 }
1988
1989                 if( str_is_true( quiet_str ) ) {  // if quietness is specified
1990                         obj = jsonNewObject(id);
1991                 }
1992                 else {
1993
1994                         jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1995                         jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1996
1997                         jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1998
1999                         jsonObjectFree( where_clause );
2000
2001                         if(*err) {
2002                                 obj = jsonNULL;
2003                         } else {
2004                                 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
2005                         }
2006
2007                         jsonObjectFree( list );
2008                 }
2009
2010                 free(id);
2011         }
2012
2013         free(query);
2014
2015         return obj;
2016
2017 }
2018
2019 /**
2020         @brief Implement the retrieve method.
2021         @param ctx Pointer to the method context.
2022         @param err Pointer through which to return an error code.
2023         @return If successful, a pointer to the result to be returned to the client;
2024         otherwise NULL.
2025
2026         From the method's class, fetch a row with a specified value in the primary key.  This
2027         method relies on the database design convention that a primary key consists of a single
2028         column.
2029
2030         Method parameters:
2031         - authkey (PCRUD only)
2032         - value of the primary key for the desired row, for building the WHERE clause
2033         - a JSON_HASH containing any other SQL clauses: select, join, etc.
2034
2035         Return to client: One row from the query.
2036 */
2037 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
2038
2039         int id_pos = 0;
2040         int order_pos = 1;
2041
2042 #ifdef PCRUD
2043         id_pos = 1;
2044         order_pos = 2;
2045 #endif
2046
2047         // Get the class metadata
2048         osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2049
2050         // Get the value of the primary key, from a method parameter
2051         const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos);
2052
2053         osrfLogDebug(
2054                 OSRF_LOG_MARK,
2055                 "%s retrieving %s object with primary key value of %s",
2056                 MODULENAME,
2057                 osrfHashGet( class_def, "fieldmapper" ),
2058                 jsonObjectGetString( id_obj )
2059         );
2060
2061         // Build a WHERE clause based on the key value
2062         jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2063         jsonObjectSetKey(
2064                 where_clause,
2065                 osrfHashGet( class_def, "primarykey" ),  // name of key column
2066                 jsonObjectClone( id_obj )                // value of key column
2067         );
2068
2069         jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
2070
2071         // Do the query
2072         jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
2073
2074         jsonObjectFree( where_clause );
2075         if(*err)
2076                 return NULL;
2077
2078         jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2079         jsonObjectFree( list );
2080
2081 #ifdef PCRUD
2082         if(!verifyObjectPCRUD(ctx, obj)) {
2083                 jsonObjectFree(obj);
2084                 *err = -1;
2085
2086                 growing_buffer* msg = buffer_init(128);
2087                 OSRF_BUFFER_ADD( msg, MODULENAME );
2088                 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2089
2090                 char* m = buffer_release(msg);
2091                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2092                                 ctx->request, m );
2093
2094                 free(m);
2095
2096                 return jsonNULL;
2097         }
2098 #endif
2099
2100         return obj;
2101 }
2102
2103 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
2104         growing_buffer* val_buf = buffer_init(32);
2105         const char* numtype = get_datatype( field );
2106
2107         if ( !strncmp( numtype, "INT", 3 ) ) {
2108                 if (value->type == JSON_NUMBER)
2109                         //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2110                         buffer_fadd( val_buf, jsonObjectGetString( value ) );
2111                 else {
2112                         buffer_fadd( val_buf, jsonObjectGetString( value ) );
2113                 }
2114
2115         } else if ( !strcmp( numtype, "NUMERIC" ) ) {
2116                 if (value->type == JSON_NUMBER)
2117                         buffer_fadd( val_buf, jsonObjectGetString( value ) );
2118                 else {
2119                         buffer_fadd( val_buf, jsonObjectGetString( value ) );
2120                 }
2121
2122         } else {
2123                 // Presumably this was really intended ot be a string, so quote it
2124                 char* str = jsonObjectToSimpleString( value );
2125                 if ( dbi_conn_quote_string(dbhandle, &str) ) {
2126                         OSRF_BUFFER_ADD( val_buf, str );
2127                         free(str);
2128                 } else {
2129                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
2130                         free(str);
2131                         buffer_free(val_buf);
2132                         return NULL;
2133                 }
2134         }
2135
2136         return buffer_release(val_buf);
2137 }
2138
2139 static char* searchINPredicate (const char* class_alias, osrfHash* field,
2140                 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2141         growing_buffer* sql_buf = buffer_init(32);
2142
2143         buffer_fadd(
2144                 sql_buf,
2145                 "\"%s\".%s ",
2146                 class_alias,
2147                 osrfHashGet(field, "name")
2148         );
2149
2150         if (!op) {
2151                 buffer_add(sql_buf, "IN (");
2152         } else if (!(strcasecmp(op,"not in"))) {
2153                 buffer_add(sql_buf, "NOT IN (");
2154         } else {
2155                 buffer_add(sql_buf, "IN (");
2156         }
2157
2158         if (node->type == JSON_HASH) {
2159                 // subquery predicate
2160                 char* subpred = buildQuery( ctx, node, SUBSELECT );
2161                 if( ! subpred ) {
2162                         buffer_free( sql_buf );
2163                         return NULL;
2164                 }
2165
2166                 buffer_add(sql_buf, subpred);
2167                 free(subpred);
2168
2169         } else if (node->type == JSON_ARRAY) {
2170                 // literal value list
2171                 int in_item_index = 0;
2172                 int in_item_first = 1;
2173                 const jsonObject* in_item;
2174                 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
2175
2176                         if (in_item_first)
2177                                 in_item_first = 0;
2178                         else
2179                                 buffer_add(sql_buf, ", ");
2180
2181                         // Sanity check
2182                         if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2183                                 osrfLogError( OSRF_LOG_MARK,
2184                                                 "%s: Expected string or number within IN list; found %s",
2185                                                 MODULENAME, json_type( in_item->type ) );
2186                                 buffer_free(sql_buf);
2187                                 return NULL;
2188                         }
2189
2190                         // Append the literal value -- quoted if not a number
2191                         if ( JSON_NUMBER == in_item->type ) {
2192                                 char* val = jsonNumberToDBString( field, in_item );
2193                                 OSRF_BUFFER_ADD( sql_buf, val );
2194                                 free(val);
2195
2196                         } else if ( !strcmp( get_primitive( field ), "number") ) {
2197                                 char* val = jsonNumberToDBString( field, in_item );
2198                                 OSRF_BUFFER_ADD( sql_buf, val );
2199                                 free(val);
2200
2201                         } else {
2202                                 char* key_string = jsonObjectToSimpleString(in_item);
2203                                 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
2204                                         OSRF_BUFFER_ADD( sql_buf, key_string );
2205                                         free(key_string);
2206                                 } else {
2207                                         osrfLogError(OSRF_LOG_MARK,
2208                                                         "%s: Error quoting key string [%s]", MODULENAME, key_string);
2209                                         free(key_string);
2210                                         buffer_free(sql_buf);
2211                                         return NULL;
2212                                 }
2213                         }
2214                 }
2215
2216                 if( in_item_first ) {
2217                         osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
2218                         buffer_free( sql_buf );
2219                         return NULL;
2220                 }
2221         } else {
2222                 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2223                         MODULENAME, json_type( node->type ) );
2224                 buffer_free(sql_buf);
2225                 return NULL;
2226         }
2227
2228         OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2229
2230         return buffer_release(sql_buf);
2231 }
2232
2233 // Receive a JSON_ARRAY representing a function call.  The first
2234 // entry in the array is the function name.  The rest are parameters.
2235 static char* searchValueTransform( const jsonObject* array ) {
2236
2237         if( array->size < 1 ) {
2238                 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
2239                 return NULL;
2240         }
2241
2242         // Get the function name
2243         jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2244         if( func_item->type != JSON_STRING ) {
2245                 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2246                         MODULENAME, json_type( func_item->type ) );
2247                 return NULL;
2248         }
2249
2250         growing_buffer* sql_buf = buffer_init(32);
2251
2252         OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2253         OSRF_BUFFER_ADD( sql_buf, "( " );
2254
2255         // Get the parameters
2256         int func_item_index = 1;   // We already grabbed the zeroth entry
2257         while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2258
2259                 // Add a separator comma, if we need one
2260                 if( func_item_index > 2 )
2261                         buffer_add( sql_buf, ", " );
2262
2263                 // Add the current parameter
2264                 if (func_item->type == JSON_NULL) {
2265                         buffer_add( sql_buf, "NULL" );
2266                 } else {
2267                         char* val = jsonObjectToSimpleString(func_item);
2268                         if ( dbi_conn_quote_string(dbhandle, &val) ) {
2269                                 OSRF_BUFFER_ADD( sql_buf, val );
2270                                 free(val);
2271                         } else {
2272                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
2273                                 buffer_free(sql_buf);
2274                                 free(val);
2275                                 return NULL;
2276                         }
2277                 }
2278         }
2279
2280         buffer_add( sql_buf, " )" );
2281
2282         return buffer_release(sql_buf);
2283 }
2284
2285 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
2286                 const jsonObject* node, const char* op) {
2287
2288         if( ! is_good_operator( op ) ) {
2289                 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2290                 return NULL;
2291         }
2292
2293         char* val = searchValueTransform(node);
2294         if( !val )
2295                 return NULL;
2296
2297         growing_buffer* sql_buf = buffer_init(32);
2298         buffer_fadd(
2299                 sql_buf,
2300                 "\"%s\".%s %s %s",
2301                 class_alias,
2302                 osrfHashGet(field, "name"),
2303                 op,
2304                 val
2305         );
2306
2307         free(val);
2308
2309         return buffer_release(sql_buf);
2310 }
2311
2312 // class_alias is a class name or other table alias
2313 // field is a field definition as stored in the IDL
2314 // node comes from the method parameter, and may represent an entry in the SELECT list
2315 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
2316         growing_buffer* sql_buf = buffer_init(32);
2317
2318         const char* field_transform = jsonObjectGetString(
2319                 jsonObjectGetKeyConst( node, "transform" ) );
2320         const char* transform_subcolumn = jsonObjectGetString(
2321                 jsonObjectGetKeyConst( node, "result_field" ) );
2322
2323         if(transform_subcolumn) {
2324                 if( ! is_identifier( transform_subcolumn ) ) {
2325                         osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2326                                         MODULENAME, transform_subcolumn );
2327                         buffer_free( sql_buf );
2328                         return NULL;
2329                 }
2330                 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' );    // enclose transform in parentheses
2331         }
2332
2333         if (field_transform) {
2334
2335                 if( ! is_identifier( field_transform ) ) {
2336                         osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2337                                         MODULENAME, field_transform );
2338                         buffer_free( sql_buf );
2339                         return NULL;
2340                 }
2341
2342                 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2343                         field_transform, class_alias, osrfHashGet(field, "name"));
2344                 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2345
2346                 if (array) {
2347                         if( array->type != JSON_ARRAY ) {
2348                                 osrfLogError( OSRF_LOG_MARK,
2349                                         "%s: Expected JSON_ARRAY for function params; found %s",
2350                                         MODULENAME, json_type( array->type ) );
2351                                 buffer_free( sql_buf );
2352                                 return NULL;
2353                         }
2354                         int func_item_index = 0;
2355                         jsonObject* func_item;
2356                         while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2357
2358                                 char* val = jsonObjectToSimpleString(func_item);
2359
2360                                 if ( !val ) {
2361                                         buffer_add( sql_buf, ",NULL" );
2362                                 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
2363                                         OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2364                                         OSRF_BUFFER_ADD( sql_buf, val );
2365                                 } else {
2366                                         osrfLogError( OSRF_LOG_MARK,
2367                                                         "%s: Error quoting key string [%s]", MODULENAME, val);
2368                                         free(val);
2369                                         buffer_free(sql_buf);
2370                                         return NULL;
2371                                 }
2372                                 free(val);
2373                         }
2374                 }
2375
2376                 buffer_add( sql_buf, " )" );
2377
2378         } else {
2379                 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
2380         }
2381
2382         if (transform_subcolumn)
2383                 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2384
2385         return buffer_release(sql_buf);
2386 }
2387
2388 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2389                 const jsonObject* node, const char* op ) {
2390
2391         if( ! is_good_operator( op ) ) {
2392                 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2393                 return NULL;
2394         }
2395
2396         char* field_transform = searchFieldTransform( class_info->alias, field, node );
2397         if( ! field_transform )
2398                 return NULL;
2399         char* value = NULL;
2400         int extra_parens = 0;   // boolean
2401
2402         const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2403         if ( ! value_obj ) {
2404                 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2405                 if( !value ) {
2406                         osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2407                                 MODULENAME );
2408                         free(field_transform);
2409                         return NULL;
2410                 }
2411                 extra_parens = 1;
2412         } else if ( value_obj->type == JSON_ARRAY ) {
2413                 value = searchValueTransform( value_obj );
2414                 if( !value ) {
2415                         osrfLogError(OSRF_LOG_MARK,
2416                                 "%s: Error building value transform for field transform", MODULENAME);
2417                         free( field_transform );
2418                         return NULL;
2419                 }
2420         } else if ( value_obj->type == JSON_HASH ) {
2421                 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2422                 if( !value ) {
2423                         osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2424                                 MODULENAME);
2425                         free(field_transform);
2426                         return NULL;
2427                 }
2428                 extra_parens = 1;
2429         } else if ( value_obj->type == JSON_NUMBER ) {
2430                 value = jsonNumberToDBString( field, value_obj );
2431         } else if ( value_obj->type == JSON_NULL ) {
2432                 osrfLogError( OSRF_LOG_MARK,
2433                         "%s: Error building predicate for field transform: null value", MODULENAME );
2434                 free(field_transform);
2435                 return NULL;
2436         } else if ( value_obj->type == JSON_BOOL ) {
2437                 osrfLogError( OSRF_LOG_MARK,
2438                         "%s: Error building predicate for field transform: boolean value", MODULENAME );
2439                 free(field_transform);
2440                 return NULL;
2441         } else {
2442                 if ( !strcmp( get_primitive( field ), "number") ) {
2443                         value = jsonNumberToDBString( field, value_obj );
2444                 } else {
2445                         value = jsonObjectToSimpleString( value_obj );
2446                         if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2447                                 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2448                                         MODULENAME, value);
2449                                 free(value);
2450                                 free(field_transform);
2451                                 return NULL;
2452                         }
2453                 }
2454         }
2455
2456         const char* left_parens  = "";
2457         const char* right_parens = "";
2458
2459         if( extra_parens ) {
2460                 left_parens  = "(";
2461                 right_parens = ")";
2462         }
2463
2464         growing_buffer* sql_buf = buffer_init(32);
2465
2466         buffer_fadd(
2467                 sql_buf,
2468                 "%s%s %s %s %s %s%s",
2469                 left_parens,
2470                 field_transform,
2471                 op,
2472                 left_parens,
2473                 value,
2474                 right_parens,
2475                 right_parens
2476         );
2477
2478         free(value);
2479         free(field_transform);
2480
2481         return buffer_release(sql_buf);
2482 }
2483
2484 static char* searchSimplePredicate (const char* op, const char* class_alias,
2485                 osrfHash* field, const jsonObject* node) {
2486
2487         if( ! is_good_operator( op ) ) {
2488                 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2489                 return NULL;
2490         }
2491
2492         char* val = NULL;
2493
2494         // Get the value to which we are comparing the specified column
2495         if (node->type != JSON_NULL) {
2496                 if ( node->type == JSON_NUMBER ) {
2497                         val = jsonNumberToDBString( field, node );
2498                 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2499                         val = jsonNumberToDBString( field, node );
2500                 } else {
2501                         val = jsonObjectToSimpleString(node);
2502                 }
2503         }
2504
2505         if( val ) {
2506                 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2507                         // Value is not numeric; enclose it in quotes
2508                         if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2509                                 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2510                                         MODULENAME, val );
2511                                 free( val );
2512                                 return NULL;
2513                         }
2514                 }
2515         } else {
2516                 // Compare to a null value
2517                 val = strdup( "NULL" );
2518                 if (strcmp( op, "=" ))
2519                         op = "IS NOT";
2520                 else
2521                         op = "IS";
2522         }
2523
2524         growing_buffer* sql_buf = buffer_init(32);
2525         buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2526         char* pred = buffer_release( sql_buf );
2527
2528         free(val);
2529
2530         return pred;
2531 }
2532
2533 static char* searchBETWEENPredicate (const char* class_alias,
2534                 osrfHash* field, const jsonObject* node) {
2535
2536         const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2537         const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2538
2539         if( NULL == y_node ) {
2540                 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2541                 return NULL;
2542         }
2543         else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2544                 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2545                 return NULL;
2546         }
2547
2548         char* x_string;
2549         char* y_string;
2550
2551         if ( !strcmp( get_primitive( field ), "number") ) {
2552                 x_string = jsonNumberToDBString(field, x_node);
2553                 y_string = jsonNumberToDBString(field, y_node);
2554
2555         } else {
2556                 x_string = jsonObjectToSimpleString(x_node);
2557                 y_string = jsonObjectToSimpleString(y_node);
2558                 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2559                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2560                                         MODULENAME, x_string, y_string);
2561                         free(x_string);
2562                         free(y_string);
2563                         return NULL;
2564                 }
2565         }
2566
2567         growing_buffer* sql_buf = buffer_init(32);
2568         buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2569                         class_alias, osrfHashGet(field, "name"), x_string, y_string );
2570         free(x_string);
2571         free(y_string);
2572
2573         return buffer_release(sql_buf);
2574 }
2575
2576 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2577                                                            jsonObject* node, osrfMethodContext* ctx ) {
2578
2579         char* pred = NULL;
2580         if (node->type == JSON_ARRAY) { // equality IN search
2581                 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2582         } else if (node->type == JSON_HASH) { // other search
2583                 jsonIterator* pred_itr = jsonNewIterator( node );
2584                 if( !jsonIteratorHasNext( pred_itr ) ) {
2585                         osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2586                                         MODULENAME, osrfHashGet(field, "name") );
2587                 } else {
2588                         jsonObject* pred_node = jsonIteratorNext( pred_itr );
2589
2590                         // Verify that there are no additional predicates
2591                         if( jsonIteratorHasNext( pred_itr ) ) {
2592                                 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2593                                                 MODULENAME, osrfHashGet(field, "name") );
2594                         } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2595                                 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2596                         else if ( !(strcasecmp( pred_itr->key,"in" ))
2597                                         || !(strcasecmp( pred_itr->key,"not in" )) )
2598                                 pred = searchINPredicate(
2599                                         class_info->alias, field, pred_node, pred_itr->key, ctx );
2600                         else if ( pred_node->type == JSON_ARRAY )
2601                                 pred = searchFunctionPredicate(
2602                                         class_info->alias, field, pred_node, pred_itr->key );
2603                         else if ( pred_node->type == JSON_HASH )
2604                                 pred = searchFieldTransformPredicate(
2605                                         class_info, field, pred_node, pred_itr->key );
2606                         else
2607                                 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2608                 }
2609                 jsonIteratorFree(pred_itr);
2610
2611         } else if (node->type == JSON_NULL) { // IS NULL search
2612                 growing_buffer* _p = buffer_init(64);
2613                 buffer_fadd(
2614                         _p,
2615                         "\"%s\".%s IS NULL",
2616                         class_info->class_name,
2617                         osrfHashGet(field, "name")
2618                 );
2619                 pred = buffer_release(_p);
2620         } else { // equality search
2621                 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2622         }
2623
2624         return pred;
2625
2626 }
2627
2628
2629 /*
2630
2631 join : {
2632         acn : {
2633                 field : record,
2634                 fkey : id
2635                 type : left
2636                 filter_op : or
2637                 filter : { ... },
2638                 join : {
2639                         acp : {
2640                                 field : call_number,
2641                                 fkey : id,
2642                                 filter : { ... },
2643                         },
2644                 },
2645         },
2646         mrd : {
2647                 field : record,
2648                 type : inner
2649                 fkey : id,
2650                 filter : { ... },
2651         }
2652 }
2653
2654 */
2655
2656 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2657
2658         const jsonObject* working_hash;
2659         jsonObject* freeable_hash = NULL;
2660
2661         if (join_hash->type == JSON_HASH) {
2662                 working_hash = join_hash;
2663         } else if (join_hash->type == JSON_STRING) {
2664                 // turn it into a JSON_HASH by creating a wrapper
2665                 // around a copy of the original
2666                 const char* _tmp = jsonObjectGetString( join_hash );
2667                 freeable_hash = jsonNewObjectType(JSON_HASH);
2668                 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2669                 working_hash = freeable_hash;
2670         } else {
2671                 osrfLogError(
2672                         OSRF_LOG_MARK,
2673                         "%s: JOIN failed; expected JSON object type not found",
2674                         MODULENAME
2675                 );
2676                 return NULL;
2677         }
2678
2679         growing_buffer* join_buf = buffer_init(128);
2680         const char* leftclass = left_info->class_name;
2681
2682         jsonObject* snode = NULL;
2683         jsonIterator* search_itr = jsonNewIterator( working_hash );
2684
2685         while ( (snode = jsonIteratorNext( search_itr )) ) {
2686                 const char* right_alias = search_itr->key;
2687                 const char* class =
2688                                 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2689                 if( ! class )
2690                         class = right_alias;
2691
2692                 const ClassInfo* right_info = add_joined_class( right_alias, class );
2693                 if( !right_info ) {
2694                         osrfLogError(
2695                                 OSRF_LOG_MARK,
2696                                 "%s: JOIN failed.  Class \"%s\" not resolved in IDL",
2697                                 MODULENAME,
2698                                 search_itr->key
2699                         );
2700                         jsonIteratorFree( search_itr );
2701                         buffer_free( join_buf );
2702                         if( freeable_hash )
2703                                 jsonObjectFree( freeable_hash );
2704                         return NULL;
2705                 }
2706                 osrfHash* links    = right_info->links;
2707                 const char* table  = right_info->source_def;
2708
2709                 const char* fkey  = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2710                 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2711
2712                 if (field && !fkey) {
2713                         // Look up the corresponding join column in the IDL.
2714                         // The link must be defined in the child table,
2715                         // and point to the right parent table.
2716                         osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2717                         const char* reltype = NULL;
2718                         const char* other_class = NULL;
2719                         reltype = osrfHashGet( idl_link, "reltype" );
2720                         if( reltype && strcmp( reltype, "has_many" ) )
2721                                 other_class = osrfHashGet( idl_link, "class" );
2722                         if( other_class && !strcmp( other_class, leftclass ) )
2723                                 fkey = osrfHashGet( idl_link, "key" );
2724                         if (!fkey) {
2725                                 osrfLogError(
2726                                         OSRF_LOG_MARK,
2727                                         "%s: JOIN failed.  No link defined from %s.%s to %s",
2728                                         MODULENAME,
2729                                         class,
2730                                         field,
2731                                         leftclass
2732                                 );
2733                                 buffer_free(join_buf);
2734                                 if(freeable_hash)
2735                                         jsonObjectFree(freeable_hash);
2736                                 jsonIteratorFree(search_itr);
2737                                 return NULL;
2738                         }
2739
2740                 } else if (!field && fkey) {
2741                         // Look up the corresponding join column in the IDL.
2742                         // The link must be defined in the child table,
2743                         // and point to the right parent table.
2744                         osrfHash* left_links = left_info->links;
2745                         osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2746                         const char* reltype = NULL;
2747                         const char* other_class = NULL;
2748                         reltype = osrfHashGet( idl_link, "reltype" );
2749                         if( reltype && strcmp( reltype, "has_many" ) )
2750                                 other_class = osrfHashGet( idl_link, "class" );
2751                         if( other_class && !strcmp( other_class, class ) )
2752                                 field = osrfHashGet( idl_link, "key" );
2753                         if (!field) {
2754                                 osrfLogError(
2755                                         OSRF_LOG_MARK,
2756                                         "%s: JOIN failed.  No link defined from %s.%s to %s",
2757                                         MODULENAME,
2758                                         leftclass,
2759                                         fkey,
2760                                         class
2761                                 );
2762                                 buffer_free(join_buf);
2763                                 if(freeable_hash)
2764                                         jsonObjectFree(freeable_hash);
2765                                 jsonIteratorFree(search_itr);
2766                                 return NULL;
2767                         }
2768
2769                 } else if (!field && !fkey) {
2770                         osrfHash* left_links = left_info->links;
2771
2772                         // For each link defined for the left class:
2773                         // see if the link references the joined class
2774                         osrfHashIterator* itr = osrfNewHashIterator( left_links );
2775                         osrfHash* curr_link = NULL;
2776                         while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2777                                 const char* other_class = osrfHashGet( curr_link, "class" );
2778                                 if( other_class && !strcmp( other_class, class ) ) {
2779
2780                                         // In the IDL, the parent class doesn't always know then names of the child
2781                                         // columns that are pointing to it, so don't use that end of the link
2782                                         const char* reltype = osrfHashGet( curr_link, "reltype" );
2783                                         if( reltype && strcmp( reltype, "has_many" ) ) {
2784                                                 // Found a link between the classes
2785                                                 fkey = osrfHashIteratorKey( itr );
2786                                                 field = osrfHashGet( curr_link, "key" );
2787                                                 break;
2788                                         }
2789                                 }
2790                         }
2791                         osrfHashIteratorFree( itr );
2792
2793                         if (!field || !fkey) {
2794                                 // Do another such search, with the classes reversed
2795
2796                                 // For each link defined for the joined class:
2797                                 // see if the link references the left class
2798                                 osrfHashIterator* itr = osrfNewHashIterator( links );
2799                                 osrfHash* curr_link = NULL;
2800                                 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2801                                         const char* other_class = osrfHashGet( curr_link, "class" );
2802                                         if( other_class && !strcmp( other_class, leftclass ) ) {
2803
2804                                                 // In the IDL, the parent class doesn't know then names of the child
2805                                                 // columns that are pointing to it, so don't use that end of the link
2806                                                 const char* reltype = osrfHashGet( curr_link, "reltype" );
2807                                                 if( reltype && strcmp( reltype, "has_many" ) ) {
2808                                                         // Found a link between the classes
2809                                                         field = osrfHashIteratorKey( itr );
2810                                                         fkey = osrfHashGet( curr_link, "key" );
2811                                                         break;
2812                                                 }
2813                                         }
2814                                 }
2815                                 osrfHashIteratorFree( itr );
2816                         }
2817
2818                         if (!field || !fkey) {
2819                                 osrfLogError(
2820                                         OSRF_LOG_MARK,
2821                                         "%s: JOIN failed.  No link defined between %s and %s",
2822                                         MODULENAME,
2823                                         leftclass,
2824                                         class
2825                                 );
2826                                 buffer_free(join_buf);
2827                                 if(freeable_hash)
2828                                         jsonObjectFree(freeable_hash);
2829                                 jsonIteratorFree(search_itr);
2830                                 return NULL;
2831                         }
2832                 }
2833
2834                 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2835                 if (type) {
2836                         if ( !strcasecmp(type,"left") ) {
2837                                 buffer_add(join_buf, " LEFT JOIN");
2838                         } else if ( !strcasecmp(type,"right") ) {
2839                                 buffer_add(join_buf, " RIGHT JOIN");
2840                         } else if ( !strcasecmp(type,"full") ) {
2841                                 buffer_add(join_buf, " FULL JOIN");
2842                         } else {
2843                                 buffer_add(join_buf, " INNER JOIN");
2844                         }
2845                 } else {
2846                         buffer_add(join_buf, " INNER JOIN");
2847                 }
2848
2849                 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2850                                         table, right_alias, right_alias, field, left_info->alias, fkey);
2851
2852                 // Add any other join conditions as specified by "filter"
2853                 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2854                 if (filter) {
2855                         const char* filter_op = jsonObjectGetString(
2856                                 jsonObjectGetKeyConst( snode, "filter_op" ) );
2857                         if ( filter_op && !strcasecmp("or",filter_op) ) {
2858                                 buffer_add( join_buf, " OR " );
2859                         } else {
2860                                 buffer_add( join_buf, " AND " );
2861                         }
2862
2863                         char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2864                         if( jpred ) {
2865                                 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2866                                 OSRF_BUFFER_ADD( join_buf, jpred );
2867                                 free(jpred);
2868                         } else {
2869                                 osrfLogError(
2870                                         OSRF_LOG_MARK,
2871                                         "%s: JOIN failed.  Invalid conditional expression.",
2872                                         MODULENAME
2873                                 );
2874                                 jsonIteratorFree( search_itr );
2875                                 buffer_free( join_buf );
2876                                 if( freeable_hash )
2877                                         jsonObjectFree( freeable_hash );
2878                                 return NULL;
2879                         }
2880                 }
2881
2882                 buffer_add(join_buf, " ) ");
2883
2884                 // Recursively add a nested join, if one is present
2885                 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2886                 if (join_filter) {
2887                         char* jpred = searchJOIN( join_filter, right_info );
2888                         if( jpred ) {
2889                                 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2890                                 OSRF_BUFFER_ADD( join_buf, jpred );
2891                                 free(jpred);
2892                         } else {
2893                                 osrfLogError(  OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2894                                 jsonIteratorFree( search_itr );
2895                                 buffer_free( join_buf );
2896                                 if( freeable_hash )
2897                                         jsonObjectFree( freeable_hash );
2898                                 return NULL;
2899                         }
2900                 }
2901         }
2902
2903         if(freeable_hash)
2904                 jsonObjectFree(freeable_hash);
2905         jsonIteratorFree(search_itr);
2906
2907         return buffer_release(join_buf);
2908 }
2909
2910 /*
2911
2912 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2913 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2914 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2915
2916 Generate code to express a set of conditions, as for a WHERE clause.  Parameters:
2917
2918 search_hash is the JSON expression of the conditions.
2919 meta is the class definition from the IDL, for the relevant table.
2920 opjoin_type indicates whether multiple conditions, if present, should be
2921         connected by AND or OR.
2922 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2923         to pass it to other functions -- and all they do with it is to use the session
2924         and request members to send error messages back to the client.
2925
2926 */
2927
2928 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
2929                 int opjoin_type, osrfMethodContext* ctx ) {
2930
2931         osrfLogDebug(
2932                 OSRF_LOG_MARK,
2933                 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
2934                 "opjoin_type = %d, ctx addr = %p",
2935                 MODULENAME,
2936                 search_hash,
2937                 class_info->class_def,
2938                 opjoin_type,
2939                 ctx
2940         );
2941
2942         growing_buffer* sql_buf = buffer_init(128);
2943
2944         jsonObject* node = NULL;
2945
2946         int first = 1;
2947         if ( search_hash->type == JSON_ARRAY ) {
2948                 osrfLogDebug( OSRF_LOG_MARK,
2949                         "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME );
2950                 if( 0 == search_hash->size ) {
2951                         osrfLogError(
2952                                 OSRF_LOG_MARK,
2953                                 "%s: Invalid predicate structure: empty JSON array",
2954                                 MODULENAME
2955                         );
2956                         buffer_free( sql_buf );
2957                         return NULL;
2958                 }
2959
2960                 unsigned long i = 0;
2961                 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
2962                         if (first) {
2963                                 first = 0;
2964                         } else {
2965                                 if (opjoin_type == OR_OP_JOIN)
2966                                         buffer_add(sql_buf, " OR ");
2967                                 else
2968                                         buffer_add(sql_buf, " AND ");
2969                         }
2970
2971                         char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2972                         if( ! subpred ) {
2973                                 buffer_free( sql_buf );
2974                                 return NULL;
2975                         }
2976
2977                         buffer_fadd(sql_buf, "( %s )", subpred);
2978                         free(subpred);
2979                 }
2980
2981         } else if ( search_hash->type == JSON_HASH ) {
2982                 osrfLogDebug( OSRF_LOG_MARK,
2983                         "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME );
2984                 jsonIterator* search_itr = jsonNewIterator( search_hash );
2985                 if( !jsonIteratorHasNext( search_itr ) ) {
2986                         osrfLogError(
2987                                 OSRF_LOG_MARK,
2988                                 "%s: Invalid predicate structure: empty JSON object",
2989                                 MODULENAME
2990                         );
2991                         jsonIteratorFree( search_itr );
2992                         buffer_free( sql_buf );
2993                         return NULL;
2994                 }
2995
2996                 while ( (node = jsonIteratorNext( search_itr )) ) {
2997
2998                         if (first) {
2999                                 first = 0;
3000                         } else {
3001                                 if (opjoin_type == OR_OP_JOIN)
3002                                         buffer_add(sql_buf, " OR ");
3003                                 else
3004                                         buffer_add(sql_buf, " AND ");
3005                         }
3006
3007                         if ( '+' == search_itr->key[ 0 ] ) {
3008
3009                                 // This plus sign prefixes a class name or other table alias;
3010                                 // make sure the table alias is in scope
3011                                 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3012                                 if( ! alias_info ) {
3013                                         osrfLogError(
3014                                                          OSRF_LOG_MARK,
3015                                                         "%s: Invalid table alias \"%s\" in WHERE clause",
3016                                                         MODULENAME,
3017                                                         search_itr->key + 1
3018                                         );
3019                                         jsonIteratorFree( search_itr );
3020                                         buffer_free( sql_buf );
3021                                         return NULL;
3022                                 }
3023
3024                                 if ( node->type == JSON_STRING ) {
3025                                         // It's the name of a column; make sure it belongs to the class
3026                                         const char* fieldname = jsonObjectGetString( node );
3027                                         if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3028                                                 osrfLogError(
3029                                                         OSRF_LOG_MARK,
3030                                                         "%s: Invalid column name \"%s\" in WHERE clause "
3031                                                         "for table alias \"%s\"",
3032                                                         MODULENAME,
3033                                                         fieldname,
3034                                                         alias_info->alias
3035                                                 );
3036                                                 jsonIteratorFree( search_itr );
3037                                                 buffer_free( sql_buf );
3038                                                 return NULL;
3039                                         }
3040
3041                                         buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3042                                 } else {
3043                                         // It's something more complicated
3044                                         char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3045                                         if( ! subpred ) {
3046                                                 jsonIteratorFree( search_itr );
3047                                                 buffer_free( sql_buf );
3048                                                 return NULL;
3049                                         }
3050
3051                                         buffer_fadd(sql_buf, "( %s )", subpred);
3052                                         free(subpred);
3053                                 }
3054                         } else if ( '-' == search_itr->key[ 0 ] ) {
3055                                 if ( !strcasecmp("-or",search_itr->key) ) {
3056                                         char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3057                                         if( ! subpred ) {
3058                                                 jsonIteratorFree( search_itr );
3059                                                 buffer_free( sql_buf );
3060                                                 return NULL;
3061                                         }
3062
3063                                         buffer_fadd(sql_buf, "( %s )", subpred);
3064                                         free( subpred );
3065                                 } else if ( !strcasecmp("-and",search_itr->key) ) {
3066                                         char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3067                                         if( ! subpred ) {
3068                                                 jsonIteratorFree( search_itr );
3069                                                 buffer_free( sql_buf );
3070                                                 return NULL;
3071                                         }
3072
3073                                         buffer_fadd(sql_buf, "( %s )", subpred);
3074                                         free( subpred );
3075                                 } else if ( !strcasecmp("-not",search_itr->key) ) {
3076                                         char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3077                                         if( ! subpred ) {
3078                                                 jsonIteratorFree( search_itr );
3079                                                 buffer_free( sql_buf );
3080                                                 return NULL;
3081                                         }
3082
3083                                         buffer_fadd(sql_buf, " NOT ( %s )", subpred);
3084                                         free( subpred );
3085                                 } else if ( !strcasecmp("-exists",search_itr->key) ) {
3086                                         char* subpred = buildQuery( ctx, node, SUBSELECT );
3087                                         if( ! subpred ) {
3088                                                 jsonIteratorFree( search_itr );
3089                                                 buffer_free( sql_buf );
3090                                                 return NULL;
3091                                         }
3092
3093                                         buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
3094                                         free(subpred);
3095                                 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
3096                                         char* subpred = buildQuery( ctx, node, SUBSELECT );
3097                                         if( ! subpred ) {
3098                                                 jsonIteratorFree( search_itr );
3099                                                 buffer_free( sql_buf );
3100                                                 return NULL;
3101                                         }
3102
3103                                         buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
3104                                         free(subpred);
3105                                 } else {     // Invalid "minus" operator
3106                                         osrfLogError(
3107                                                          OSRF_LOG_MARK,
3108                                                         "%s: Invalid operator \"%s\" in WHERE clause",
3109                                                         MODULENAME,
3110                                                         search_itr->key
3111                                         );
3112                                         jsonIteratorFree( search_itr );
3113                                         buffer_free( sql_buf );
3114                                         return NULL;
3115                                 }
3116
3117                         } else {
3118
3119                                 const char* class = class_info->class_name;
3120                                 osrfHash* fields = class_info->fields;
3121                                 osrfHash* field = osrfHashGet( fields, search_itr->key );
3122
3123                                 if (!field) {
3124                                         const char* table = class_info->source_def;
3125                                         osrfLogError(
3126                                                 OSRF_LOG_MARK,
3127                                                 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3128                                                 MODULENAME,
3129                                                 search_itr->key,
3130                                                 table ? table : "?",
3131                                                 class ? class : "?"
3132                                         );
3133                                         jsonIteratorFree(search_itr);
3134                                         buffer_free(sql_buf);
3135                                         return NULL;
3136                                 }
3137
3138                                 char* subpred = searchPredicate( class_info, field, node, ctx );
3139                                 if( ! subpred ) {
3140                                         buffer_free(sql_buf);
3141                                         jsonIteratorFree(search_itr);
3142                                         return NULL;
3143                                 }
3144
3145                                 buffer_add( sql_buf, subpred );
3146                                 free(subpred);
3147                         }
3148                 }
3149                 jsonIteratorFree(search_itr);
3150
3151         } else {
3152                 // ERROR ... only hash and array allowed at this level
3153                 char* predicate_string = jsonObjectToJSON( search_hash );
3154                 osrfLogError(
3155                         OSRF_LOG_MARK,
3156                         "%s: Invalid predicate structure: %s",
3157                         MODULENAME,
3158                         predicate_string
3159                 );
3160                 buffer_free(sql_buf);
3161                 free(predicate_string);
3162                 return NULL;
3163         }
3164
3165         return buffer_release(sql_buf);
3166 }
3167
3168 /* Build a JSON_ARRAY of field names for a given table alias
3169 */
3170 static jsonObject* defaultSelectList( const char* table_alias ) {
3171
3172         if( ! table_alias )
3173                 table_alias = "";
3174
3175         ClassInfo* class_info = search_all_alias( table_alias );
3176         if( ! class_info ) {
3177                 osrfLogError(
3178                         OSRF_LOG_MARK,
3179                         "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3180                         MODULENAME,
3181                         table_alias
3182                 );
3183                 return NULL;
3184         }
3185
3186         jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3187         osrfHash* field_def = NULL;
3188         osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3189         while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3190                 const char* field_name = osrfHashIteratorKey( field_itr );
3191                 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3192                         jsonObjectPush( array, jsonNewObject( field_name ) );
3193                 }
3194         }
3195         osrfHashIteratorFree( field_itr );
3196
3197         return array;
3198 }
3199
3200 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3201 // The jsonObject must be a JSON_HASH with an single entry for "union",
3202 // "intersect", or "except".  The data associated with this key must be an
3203 // array of hashes, each hash being a query.
3204 // Also allowed but currently ignored: entries for "order_by" and "alias".
3205 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3206         // Sanity check
3207         if( ! combo || combo->type != JSON_HASH )
3208                 return NULL;      // should be impossible; validated by caller
3209
3210         const jsonObject* query_array = NULL;   // array of subordinate queries
3211         const char* op = NULL;     // name of operator, e.g. UNION
3212         const char* alias = NULL;  // alias for the query (needed for ORDER BY)
3213         int op_count = 0;          // for detecting conflicting operators
3214         int excepting = 0;         // boolean
3215         int all = 0;               // boolean
3216         jsonObject* order_obj = NULL;