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