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