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