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