]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/c-apps/oils_sql.c
LP#1254918: Allow skiping of user-object perms
[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 int verifyObjectPCRUD( osrfMethodContext*, osrfHash*, const jsonObject*, int );
123 static const char* org_tree_root( osrfMethodContext* ctx );
124 static jsonObject* single_hash( const char* key, const char* value );
125
126 static int child_initialized = 0;   /* boolean */
127
128 static dbi_conn writehandle; /* our MASTER db connection */
129 static dbi_conn dbhandle; /* our CURRENT db connection */
130 //static osrfHash * readHandles;
131
132 // The following points to the top of a stack of QueryFrames.  It's a little
133 // confusing because the top level of the query is at the bottom of the stack.
134 static QueryFrame* curr_query = NULL;
135
136 static dbi_conn writehandle; /* our MASTER db connection */
137 static dbi_conn dbhandle; /* our CURRENT db connection */
138 //static osrfHash * readHandles;
139
140 static int max_flesh_depth = 100;
141
142 static int perm_at_threshold = 5;
143 static int enforce_pcrud = 0;     // Boolean
144 static char* modulename = NULL;
145
146 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id);
147
148 static char* _sanitize_savepoint_name( const char* sp );
149
150 /**
151         @brief Connect to the database.
152         @return A database connection if successful, or NULL if not.
153 */
154 dbi_conn oilsConnectDB( const char* mod_name ) {
155
156         osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
157         if( dbi_initialize( NULL ) == -1 ) {
158                 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
159                 return NULL;
160         } else
161                 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
162
163         char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
164         char* user   = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
165         char* host   = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
166         char* port   = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
167         char* db     = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
168         char* pw     = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
169
170         osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
171         dbi_conn handle = dbi_conn_new( driver );
172
173         if( !handle ) {
174                 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
175                 return NULL;
176         }
177         osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
178
179         osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database.  host=%s, "
180                 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
181
182         if( host ) dbi_conn_set_option( handle, "host", host );
183         if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
184         if( user ) dbi_conn_set_option( handle, "username", user );
185         if( pw )   dbi_conn_set_option( handle, "password", pw );
186         if( db )   dbi_conn_set_option( handle, "dbname", db );
187
188         free( user );
189         free( host );
190         free( port );
191         free( db );
192         free( pw );
193
194         if( dbi_conn_connect( handle ) < 0 ) {
195                 sleep( 1 );
196                 if( dbi_conn_connect( handle ) < 0 ) {
197                         const char* msg;
198                         dbi_conn_error( handle, &msg );
199                         osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s",
200                                 msg ? msg : "(No description available)" );
201                         return NULL;
202                 }
203         }
204
205         osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
206
207         return handle;
208 }
209
210 /**
211         @brief Select some options.
212         @param module_name: Name of the server.
213         @param do_pcrud: Boolean.  True if we are to enforce PCRUD permissions.
214
215         This source file is used (at this writing) to implement three different servers:
216         - open-ils.reporter-store
217         - open-ils.pcrud
218         - open-ils.cstore
219
220         These servers behave mostly the same, but they implement different combinations of
221         methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
222
223         Here we use the server name in messages to identify which kind of server issued them.
224         We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
225 */
226 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
227         if( !module_name )
228                 module_name = "open-ils.cstore";   // bulletproofing with a default
229
230         if( modulename )
231                 free( modulename );
232
233         modulename = strdup( module_name );
234         enforce_pcrud = do_pcrud;
235         max_flesh_depth = flesh_depth;
236 }
237
238 /**
239         @brief Install a database connection.
240         @param conn Pointer to a database connection.
241
242         In some contexts, @a conn may merely provide a driver so that we can process strings
243         properly, without providing an open database connection.
244 */
245 void oilsSetDBConnection( dbi_conn conn ) {
246         dbhandle = writehandle = conn;
247 }
248
249 /**
250         @brief Determine whether a database connection is alive.
251         @param handle Handle for a database connection.
252         @return 1 if the connection is alive, or zero if it isn't.
253 */
254 int oilsIsDBConnected( dbi_conn handle ) {
255         // Do an innocuous SELECT.  If it succeeds, the database connection is still good.
256         dbi_result result = dbi_conn_query( handle, "SELECT 1;" );
257         if( result ) {
258                 dbi_result_free( result );
259                 return 1;
260         } else {
261                 // This is a terrible, horrible, no good, very bad kludge.
262                 // Sometimes the SELECT 1 query fails, not because the database connection is dead,
263                 // but because (due to a previous error) the database is ignoring all commands,
264                 // even innocuous SELECTs, until the current transaction is rolled back.  The only
265                 // known way to detect this condition via the dbi library is by looking at the error
266                 // message.  This approach will break if the language or wording of the message ever
267                 // changes.
268                 // Note: the dbi_conn_ping function purports to determine whether the database
269                 // connection is live, but at this writing this function is unreliable and useless.
270                 static const char* ok_msg = "ERROR:  current transaction is aborted, commands "
271                         "ignored until end of transaction block\n";
272                 const char* msg;
273                 dbi_conn_error( handle, &msg );
274                 if( strcmp( msg, ok_msg )) {
275                         osrfLogError( OSRF_LOG_MARK, "Database connection isn't working" );
276                         return 0;
277                 } else
278                         return 1;   // ignoring SELECT due to previous error; that's okay
279         }
280 }
281
282 /**
283         @brief Get a table name, view name, or subquery for use in a FROM clause.
284         @param class Pointer to the IDL class entry.
285         @return A table name, a view name, or a subquery in parentheses.
286
287         In some cases the IDL defines a class, not with a table name or a view name, but with
288         a SELECT statement, which may be used as a subquery.
289 */
290 char* oilsGetRelation( osrfHash* classdef ) {
291
292         char* source_def = NULL;
293         const char* tabledef = osrfHashGet( classdef, "tablename" );
294
295         if( tabledef ) {
296                 source_def = strdup( tabledef );   // Return the name of a table or view
297         } else {
298                 tabledef = osrfHashGet( classdef, "source_definition" );
299                 if( tabledef ) {
300                         // Return a subquery, enclosed in parentheses
301                         source_def = safe_malloc( strlen( tabledef ) + 3 );
302                         source_def[ 0 ] = '(';
303                         strcpy( source_def + 1, tabledef );
304                         strcat( source_def, ")" );
305                 } else {
306                         // Not found: return an error
307                         const char* classname = osrfHashGet( classdef, "classname" );
308                         if( !classname )
309                                 classname = "???";
310                         osrfLogError(
311                                 OSRF_LOG_MARK,
312                                 "%s ERROR No tablename or source_definition for class \"%s\"",
313                                 modulename,
314                                 classname
315                         );
316                 }
317         }
318
319         return source_def;
320 }
321
322 /**
323         @brief Add datatypes from the database to the fields in the IDL.
324         @param handle Handle for a database connection
325         @return Zero if successful, or 1 upon error.
326
327         For each relevant class in the IDL: ask the database for the datatype of every field.
328         In particular, determine which fields are text fields and which fields are numeric
329         fields, so that we know whether to enclose their values in quotes.
330 */
331 int oilsExtendIDL( dbi_conn handle ) {
332         osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
333         osrfHash* class = NULL;
334         growing_buffer* query_buf = buffer_init( 64 );
335         int results_found = 0;   // boolean
336
337         // For each class in the IDL...
338         while( (class = osrfHashIteratorNext( class_itr ) ) ) {
339                 const char* classname = osrfHashIteratorKey( class_itr );
340                 osrfHash* fields = osrfHashGet( class, "fields" );
341
342                 // If the class is virtual, ignore it
343                 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
344                         osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
345                         continue;
346                 }
347
348                 char* tabledef = oilsGetRelation( class );
349                 if( !tabledef )
350                         continue;   // No such relation -- a query of it would be doomed to failure
351
352                 buffer_reset( query_buf );
353                 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
354
355                 free(tabledef );
356
357                 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
358                                 modulename, OSRF_BUFFER_C_STR( query_buf ) );
359
360                 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
361                 if( result ) {
362
363                         results_found = 1;
364                         int columnIndex = 1;
365                         const char* columnName;
366                         while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
367
368                                 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
369                                                 columnName );
370
371                                 /* fetch the fieldmapper index */
372                                 osrfHash* _f = osrfHashGet(fields, columnName);
373                                 if( _f ) {
374
375                                         osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
376
377                                         /* determine the field type and storage attributes */
378
379                                         switch( dbi_result_get_field_type_idx( result, columnIndex )) {
380
381                                                 case DBI_TYPE_INTEGER : {
382
383                                                         if( !osrfHashGet(_f, "primitive") )
384                                                                 osrfHashSet(_f, "number", "primitive");
385
386                                                         int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
387                                                         if( attr & DBI_INTEGER_SIZE8 )
388                                                                 osrfHashSet( _f, "INT8", "datatype" );
389                                                         else
390                                                                 osrfHashSet( _f, "INT", "datatype" );
391                                                         break;
392                                                 }
393                                                 case DBI_TYPE_DECIMAL :
394                                                         if( !osrfHashGet( _f, "primitive" ))
395                                                                 osrfHashSet( _f, "number", "primitive" );
396
397                                                         osrfHashSet( _f, "NUMERIC", "datatype" );
398                                                         break;
399
400                                                 case DBI_TYPE_STRING :
401                                                         if( !osrfHashGet( _f, "primitive" ))
402                                                                 osrfHashSet( _f, "string", "primitive" );
403
404                                                         osrfHashSet( _f,"TEXT", "datatype" );
405                                                         break;
406
407                                                 case DBI_TYPE_DATETIME :
408                                                         if( !osrfHashGet( _f, "primitive" ))
409                                                                 osrfHashSet( _f, "string", "primitive" );
410
411                                                         osrfHashSet( _f, "TIMESTAMP", "datatype" );
412                                                         break;
413
414                                                 case DBI_TYPE_BINARY :
415                                                         if( !osrfHashGet( _f, "primitive" ))
416                                                                 osrfHashSet( _f, "string", "primitive" );
417
418                                                         osrfHashSet( _f, "BYTEA", "datatype" );
419                                         }
420
421                                         osrfLogDebug(
422                                                 OSRF_LOG_MARK,
423                                                 "Setting [%s] to primitive [%s] and datatype [%s]...",
424                                                 columnName,
425                                                 osrfHashGet( _f, "primitive" ),
426                                                 osrfHashGet( _f, "datatype" )
427                                         );
428                                 }
429                                 ++columnIndex;
430                         } // end while loop for traversing columns of result
431                         dbi_result_free( result  );
432                 } else {
433                         const char* msg;
434                         int errnum = dbi_conn_error( handle, &msg );
435                         osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]: %d, %s", classname,
436                                 errnum, msg ? msg : "(No description available)" );
437                         // We don't check the database connection here.  It's routine to get failures at
438                         // this point; we routinely try to query tables that don't exist, because they
439                         // are defined in the IDL but not in the database.
440                 }
441         } // end for each class in IDL
442
443         buffer_free( query_buf );
444         osrfHashIteratorFree( class_itr );
445         child_initialized = 1;
446
447         if( !results_found ) {
448                 osrfLogError( OSRF_LOG_MARK,
449                         "No results found for any class -- bad database connection?" );
450                 return 1;
451         } else if( ! oilsIsDBConnected( handle )) {
452                 osrfLogError( OSRF_LOG_MARK,
453                         "Unable to extend IDL: database connection isn't working" );
454                 return 1;
455         }
456         else
457                 return 0;
458 }
459
460 /**
461         @brief Free an osrfHash that stores a transaction ID.
462         @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
463
464         This function is a callback, to be called by the application session when it ends.
465         The application session stores the osrfHash via an opaque pointer.
466
467         If the osrfHash contains an entry for the key "xact_id", it means that an
468         uncommitted transaction is pending.  Roll it back.
469 */
470 void userDataFree( void* blob ) {
471         osrfHash* hash = (osrfHash*) blob;
472         if( osrfHashGet( hash, "xact_id" ) && writehandle ) {
473                 if( !dbi_conn_query( writehandle, "ROLLBACK;" )) {
474                         const char* msg;
475                         int errnum = dbi_conn_error( writehandle, &msg );
476                         osrfLogWarning( OSRF_LOG_MARK, "Unable to perform rollback: %d %s",
477                                 errnum, msg ? msg : "(No description available)" );
478                 };
479         }
480         if( writehandle ) {
481                 if( !dbi_conn_query( writehandle, "SELECT auditor.clear_audit_info();" ) ) {
482                         const char* msg;
483                         int errnum = dbi_conn_error( writehandle, &msg );
484                         osrfLogWarning( OSRF_LOG_MARK, "Unable to perform audit info clearing: %d %s",
485                                 errnum, msg ? msg : "(No description available)" );
486                 }
487         }
488
489         osrfHashFree( hash );
490 }
491
492 /**
493         @name Managing session data
494         @brief Maintain data stored via the userData pointer of the application session.
495
496         Currently, session-level data is stored in an osrfHash.  Other arrangements are
497         possible, and some would be more efficient.  The application session calls a
498         callback function to free userData before terminating.
499
500         Currently, the only data we store at the session level is the transaction id.  By this
501         means we can ensure that any pending transactions are rolled back before the application
502         session terminates.
503 */
504 /*@{*/
505
506 /**
507         @brief Free an item in the application session's userData.
508         @param key The name of a key for an osrfHash.
509         @param item An opaque pointer to the item associated with the key.
510
511         We store an osrfHash as userData with the application session, and arrange (by
512         installing userDataFree() as a different callback) for the session to free that
513         osrfHash before terminating.
514
515         This function is a callback for freeing items in the osrfHash.  Currently we store
516         two things:
517         - Transaction id of a pending transaction; a character string.  Key: "xact_id".
518         - Authkey; a character string.  Key: "authkey".
519         - User object from the authentication server; a jsonObject.  Key: "user_login".
520
521         If we ever store anything else in userData, we will need to revisit this function so
522         that it will free whatever else needs freeing.
523 */
524 static void sessionDataFree( char* key, void* item ) {
525         if( !strcmp( key, "xact_id" ) || !strcmp( key, "authkey" ) || !strncmp( key, "rs_size_", 8) ) 
526                 free( item );
527         else if( !strcmp( key, "user_login" ) )
528                 jsonObjectFree( (jsonObject*) item );
529         else if( !strcmp( key, "pcache" ) )
530                 osrfHashFree( (osrfHash*) item );
531 }
532
533 static void pcacheFree( char* key, void* item ) {
534         osrfStringArrayFree( (osrfStringArray*) item );
535 }
536
537 /**
538         @brief Initialize session cache.
539         @param ctx Pointer to the method context.
540
541         Create a cache for the session by making the session's userData member point
542         to an osrfHash instance.
543 */
544 static osrfHash* initSessionCache( osrfMethodContext* ctx ) {
545         ctx->session->userData = osrfNewHash();
546         osrfHashSetCallback( (osrfHash*) ctx->session->userData, &sessionDataFree );
547         ctx->session->userDataFree = &userDataFree;
548         return ctx->session->userData;
549 }
550
551 /**
552         @brief Save a transaction id.
553         @param ctx Pointer to the method context.
554
555         Save the session_id of the current application session as a transaction id.
556 */
557 static void setXactId( osrfMethodContext* ctx ) {
558         if( ctx && ctx->session ) {
559                 osrfAppSession* session = ctx->session;
560
561                 osrfHash* cache = session->userData;
562
563                 // If the session doesn't already have a hash, create one.  Make sure
564                 // that the application session frees the hash when it terminates.
565                 if( NULL == cache )
566                         cache = initSessionCache( ctx );
567
568                 // Save the transaction id in the hash, with the key "xact_id"
569                 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
570         }
571 }
572
573 /**
574         @brief Get the transaction ID for the current transaction, if any.
575         @param ctx Pointer to the method context.
576         @return Pointer to the transaction ID.
577
578         The return value points to an internal buffer, and will become invalid upon issuing
579         a commit or rollback.
580 */
581 static inline const char* getXactId( osrfMethodContext* ctx ) {
582         if( ctx && ctx->session && ctx->session->userData )
583                 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
584         else
585                 return NULL;
586 }
587
588 /**
589         @brief Clear the current transaction id.
590         @param ctx Pointer to the method context.
591 */
592 static inline void clearXactId( osrfMethodContext* ctx ) {
593         if( ctx && ctx->session && ctx->session->userData )
594                 osrfHashRemove( ctx->session->userData, "xact_id" );
595 }
596 /*@}*/
597
598 /**
599         @brief Stash the location for a particular perm in the sessionData cache
600         @param ctx Pointer to the method context.
601         @param perm Name of the permission we're looking at
602         @param array StringArray of perm location ids
603 */
604 static void setPermLocationCache( osrfMethodContext* ctx, const char* perm, osrfStringArray* locations ) {
605         if( ctx && ctx->session ) {
606                 osrfAppSession* session = ctx->session;
607
608                 osrfHash* cache = session->userData;
609
610                 // If the session doesn't already have a hash, create one.  Make sure
611                 // that the application session frees the hash when it terminates.
612                 if( NULL == cache )
613                         cache = initSessionCache( ctx );
614
615                 osrfHash* pcache = osrfHashGet(cache, "pcache");
616
617                 if( NULL == pcache ) {
618                         pcache = osrfNewHash();
619                         osrfHashSetCallback( pcache, &pcacheFree );
620                         osrfHashSet( cache, pcache, "pcache" );
621                 }
622
623                 if( perm && locations )
624                         osrfHashSet( pcache, locations, strdup(perm) );
625         }
626 }
627
628 /**
629         @brief Grab stashed location for a particular perm in the sessionData cache
630         @param ctx Pointer to the method context.
631         @param perm Name of the permission we're looking at
632 */
633 static osrfStringArray* getPermLocationCache( osrfMethodContext* ctx, const char* perm ) {
634         if( ctx && ctx->session ) {
635                 osrfAppSession* session = ctx->session;
636                 osrfHash* cache = session->userData;
637                 if( cache ) {
638                         osrfHash* pcache = osrfHashGet(cache, "pcache");
639                         if( pcache ) {
640                                 return osrfHashGet( pcache, perm );
641                         }
642                 }
643         }
644
645         return NULL;
646 }
647
648 /**
649         @brief Save the user's login in the userData for the current application session.
650         @param ctx Pointer to the method context.
651         @param user_login Pointer to the user login object to be cached (we cache the original,
652         not a copy of it).
653
654         If @a user_login is NULL, remove the user login if one is already cached.
655 */
656 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
657         if( ctx && ctx->session ) {
658                 osrfAppSession* session = ctx->session;
659
660                 osrfHash* cache = session->userData;
661
662                 // If the session doesn't already have a hash, create one.  Make sure
663                 // that the application session frees the hash when it terminates.
664                 if( NULL == cache )
665                         cache = initSessionCache( ctx );
666
667                 if( user_login )
668                         osrfHashSet( cache, user_login, "user_login" );
669                 else
670                         osrfHashRemove( cache, "user_login" );
671         }
672 }
673
674 /**
675         @brief Get the user login object for the current application session, if any.
676         @param ctx Pointer to the method context.
677         @return Pointer to the user login object if found; otherwise NULL.
678
679         The user login object was returned from the authentication server, and then cached so
680         we don't have to call the authentication server again for the same user.
681 */
682 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
683         if( ctx && ctx->session && ctx->session->userData )
684                 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
685         else
686                 return NULL;
687 }
688
689 /**
690         @brief Save a copy of an authkey in the userData of the current application session.
691         @param ctx Pointer to the method context.
692         @param authkey The authkey to be saved.
693
694         If @a authkey is NULL, remove the authkey if one is already cached.
695 */
696 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
697         if( ctx && ctx->session && authkey ) {
698                 osrfAppSession* session = ctx->session;
699                 osrfHash* cache = session->userData;
700
701                 // If the session doesn't already have a hash, create one.  Make sure
702                 // that the application session frees the hash when it terminates.
703                 if( NULL == cache )
704                         cache = initSessionCache( ctx );
705
706                 // Save the transaction id in the hash, with the key "xact_id"
707                 if( authkey && *authkey )
708                         osrfHashSet( cache, strdup( authkey ), "authkey" );
709                 else
710                         osrfHashRemove( cache, "authkey" );
711         }
712 }
713
714 /**
715         @brief Reset the login timeout.
716         @param authkey The authentication key for the current login session.
717         @param now The current time.
718         @return Zero if successful, or 1 if not.
719
720         Tell the authentication server to reset the timeout so that the login session won't
721         expire for a while longer.
722
723         We could dispense with the @a now parameter by calling time().  But we just called
724         time() in order to decide whether to reset the timeout, so we might as well reuse
725         the result instead of calling time() again.
726 */
727 static int reset_timeout( const char* authkey, time_t now ) {
728         jsonObject* auth_object = jsonNewObject( authkey );
729
730         // Ask the authentication server to reset the timeout.  It returns an event
731         // indicating success or failure.
732         jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
733                 "open-ils.auth.session.reset_timeout", auth_object );
734         jsonObjectFree( auth_object );
735
736         if( !result || result->type != JSON_HASH ) {
737                 osrfLogError( OSRF_LOG_MARK,
738                          "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
739                 jsonObjectFree( result );
740                 return 1;       // Not the right sort of object returned
741         }
742
743         const jsonObject* ilsevent = jsonObjectGetKeyConst( result, "ilsevent" );
744         if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
745                 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
746                 jsonObjectFree( result );
747                 return 1;    // Return code from method not available
748         }
749
750         if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
751                 const char* desc = jsonObjectGetString( jsonObjectGetKeyConst( result, "desc" ));
752                 if( !desc )
753                         desc = "(No reason available)";    // failsafe; shouldn't happen
754                 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
755                 jsonObjectFree( result );
756                 return 1;
757         }
758
759         // Revise our local proxy for the timeout deadline
760         // by a smallish fraction of the timeout interval
761         const char* timeout = jsonObjectGetString( jsonObjectGetKeyConst( result, "payload" ));
762         if( !timeout )
763                 timeout = "1";   // failsafe; shouldn't happen
764         time_next_reset = now + atoi( timeout ) / 15;
765
766         jsonObjectFree( result );
767         return 0;     // Successfully reset timeout
768 }
769
770 /**
771         @brief Get the authkey string for the current application session, if any.
772         @param ctx Pointer to the method context.
773         @return Pointer to the cached authkey if found; otherwise NULL.
774
775         If present, the authkey string was cached from a previous method call.
776 */
777 static const char* getAuthkey( osrfMethodContext* ctx ) {
778         if( ctx && ctx->session && ctx->session->userData ) {
779                 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
780         // LFW recent changes mean the userData hash gets set up earlier, but
781         // doesn't necessarily have an authkey yet
782         if (!authkey)
783             return NULL;
784
785                 // Possibly reset the authentication timeout to keep the login alive.  We do so
786                 // no more than once per method call, and not at all if it has been only a short
787                 // time since the last reset.
788
789                 // Here we reset explicitly, if at all.  We also implicitly reset the timeout
790                 // whenever we call the "open-ils.auth.session.retrieve" method.
791                 if( timeout_needs_resetting ) {
792                         time_t now = time( NULL );
793                         if( now >= time_next_reset && reset_timeout( authkey, now ) )
794                                 authkey = NULL;    // timeout has apparently expired already
795                 }
796
797                 timeout_needs_resetting = 0;
798                 return authkey;
799         }
800         else
801                 return NULL;
802 }
803
804 /**
805         @brief Implement the transaction.begin method.
806         @param ctx Pointer to the method context.
807         @return Zero if successful, or -1 upon error.
808
809         Start a transaction.  Save a transaction ID for future reference.
810
811         Method parameters:
812         - authkey (PCRUD only)
813
814         Return to client: Transaction ID
815 */
816 int beginTransaction( osrfMethodContext* ctx ) {
817         if(osrfMethodVerifyContext( ctx )) {
818                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
819                 return -1;
820         }
821
822         if( enforce_pcrud ) {
823                 timeout_needs_resetting = 1;
824                 const jsonObject* user = verifyUserPCRUD( ctx );
825                 if( !user )
826                         return -1;
827         }
828
829         dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
830         if( !result ) {
831                 const char* msg;
832                 int errnum = dbi_conn_error( writehandle, &msg );
833                 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
834                         modulename, errnum, msg ? msg : "(No description available)" );
835                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
836                         "osrfMethodException", ctx->request, "Error starting transaction" );
837                 if( !oilsIsDBConnected( writehandle ))
838                         osrfAppSessionPanic( ctx->session );
839                 return -1;
840         } else {
841                 dbi_result_free( result );
842                 setXactId( ctx );
843                 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
844                 osrfAppRespondComplete( ctx, ret );
845                 jsonObjectFree( ret );
846                 return 0;
847         }
848 }
849
850 /**
851         @brief Implement the savepoint.set method.
852         @param ctx Pointer to the method context.
853         @return Zero if successful, or -1 if not.
854
855         Issue a SAVEPOINT to the database server.
856
857         Method parameters:
858         - authkey (PCRUD only)
859         - savepoint name
860
861         Return to client: Savepoint name
862 */
863 int setSavepoint( osrfMethodContext* ctx ) {
864         if(osrfMethodVerifyContext( ctx )) {
865                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
866                 return -1;
867         }
868
869         int spNamePos = 0;
870         if( enforce_pcrud ) {
871                 spNamePos = 1;
872                 timeout_needs_resetting = 1;
873                 const jsonObject* user = verifyUserPCRUD( ctx );
874                 if( !user )
875                         return -1;
876         }
877
878         // Verify that a transaction is pending
879         const char* trans_id = getXactId( ctx );
880         if( NULL == trans_id ) {
881                 osrfAppSessionStatus(
882                         ctx->session,
883                         OSRF_STATUS_INTERNALSERVERERROR,
884                         "osrfMethodException",
885                         ctx->request,
886                         "No active transaction -- required for savepoints"
887                 );
888                 return -1;
889         }
890
891         // Get the savepoint name from the method params
892         const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
893
894         if (!spName) {
895                 osrfLogWarning(OSRF_LOG_MARK, "savepoint.set called with no name");
896                 return -1;
897         }
898
899         char *safeSpName = _sanitize_savepoint_name( spName );
900
901         dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", safeSpName );
902         free( safeSpName );
903         if( !result ) {
904                 const char* msg;
905                 int errnum = dbi_conn_error( writehandle, &msg );
906                 osrfLogError(
907                         OSRF_LOG_MARK,
908                         "%s: Error creating savepoint %s in transaction %s: %d %s",
909                         modulename,
910                         spName,
911                         trans_id,
912                         errnum,
913                         msg ? msg : "(No description available)"
914                 );
915                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
916                         "osrfMethodException", ctx->request, "Error creating savepoint" );
917                 if( !oilsIsDBConnected( writehandle ))
918                         osrfAppSessionPanic( ctx->session );
919                 return -1;
920         } else {
921                 dbi_result_free( result );
922                 jsonObject* ret = jsonNewObject( spName );
923                 osrfAppRespondComplete( ctx, ret );
924                 jsonObjectFree( ret  );
925                 return 0;
926         }
927 }
928
929 /**
930         @brief Implement the savepoint.release method.
931         @param ctx Pointer to the method context.
932         @return Zero if successful, or -1 if not.
933
934         Issue a RELEASE SAVEPOINT to the database server.
935
936         Method parameters:
937         - authkey (PCRUD only)
938         - savepoint name
939
940         Return to client: Savepoint name
941 */
942 int releaseSavepoint( osrfMethodContext* ctx ) {
943         if(osrfMethodVerifyContext( ctx )) {
944                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
945                 return -1;
946         }
947
948         int spNamePos = 0;
949         if( enforce_pcrud ) {
950                 spNamePos = 1;
951                 timeout_needs_resetting = 1;
952                 const jsonObject* user = verifyUserPCRUD( ctx );
953                 if(  !user )
954                         return -1;
955         }
956
957         // Verify that a transaction is pending
958         const char* trans_id = getXactId( ctx );
959         if( NULL == trans_id ) {
960                 osrfAppSessionStatus(
961                         ctx->session,
962                         OSRF_STATUS_INTERNALSERVERERROR,
963                         "osrfMethodException",
964                         ctx->request,
965                         "No active transaction -- required for savepoints"
966                 );
967                 return -1;
968         }
969
970         // Get the savepoint name from the method params
971         const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
972
973         if (!spName) {
974                 osrfLogWarning(OSRF_LOG_MARK, "savepoint.release called with no name");
975                 return -1;
976         }
977
978         char *safeSpName = _sanitize_savepoint_name( spName );
979
980         dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", safeSpName );
981         free( safeSpName );
982         if( !result ) {
983                 const char* msg;
984                 int errnum = dbi_conn_error( writehandle, &msg );
985                 osrfLogError(
986                         OSRF_LOG_MARK,
987                         "%s: Error releasing savepoint %s in transaction %s: %d %s",
988                         modulename,
989                         spName,
990                         trans_id,
991                         errnum,
992                         msg ? msg : "(No description available)"
993                 );
994                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
995                         "osrfMethodException", ctx->request, "Error releasing savepoint" );
996                 if( !oilsIsDBConnected( writehandle ))
997                         osrfAppSessionPanic( ctx->session );
998                 return -1;
999         } else {
1000                 dbi_result_free( result );
1001                 jsonObject* ret = jsonNewObject( spName );
1002                 osrfAppRespondComplete( ctx, ret );
1003                 jsonObjectFree( ret );
1004                 return 0;
1005         }
1006 }
1007
1008 /**
1009         @brief Implement the savepoint.rollback method.
1010         @param ctx Pointer to the method context.
1011         @return Zero if successful, or -1 if not.
1012
1013         Issue a ROLLBACK TO SAVEPOINT to the database server.
1014
1015         Method parameters:
1016         - authkey (PCRUD only)
1017         - savepoint name
1018
1019         Return to client: Savepoint name
1020 */
1021 int rollbackSavepoint( osrfMethodContext* ctx ) {
1022         if(osrfMethodVerifyContext( ctx )) {
1023                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
1024                 return -1;
1025         }
1026
1027         int spNamePos = 0;
1028         if( enforce_pcrud ) {
1029                 spNamePos = 1;
1030                 timeout_needs_resetting = 1;
1031                 const jsonObject* user = verifyUserPCRUD( ctx );
1032                 if( !user )
1033                         return -1;
1034         }
1035
1036         // Verify that a transaction is pending
1037         const char* trans_id = getXactId( ctx );
1038         if( NULL == trans_id ) {
1039                 osrfAppSessionStatus(
1040                         ctx->session,
1041                         OSRF_STATUS_INTERNALSERVERERROR,
1042                         "osrfMethodException",
1043                         ctx->request,
1044                         "No active transaction -- required for savepoints"
1045                 );
1046                 return -1;
1047         }
1048
1049         // Get the savepoint name from the method params
1050         const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1051
1052         if (!spName) {
1053                 osrfLogWarning(OSRF_LOG_MARK, "savepoint.rollback called with no name");
1054                 return -1;
1055         }
1056
1057         char *safeSpName = _sanitize_savepoint_name( spName );
1058
1059         dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", safeSpName );
1060         free( safeSpName );
1061         if( !result ) {
1062                 const char* msg;
1063                 int errnum = dbi_conn_error( writehandle, &msg );
1064                 osrfLogError(
1065                         OSRF_LOG_MARK,
1066                         "%s: Error rolling back savepoint %s in transaction %s: %d %s",
1067                         modulename,
1068                         spName,
1069                         trans_id,
1070                         errnum,
1071                         msg ? msg : "(No description available)"
1072                 );
1073                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1074                         "osrfMethodException", ctx->request, "Error rolling back savepoint" );
1075                 if( !oilsIsDBConnected( writehandle ))
1076                         osrfAppSessionPanic( ctx->session );
1077                 return -1;
1078         } else {
1079                 dbi_result_free( result );
1080                 jsonObject* ret = jsonNewObject( spName );
1081                 osrfAppRespondComplete( ctx, ret );
1082                 jsonObjectFree( ret );
1083                 return 0;
1084         }
1085 }
1086
1087 /**
1088         @brief Implement the transaction.commit method.
1089         @param ctx Pointer to the method context.
1090         @return Zero if successful, or -1 if not.
1091
1092         Issue a COMMIT to the database server.
1093
1094         Method parameters:
1095         - authkey (PCRUD only)
1096
1097         Return to client: Transaction ID.
1098 */
1099 int commitTransaction( osrfMethodContext* ctx ) {
1100         if(osrfMethodVerifyContext( ctx )) {
1101                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1102                 return -1;
1103         }
1104
1105         if( enforce_pcrud ) {
1106                 timeout_needs_resetting = 1;
1107                 const jsonObject* user = verifyUserPCRUD( ctx );
1108                 if( !user )
1109                         return -1;
1110         }
1111
1112         // Verify that a transaction is pending
1113         const char* trans_id = getXactId( ctx );
1114         if( NULL == trans_id ) {
1115                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1116                                 "osrfMethodException", ctx->request, "No active transaction to commit" );
1117                 return -1;
1118         }
1119
1120         dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1121         if( !result ) {
1122                 const char* msg;
1123                 int errnum = dbi_conn_error( writehandle, &msg );
1124                 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1125                         modulename, errnum, msg ? msg : "(No description available)" );
1126                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1127                         "osrfMethodException", ctx->request, "Error committing transaction" );
1128                 if( !oilsIsDBConnected( writehandle ))
1129                         osrfAppSessionPanic( ctx->session );
1130                 return -1;
1131         } else {
1132                 dbi_result_free( result );
1133                 jsonObject* ret = jsonNewObject( trans_id );
1134                 osrfAppRespondComplete( ctx, ret );
1135                 jsonObjectFree( ret );
1136                 clearXactId( ctx );
1137                 return 0;
1138         }
1139 }
1140
1141 /**
1142         @brief Implement the transaction.rollback method.
1143         @param ctx Pointer to the method context.
1144         @return Zero if successful, or -1 if not.
1145
1146         Issue a ROLLBACK to the database server.
1147
1148         Method parameters:
1149         - authkey (PCRUD only)
1150
1151         Return to client: Transaction ID
1152 */
1153 int rollbackTransaction( osrfMethodContext* ctx ) {
1154         if( osrfMethodVerifyContext( ctx )) {
1155                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
1156                 return -1;
1157         }
1158
1159         if( enforce_pcrud ) {
1160                 timeout_needs_resetting = 1;
1161                 const jsonObject* user = verifyUserPCRUD( ctx );
1162                 if( !user )
1163                         return -1;
1164         }
1165
1166         // Verify that a transaction is pending
1167         const char* trans_id = getXactId( ctx );
1168         if( NULL == trans_id ) {
1169                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1170                                 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1171                 return -1;
1172         }
1173
1174         dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1175         if( !result ) {
1176                 const char* msg;
1177                 int errnum = dbi_conn_error( writehandle, &msg );
1178                 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1179                         modulename, errnum, msg ? msg : "(No description available)" );
1180                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1181                         "osrfMethodException", ctx->request, "Error rolling back transaction" );
1182                 if( !oilsIsDBConnected( writehandle ))
1183                         osrfAppSessionPanic( ctx->session );
1184                 return -1;
1185         } else {
1186                 dbi_result_free( result );
1187                 jsonObject* ret = jsonNewObject( trans_id );
1188                 osrfAppRespondComplete( ctx, ret );
1189                 jsonObjectFree( ret );
1190                 clearXactId( ctx );
1191                 return 0;
1192         }
1193 }
1194
1195 /**
1196         @brief Implement the "search" method.
1197         @param ctx Pointer to the method context.
1198         @return Zero if successful, or -1 if not.
1199
1200         Method parameters:
1201         - authkey (PCRUD only)
1202         - WHERE clause, as jsonObject
1203         - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1204
1205         Return to client: rows of the specified class that satisfy a specified WHERE clause.
1206         Optionally flesh linked fields.
1207 */
1208 int doSearch( osrfMethodContext* ctx ) {
1209         if( osrfMethodVerifyContext( ctx )) {
1210                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1211                 return -1;
1212         }
1213
1214         if( enforce_pcrud )
1215                 timeout_needs_resetting = 1;
1216
1217         jsonObject* where_clause;
1218         jsonObject* rest_of_query;
1219
1220         if( enforce_pcrud ) {
1221                 where_clause  = jsonObjectGetIndex( ctx->params, 1 );
1222                 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1223         } else {
1224                 where_clause  = jsonObjectGetIndex( ctx->params, 0 );
1225                 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1226         }
1227
1228         if( !where_clause ) { 
1229                 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1230                 return -1;
1231         }
1232
1233         // Get the class metadata
1234         osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1235         osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1236
1237         // Do the query
1238         int err = 0;
1239         jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1240         if( err ) {
1241                 osrfAppRespondComplete( ctx, NULL );
1242                 return -1;
1243         }
1244
1245         // doFieldmapperSearch() now takes care of our responding for us
1246 //      // Return each row to the client
1247 //      jsonObject* cur = 0;
1248 //      unsigned long res_idx = 0;
1249 //
1250 //      while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1251 //              // We used to discard based on perms here, but now that's
1252 //              // inside doFieldmapperSearch()
1253 //              osrfAppRespond( ctx, cur );
1254 //      }
1255
1256         jsonObjectFree( obj );
1257
1258         osrfAppRespondComplete( ctx, NULL );
1259         return 0;
1260 }
1261
1262 /**
1263         @brief Implement the "id_list" method.
1264         @param ctx Pointer to the method context.
1265         @param err Pointer through which to return an error code.
1266         @return Zero if successful, or -1 if not.
1267
1268         Method parameters:
1269         - authkey (PCRUD only)
1270         - WHERE clause, as jsonObject
1271         - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1272
1273         Return to client: The primary key values for all rows of the relevant class that
1274         satisfy a specified WHERE clause.
1275
1276         This method relies on the assumption that every class has a primary key consisting of
1277         a single column.
1278 */
1279 int doIdList( osrfMethodContext* ctx ) {
1280         if( osrfMethodVerifyContext( ctx )) {
1281                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1282                 return -1;
1283         }
1284
1285         if( enforce_pcrud )
1286                 timeout_needs_resetting = 1;
1287
1288         jsonObject* where_clause;
1289         jsonObject* rest_of_query;
1290
1291         // We use the where clause without change.  But we need to massage the rest of the
1292         // query, so we work with a copy of it instead of modifying the original.
1293
1294         if( enforce_pcrud ) {
1295                 where_clause  = jsonObjectGetIndex( ctx->params, 1 );
1296                 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1297         } else {
1298                 where_clause  = jsonObjectGetIndex( ctx->params, 0 );
1299                 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1300         }
1301
1302         if( !where_clause ) { 
1303                 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1304                 return -1;
1305         }
1306
1307         // Eliminate certain SQL clauses, if present.
1308         if( rest_of_query ) {
1309                 jsonObjectRemoveKey( rest_of_query, "select" );
1310                 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1311                 jsonObjectRemoveKey( rest_of_query, "flesh" );
1312                 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1313         } else {
1314                 rest_of_query = jsonNewObjectType( JSON_HASH );
1315         }
1316
1317         jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1318
1319         // Get the class metadata
1320         osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1321         osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1322
1323         // Build a SELECT list containing just the primary key,
1324         // i.e. like { "classname":["keyname"] }
1325         jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1326
1327         // Load array with name of primary key
1328         jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1329         jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1330         jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1331
1332         jsonObjectSetKey( rest_of_query, "select", select_clause );
1333
1334         // Do the query
1335         int err = 0;
1336         jsonObject* obj =
1337                 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1338
1339         jsonObjectFree( rest_of_query );
1340         if( err ) {
1341                 osrfAppRespondComplete( ctx, NULL );
1342                 return -1;
1343         }
1344
1345         // Return each primary key value to the client
1346         jsonObject* cur;
1347         unsigned long res_idx = 0;
1348         while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1349                 // We used to discard based on perms here, but now that's
1350                 // inside doFieldmapperSearch()
1351                 osrfAppRespond( ctx,
1352                         oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1353         }
1354
1355         jsonObjectFree( obj );
1356         osrfAppRespondComplete( ctx, NULL );
1357         return 0;
1358 }
1359
1360 /**
1361         @brief Verify that we have a valid class reference.
1362         @param ctx Pointer to the method context.
1363         @param param Pointer to the method parameters.
1364         @return 1 if the class reference is valid, or zero if it isn't.
1365
1366         The class of the method params must match the class to which the method id devoted.
1367         For PCRUD there are additional restrictions.
1368 */
1369 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1370
1371         osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1372         osrfHash* class = osrfHashGet( method_meta, "class" );
1373
1374         // Compare the method's class to the parameters' class
1375         if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1376
1377                 // Oops -- they don't match.  Complain.
1378                 growing_buffer* msg = buffer_init( 128 );
1379                 buffer_fadd(
1380                         msg,
1381                         "%s: %s method for type %s was passed a %s",
1382                         modulename,
1383                         osrfHashGet( method_meta, "methodtype" ),
1384                         osrfHashGet( class, "classname" ),
1385                         param->classname ? param->classname : "(null)"
1386                 );
1387
1388                 char* m = buffer_release( msg );
1389                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1390                                 ctx->request, m );
1391                 free( m );
1392
1393                 return 0;
1394         }
1395
1396         if( enforce_pcrud )
1397                 return verifyObjectPCRUD( ctx, class, param, 1 );
1398         else
1399                 return 1;
1400 }
1401
1402 /**
1403         @brief (PCRUD only) Verify that the user is properly logged in.
1404         @param ctx Pointer to the method context.
1405         @return If the user is logged in, a pointer to the user object from the authentication
1406         server; otherwise NULL.
1407 */
1408 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1409
1410         // Get the authkey (the first method parameter)
1411         const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1412
1413         // See if we have the same authkey, and a user object,
1414         // locally cached from a previous call
1415         const char* cached_authkey = getAuthkey( ctx );
1416         if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1417                 const jsonObject* cached_user = getUserLogin( ctx );
1418                 if( cached_user )
1419                         return cached_user;
1420         }
1421
1422         // We have no matching authentication data in the cache.  Authenticate from scratch.
1423         jsonObject* auth_object = jsonNewObject( auth );
1424
1425         // Fetch the user object from the authentication server
1426         jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1427                         auth_object );
1428         jsonObjectFree( auth_object );
1429
1430         if( !user->classname || strcmp(user->classname, "au" )) {
1431
1432                 growing_buffer* msg = buffer_init( 128 );
1433                 buffer_fadd(
1434                         msg,
1435                         "%s: permacrud received a bad auth token: %s",
1436                         modulename,
1437                         auth
1438                 );
1439
1440                 char* m = buffer_release( msg );
1441                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1442                                 ctx->request, m );
1443
1444                 free( m );
1445                 jsonObjectFree( user );
1446                 user = NULL;
1447         } else if( writeAuditInfo( ctx, oilsFMGetStringConst( user, "id" ), oilsFMGetStringConst( user, "wsid" ) ) ) {
1448                 // Failed to set audit information - But note that write_audit_info already set error information.
1449                 jsonObjectFree( user );
1450                 user = NULL;
1451         }
1452
1453         setUserLogin( ctx, user );
1454         setAuthkey( ctx, auth );
1455
1456         // Allow ourselves up to a second before we have to reset the login timeout.
1457         // It would be nice to use some fraction of the timeout interval enforced by the
1458         // authentication server, but that value is not readily available at this point.
1459         // Instead, we use a conservative default interval.
1460         time_next_reset = time( NULL ) + 1;
1461
1462         return user;
1463 }
1464
1465 /**
1466         @brief For PCRUD: Determine whether the current user may access the current row.
1467         @param ctx Pointer to the method context.
1468         @param class Same as ctx->method->userData's item for key "class" except when called in recursive doFieldmapperSearch
1469         @param obj Pointer to the row being potentially accessed.
1470         @return 1 if access is permitted, or 0 if it isn't.
1471
1472         The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1473 */
1474 static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const jsonObject* obj, int rs_size ) {
1475
1476         dbhandle = writehandle;
1477
1478         // Figure out what class and method are involved
1479         osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1480         const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1481
1482         if (!rs_size) {
1483                 int *rs_size_from_hash = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
1484                 if (rs_size_from_hash) {
1485                         rs_size = *rs_size_from_hash;
1486                         osrfLogDebug(OSRF_LOG_MARK, "used rs_size from request-scoped hash: %d", rs_size);
1487                 }
1488         }
1489
1490         // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1491         // contexts we will do another lookup of the current row, even if we already have a
1492         // previously fetched row image, because the row image in hand may not include the
1493         // foreign key(s) that we need.
1494
1495         // This is a quick fix with a bludgeon.  There are ways to avoid the extra lookup,
1496         // but they aren't implemented yet.
1497
1498         int fetch = 0;
1499         if( *method_type == 's' || *method_type == 'i' ) {
1500                 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1501                 fetch = 1;
1502         } else if( *method_type == 'u' || *method_type == 'd' ) {
1503                 fetch = 1; // MUST go to the db for the object for update and delete
1504         }
1505
1506         // Get the appropriate permacrud entry from the IDL, depending on method type
1507         osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1508         if( !pcrud ) {
1509                 // No permacrud for this method type on this class
1510
1511                 growing_buffer* msg = buffer_init( 128 );
1512                 buffer_fadd(
1513                         msg,
1514                         "%s: %s on class %s has no permacrud IDL entry",
1515                         modulename,
1516                         osrfHashGet( method_metadata, "methodtype" ),
1517                         osrfHashGet( class, "classname" )
1518                 );
1519
1520                 char* m = buffer_release( msg );
1521                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1522                                 "osrfMethodException", ctx->request, m );
1523
1524                 free( m );
1525
1526                 return 0;
1527         }
1528
1529         // Get the user id, and make sure the user is logged in
1530         const jsonObject* user = verifyUserPCRUD( ctx );
1531         if( !user )
1532                 return 0;    // Not logged in?  No access.
1533
1534         int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1535
1536         // Get a list of permissions from the permacrud entry.
1537         osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1538         if( permission->size == 0 ) {
1539                 osrfLogDebug(
1540                         OSRF_LOG_MARK,
1541                         "No permissions required for this action (class %s), passing through",
1542                         osrfHashGet(class, "classname")
1543                 );
1544                 return 1;
1545         }
1546
1547         // Build a list of org units that own the row.  This is fairly convoluted because there
1548         // are several different ways that an org unit may own the row, as defined by the
1549         // permacrud entry.
1550
1551         // Local context means that the row includes a foreign key pointing to actor.org_unit,
1552         // identifying an owning org_unit..
1553         osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1554
1555         // Foreign context adds a layer of indirection.  The row points to some other row that
1556         // an org unit may own.  The "jump" attribute, if present, adds another layer of
1557         // indirection.
1558         osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1559
1560         // The following string array stores the list of org units.  (We don't have a thingie
1561         // for storing lists of integers, so we fake it with a list of strings.)
1562         osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1563
1564         const char* context_org = NULL;
1565     const char* pkey = NULL;
1566     jsonObject *param = NULL;
1567         const char* perm = NULL;
1568         int OK = 0;
1569         int i = 0;
1570         int err = 0;
1571         const char* pkey_value = NULL;
1572         if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1573                 // If the global_required attribute is present and true, then the only owning
1574                 // org unit is the root org unit, i.e. the one with no parent.
1575                 osrfLogDebug( OSRF_LOG_MARK,
1576                                 "global-level permissions required, fetching top of the org tree" );
1577
1578                 // no need to check perms for org tree root retrieval
1579                 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1580                 // check for perm at top of org tree
1581                 const char* org_tree_root_id = org_tree_root( ctx );
1582                 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1583
1584                 if( org_tree_root_id ) {
1585                         osrfStringArrayAdd( context_org_array, org_tree_root_id );
1586                         osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1587                 } else  {
1588                         osrfStringArrayFree( context_org_array );
1589                         return 0;
1590                 }
1591
1592         } else {
1593                 // If the global_required attribute is absent or false, then we look for
1594                 // local and/or foreign context.  In order to find the relevant foreign
1595                 // keys, we must either read the relevant row from the database, or look at
1596                 // the image of the row that we already have in memory.
1597
1598                 // Even if we have an image of the row in memory, that image may not include the
1599                 // foreign key column(s) that we need.  So whenever possible, we do a fresh read
1600                 // of the row to make sure that we have what we need.
1601
1602             osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1603                                 "fetching context org ids" );
1604
1605         pkey = osrfHashGet( class, "primarykey" );
1606
1607                 if( !pkey ) {
1608                         // There is no primary key, so we can't do a fresh lookup.  Use the row
1609                         // image that we already have.  If it doesn't have everything we need, too bad.
1610                         fetch = 0;
1611                         param = jsonObjectClone( obj );
1612                         osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1613                 } else if( obj->classname ) {
1614                         pkey_value = oilsFMGetStringConst( obj, pkey );
1615                         if( !fetch )
1616                                 param = jsonObjectClone( obj );
1617                         osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1618                                 pkey_value );
1619                 } else {
1620                         pkey_value = jsonObjectGetString( obj );
1621                         fetch = 1;
1622                         osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1623                                 "of %s and retrieving from the database", pkey_value );
1624                 }
1625
1626                 if( fetch ) {
1627                         // Fetch the row so that we can look at the foreign key(s)
1628                         osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1629                         jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1630                         jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1631                         jsonObjectFree( _tmp_params );
1632                         osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1633
1634                         param = jsonObjectExtractIndex( _list, 0 );
1635                         jsonObjectFree( _list );
1636
1637             fetch = 0;
1638                 }
1639
1640                 if( !param ) {
1641                         // The row doesn't exist.  Complain, and deny access.
1642                         osrfLogDebug( OSRF_LOG_MARK,
1643                                         "Object not found in the database with primary key %s of %s",
1644                                         pkey, pkey_value );
1645
1646                         growing_buffer* msg = buffer_init( 128 );
1647                         buffer_fadd(
1648                                 msg,
1649                                 "%s: no object found with primary key %s of %s",
1650                                 modulename,
1651                                 pkey,
1652                                 pkey_value
1653                         );
1654
1655                         char* m = buffer_release( msg );
1656                         osrfAppSessionStatus(
1657                                 ctx->session,
1658                                 OSRF_STATUS_INTERNALSERVERERROR,
1659                                 "osrfMethodException",
1660                                 ctx->request,
1661                                 m
1662                         );
1663
1664                         free( m );
1665                         return 0;
1666                 }
1667
1668                 if( local_context && local_context->size > 0 ) {
1669                         // The IDL provides a list of column names for the foreign keys denoting
1670                         // local context, i.e. columns identifying owing org units directly.  Look up
1671                         // the value of each one, and if it isn't null, add it to the list of org units.
1672                         osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1673                                 local_context->size );
1674                         int i = 0;
1675                         const char* lcontext = NULL;
1676                         while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1677                                 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1678                                 if( fkey_value ) {    // if not null
1679                                         osrfStringArrayAdd( context_org_array, fkey_value );
1680                                         osrfLogDebug(
1681                                                 OSRF_LOG_MARK,
1682                                                 "adding class-local field %s (value: %s) to the context org list",
1683                                                 lcontext,
1684                                                 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1685                                         );
1686                                 }
1687                         }
1688                 }
1689
1690                 if( foreign_context ) {
1691                         unsigned long class_count = osrfHashGetCount( foreign_context );
1692                         osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1693
1694                         if( class_count > 0 ) {
1695
1696                                 // The IDL provides a list of foreign key columns pointing to rows that
1697                                 // an org unit may own.  Follow each link, identify the owning org unit,
1698                                 // and add it to the list.
1699                                 osrfHash* fcontext = NULL;
1700                                 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1701                                 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1702                                         // For each class to which a foreign key points:
1703                                         const char* class_name = osrfHashIteratorKey( class_itr );
1704                                         osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1705
1706                                         osrfLogDebug(
1707                                                 OSRF_LOG_MARK,
1708                                                 "%d foreign context fields(s) specified for class %s",
1709                                                 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1710                                                 class_name
1711                                         );
1712
1713                                         // Get the name of the key field in the foreign table
1714                                         const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1715
1716                                         // Get the value of the foreign key pointing to the foreign table
1717                                         char* foreign_pkey_value =
1718                                                         oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1719                                         if( !foreign_pkey_value )
1720                                                 continue;    // Foreign key value is null; skip it
1721
1722                                         // Look up the row to which the foreign key points
1723                                         jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1724
1725                                         osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1726                                         jsonObject* _list = doFieldmapperSearch(
1727                                                 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1728                                         osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1729
1730                                         jsonObject* _fparam = NULL;
1731                                         if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1732                                                 _fparam = jsonObjectExtractIndex( _list, 0 );
1733
1734                                         jsonObjectFree( _tmp_params );
1735                                         jsonObjectFree( _list );
1736
1737                                         // At this point _fparam either points to the row identified by the
1738                                         // foreign key, or it's NULL (no such row found).
1739
1740                                         osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1741
1742                                         const char* bad_class = NULL;  // For noting failed lookups
1743                                         if( ! _fparam )
1744                                                 bad_class = class_name;    // Referenced row not found
1745                                         else if( jump_list ) {
1746                                                 // Follow a chain of rows, linked by foreign keys, to find an owner
1747                                                 const char* flink = NULL;
1748                                                 int k = 0;
1749                                                 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1750                                                         // For each entry in the jump list.  Each entry (i.e. flink) is
1751                                                         // the name of a foreign key column in the current row.
1752
1753                                                         // From the IDL, get the linkage information for the next jump
1754                                                         osrfHash* foreign_link_hash =
1755                                                                         oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1756
1757                                                         // Get the class metadata for the class
1758                                                         // to which the foreign key points
1759                                                         osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1760                                                                         osrfHashGet( foreign_link_hash, "class" ));
1761
1762                                                         // Get the name of the referenced key of that class
1763                                                         foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1764
1765                                                         // Get the value of the foreign key pointing to that class
1766                                                         free( foreign_pkey_value );
1767                                                         foreign_pkey_value = oilsFMGetString( _fparam, flink );
1768                                                         if( !foreign_pkey_value )
1769                                                                 break;    // Foreign key is null; quit looking
1770
1771                                                         // Build a WHERE clause for the lookup
1772                                                         _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1773
1774                                                         // Do the lookup
1775                                                         _list = doFieldmapperSearch( ctx, foreign_class_meta,
1776                                                                         _tmp_params, NULL, &err );
1777
1778                                                         // Get the resulting row
1779                                                         jsonObjectFree( _fparam );
1780                                                         if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1781                                                                 _fparam = jsonObjectExtractIndex( _list, 0 );
1782                                                         else {
1783                                                                 // Referenced row not found
1784                                                                 _fparam = NULL;
1785                                                                 bad_class = osrfHashGet( foreign_link_hash, "class" );
1786                                                         }
1787
1788                                                         jsonObjectFree( _tmp_params );
1789                                                         jsonObjectFree( _list );
1790                                                 }
1791                                         }
1792
1793                                         if( bad_class ) {
1794
1795                                                 // We had a foreign key pointing to such-and-such a row, but then
1796                                                 // we couldn't fetch that row.  The data in the database are in an
1797                                                 // inconsistent state; the database itself may even be corrupted.
1798                                                 growing_buffer* msg = buffer_init( 128 );
1799                                                 buffer_fadd(
1800                                                         msg,
1801                                                         "%s: no object of class %s found with primary key %s of %s",
1802                                                         modulename,
1803                                                         bad_class,
1804                                                         foreign_pkey,
1805                                                         foreign_pkey_value ? foreign_pkey_value : "(null)"
1806                                                 );
1807
1808                                                 char* m = buffer_release( msg );
1809                                                 osrfAppSessionStatus(
1810                                                         ctx->session,
1811                                                         OSRF_STATUS_INTERNALSERVERERROR,
1812                                                         "osrfMethodException",
1813                                                         ctx->request,
1814                                                         m
1815                                                 );
1816
1817                                                 free( m );
1818                                                 osrfHashIteratorFree( class_itr );
1819                                                 free( foreign_pkey_value );
1820                                                 jsonObjectFree( param );
1821
1822                                                 return 0;
1823                                         }
1824
1825                                         free( foreign_pkey_value );
1826
1827                                         if( _fparam ) {
1828                                                 // Examine each context column of the foreign row,
1829                                                 // and add its value to the list of org units.
1830                                                 int j = 0;
1831                                                 const char* foreign_field = NULL;
1832                                                 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1833                                                 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1834                                                         osrfStringArrayAdd( context_org_array,
1835                                                                 oilsFMGetStringConst( _fparam, foreign_field ));
1836                                                         osrfLogDebug( OSRF_LOG_MARK,
1837                                                                 "adding foreign class %s field %s (value: %s) "
1838                                                                         "to the context org list",
1839                                                                 class_name,
1840                                                                 foreign_field,
1841                                                                 osrfStringArrayGetString(
1842                                                                         context_org_array, context_org_array->size - 1 )
1843                                                         );
1844                                                 }
1845
1846                                                 jsonObjectFree( _fparam );
1847                                         }
1848                                 }
1849
1850                                 osrfHashIteratorFree( class_itr );
1851                         }
1852                 }
1853         }
1854
1855     // If there is an owning_user attached to the action, we allow that user and users with
1856     // object perms on the object. CREATE can't use this. We only do this when there is no
1857     // context org for this action, and when we're not ignoring object perms.
1858     if (
1859         *method_type != 'c' &&
1860         !str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) && // Always honor
1861         context_org_array->size == 0
1862     ) {
1863         char* owning_user_field = osrfHashGet( pcrud, "owning_user" );
1864         if (owning_user_field) {
1865
1866             if (!param) { // We didn't get it during the context lookup
1867                             pkey = osrfHashGet( class, "primarykey" );
1868                 
1869                                 if( !pkey  ) {
1870                                         // There is no primary key, so we can't do a fresh lookup.  Use the row
1871                                         // image that we already have.  If it doesn't have everything we need, too bad.
1872                                         fetch = 0;
1873                                         param = jsonObjectClone( obj );
1874                                         osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1875                                 } else if( obj->classname ) {
1876                                         pkey_value = oilsFMGetStringConst( obj, pkey );
1877                                         if( !fetch )
1878                                                 param = jsonObjectClone( obj );
1879                                         osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1880                                                 pkey_value );
1881                                 } else {
1882                                         pkey_value = jsonObjectGetString( obj );
1883                                         fetch = 1;
1884                                         osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1885                                                 "of %s and retrieving from the database", pkey_value );
1886                                 }
1887                 
1888                                 if( fetch ) {
1889                                         // Fetch the row so that we can look at the foreign key(s)
1890                                         osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1891                                         jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1892                                         jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1893                                         jsonObjectFree( _tmp_params );
1894                                         osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1895                 
1896                                         param = jsonObjectExtractIndex( _list, 0 );
1897                                         jsonObjectFree( _list );
1898                                 }
1899             }
1900         
1901                         if( !param ) {
1902                                 // The row doesn't exist.  Complain, and deny access.
1903                                 osrfLogDebug( OSRF_LOG_MARK,
1904                                                 "Object not found in the database with primary key %s of %s",
1905                                                 pkey, pkey_value );
1906         
1907                                 growing_buffer* msg = buffer_init( 128 );
1908                                 buffer_fadd(
1909                                         msg,
1910                                         "%s: no object found with primary key %s of %s",
1911                                         modulename,
1912                                         pkey,
1913                                         pkey_value
1914                                 );
1915         
1916                                 char* m = buffer_release( msg );
1917                                 osrfAppSessionStatus(
1918                                         ctx->session,
1919                                         OSRF_STATUS_INTERNALSERVERERROR,
1920                                         "osrfMethodException",
1921                                         ctx->request,
1922                                         m
1923                                 );
1924         
1925                                 free( m );
1926                                 return 0;
1927                         } else {
1928
1929                 int ownerid = atoi( oilsFMGetStringConst( param, owning_user_field ) );
1930
1931                 // Allow the owner to do whatever
1932                 if (ownerid == userid)
1933                     OK = 1;
1934
1935                 i = 0;
1936                     while( !OK && (perm = osrfStringArrayGetString(permission, i++)) ) {
1937                         dbi_result result;
1938
1939                                         osrfLogDebug(
1940                                                 OSRF_LOG_MARK,
1941                                                 "Checking object permission [%s] for user %d "
1942                                                                 "on object %s (class %s)",
1943                                                 perm,
1944                                                 userid,
1945                                                 pkey_value,
1946                                                 osrfHashGet( class, "classname" )
1947                                         );
1948         
1949                                         result = dbi_conn_queryf(
1950                                                 writehandle,
1951                                                 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s') AS has_perm;",
1952                                                 userid,
1953                                                 perm,
1954                                                 osrfHashGet( class, "classname" ),
1955                                                 pkey_value
1956                                         );
1957         
1958                                         if( result ) {
1959                                                 osrfLogDebug(
1960                                                         OSRF_LOG_MARK,
1961                                                         "Received a result for object permission [%s] "
1962                                                                         "for user %d on object %s (class %s)",
1963                                                         perm,
1964                                                         userid,
1965                                                         pkey_value,
1966                                                         osrfHashGet( class, "classname" )
1967                                                 );
1968         
1969                                                 if( dbi_result_first_row( result )) {
1970                                                         jsonObject* return_val = oilsMakeJSONFromResult( result );
1971                                                         const char* has_perm = jsonObjectGetString(
1972                                                                         jsonObjectGetKeyConst( return_val, "has_perm" ));
1973         
1974                                                         osrfLogDebug(
1975                                                                 OSRF_LOG_MARK,
1976                                                                 "Status of object permission [%s] for user %d "
1977                                                                                 "on object %s (class %s) is %s",
1978                                                                 perm,
1979                                                                 userid,
1980                                                                 pkey_value,
1981                                                                 osrfHashGet(class, "classname"),
1982                                                                 has_perm
1983                                                         );
1984         
1985                                                         if( *has_perm == 't' )
1986                                                                 OK = 1;
1987                                                         jsonObjectFree( return_val );
1988                                                 }
1989         
1990                                                 dbi_result_free( result );
1991                                                 if( OK )
1992                             break;
1993                                         } else {
1994                                                 const char* msg;
1995                                                 int errnum = dbi_conn_error( writehandle, &msg );
1996                                                 osrfLogWarning( OSRF_LOG_MARK,
1997                                                         "Unable to call check object permissions: %d, %s",
1998                                                         errnum, msg ? msg : "(No description available)" );
1999                                                 if( !oilsIsDBConnected( writehandle ))
2000                                                         osrfAppSessionPanic( ctx->session );
2001                                         }
2002                                 }
2003             }
2004         }
2005     }
2006
2007         // For every combination of permission and context org unit: call a stored procedure
2008         // to determine if the user has this permission in the context of this org unit.
2009         // If the answer is yes at any point, then we're done, and the user has permission.
2010         // In other words permissions are additive.
2011         i = 0;
2012         while( !OK && (perm = osrfStringArrayGetString(permission, i++)) ) {
2013                 dbi_result result;
2014
2015         osrfStringArray* pcache = NULL;
2016         if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
2017                         pcache = getPermLocationCache(ctx, perm);
2018
2019                         if (!pcache) {
2020                         pcache = osrfNewStringArray(0);
2021         
2022                                 result = dbi_conn_queryf(
2023                                         writehandle,
2024                                         "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
2025                                         userid,
2026                                         perm
2027                                 );
2028                 
2029                                 if( result ) {
2030                                         osrfLogDebug(
2031                                                 OSRF_LOG_MARK,
2032                                                 "Received a result for permission [%s] for user %d",
2033                                                 perm,
2034                                                 userid
2035                                         );
2036                 
2037                                         if( dbi_result_first_row( result )) {
2038                             do {
2039                                                 jsonObject* return_val = oilsMakeJSONFromResult( result );
2040                                                 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
2041                                 jsonObjectFree( return_val );
2042                                             } while( dbi_result_next_row( result ));
2043
2044                                                 setPermLocationCache(ctx, perm, pcache);
2045                                         }
2046                 
2047                                         dbi_result_free( result );
2048                     }
2049                         }
2050         }
2051
2052                 int j = 0;
2053                 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
2054
2055             if (rs_size > perm_at_threshold) {
2056                 if (osrfStringArrayContains( pcache, context_org )) {
2057                     OK = 1;
2058                     break;
2059                 }
2060             }
2061
2062                         if(
2063                 pkey_value &&
2064                 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) && // Always honor
2065                 (
2066                     !str_is_true( osrfHashGet(pcrud, "global_required") ) ||
2067                     osrfHashGet(pcrud, "owning_user") 
2068                 )
2069             ) {
2070                                 osrfLogDebug(
2071                                         OSRF_LOG_MARK,
2072                                         "Checking object permission [%s] for user %d "
2073                                                         "on object %s (class %s) at org %d",
2074                                         perm,
2075                                         userid,
2076                                         pkey_value,
2077                                         osrfHashGet( class, "classname" ),
2078                                         atoi( context_org )
2079                                 );
2080
2081                                 result = dbi_conn_queryf(
2082                                         writehandle,
2083                                         "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
2084                                         userid,
2085                                         perm,
2086                                         osrfHashGet( class, "classname" ),
2087                                         pkey_value,
2088                                         atoi( context_org )
2089                                 );
2090
2091                                 if( result ) {
2092                                         osrfLogDebug(
2093                                                 OSRF_LOG_MARK,
2094                                                 "Received a result for object permission [%s] "
2095                                                                 "for user %d on object %s (class %s) at org %d",
2096                                                 perm,
2097                                                 userid,
2098                                                 pkey_value,
2099                                                 osrfHashGet( class, "classname" ),
2100                                                 atoi( context_org )
2101                                         );
2102
2103                                         if( dbi_result_first_row( result )) {
2104                                                 jsonObject* return_val = oilsMakeJSONFromResult( result );
2105                                                 const char* has_perm = jsonObjectGetString(
2106                                                                 jsonObjectGetKeyConst( return_val, "has_perm" ));
2107
2108                                                 osrfLogDebug(
2109                                                         OSRF_LOG_MARK,
2110                                                         "Status of object permission [%s] for user %d "
2111                                                                         "on object %s (class %s) at org %d is %s",
2112                                                         perm,
2113                                                         userid,
2114                                                         pkey_value,
2115                                                         osrfHashGet(class, "classname"),
2116                                                         atoi(context_org),
2117                                                         has_perm
2118                                                 );
2119
2120                                                 if( *has_perm == 't' )
2121                                                         OK = 1;
2122                                                 jsonObjectFree( return_val );
2123                                         }
2124
2125                                         dbi_result_free( result );
2126                                         if( OK )
2127                                                 break;
2128                                 } else {
2129                                         const char* msg;
2130                                         int errnum = dbi_conn_error( writehandle, &msg );
2131                                         osrfLogWarning( OSRF_LOG_MARK,
2132                                                 "Unable to call check object permissions: %d, %s",
2133                                                 errnum, msg ? msg : "(No description available)" );
2134                                         if( !oilsIsDBConnected( writehandle ))
2135                                                 osrfAppSessionPanic( ctx->session );
2136                                 }
2137                         }
2138
2139             if (rs_size > perm_at_threshold) break;
2140
2141                         osrfLogDebug( OSRF_LOG_MARK,
2142                                         "Checking non-object permission [%s] for user %d at org %d",
2143                                         perm, userid, atoi(context_org) );
2144                         result = dbi_conn_queryf(
2145                                 writehandle,
2146                                 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
2147                                 userid,
2148                                 perm,
2149                                 atoi( context_org )
2150                         );
2151
2152                         if( result ) {
2153                                 osrfLogDebug( OSRF_LOG_MARK,
2154                                         "Received a result for permission [%s] for user %d at org %d",
2155                                         perm, userid, atoi( context_org ));
2156                                 if( dbi_result_first_row( result )) {
2157                                         jsonObject* return_val = oilsMakeJSONFromResult( result );
2158                                         const char* has_perm = jsonObjectGetString(
2159                                                 jsonObjectGetKeyConst( return_val, "has_perm" ));
2160                                         osrfLogDebug( OSRF_LOG_MARK,
2161                                                 "Status of permission [%s] for user %d at org %d is [%s]",
2162                                                 perm, userid, atoi( context_org ), has_perm );
2163                                         if( *has_perm == 't' )
2164                                                 OK = 1;
2165                                         jsonObjectFree( return_val );
2166                                 }
2167
2168                                 dbi_result_free( result );
2169                                 if( OK )
2170                                         break;
2171                         } else {
2172                                 const char* msg;
2173                                 int errnum = dbi_conn_error( writehandle, &msg );
2174                                 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
2175                                         errnum, msg ? msg : "(No description available)" );
2176                                 if( !oilsIsDBConnected( writehandle ))
2177                                         osrfAppSessionPanic( ctx->session );
2178                         }
2179
2180                 }
2181
2182                 if( OK )
2183                         break;
2184         }
2185
2186         osrfStringArrayFree( context_org_array );
2187
2188         return OK;
2189 }
2190
2191 /**
2192         @brief Look up the root of the org_unit tree.
2193         @param ctx Pointer to the method context.
2194         @return The id of the root org unit, as a character string.
2195
2196         Query actor.org_unit where parent_ou is null, and return the id as a string.
2197
2198         This function assumes that there is only one root org unit, i.e. that we
2199         have a single tree, not a forest.
2200
2201         The calling code is responsible for freeing the returned string.
2202 */
2203 static const char* org_tree_root( osrfMethodContext* ctx ) {
2204
2205         static char cached_root_id[ 32 ] = "";  // extravagantly large buffer
2206         static time_t last_lookup_time = 0;
2207         time_t current_time = time( NULL );
2208
2209         if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
2210                 // We successfully looked this up less than an hour ago.
2211                 // It's not likely to have changed since then.
2212                 return strdup( cached_root_id );
2213         }
2214         last_lookup_time = current_time;
2215
2216         int err = 0;
2217         jsonObject* where_clause = single_hash( "parent_ou", NULL );
2218         jsonObject* result = doFieldmapperSearch(
2219                 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
2220         jsonObjectFree( where_clause );
2221
2222         jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
2223
2224         if( !tree_top ) {
2225                 jsonObjectFree( result );
2226
2227                 growing_buffer* msg = buffer_init( 128 );
2228                 OSRF_BUFFER_ADD( msg, modulename );
2229                 OSRF_BUFFER_ADD( msg,
2230                                 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2231
2232                 char* m = buffer_release( msg );
2233                 osrfAppSessionStatus( ctx->session,
2234                                 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2235                 free( m );
2236
2237                 cached_root_id[ 0 ] = '\0';
2238                 return NULL;
2239         }
2240
2241         const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2242         osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2243
2244         strcpy( cached_root_id, root_org_unit_id );
2245         jsonObjectFree( result );
2246         return cached_root_id;
2247 }
2248
2249 /**
2250         @brief Create a JSON_HASH with a single key/value pair.
2251         @param key The key of the key/value pair.
2252         @param value the value of the key/value pair.
2253         @return Pointer to a newly created jsonObject of type JSON_HASH.
2254
2255         The value of the key/value is either a string or (if @a value is NULL) a null.
2256 */
2257 static jsonObject* single_hash( const char* key, const char* value ) {
2258         // Sanity check
2259         if( ! key ) key = "";
2260
2261         jsonObject* hash = jsonNewObjectType( JSON_HASH );
2262         jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2263         return hash;
2264 }
2265
2266
2267 int doCreate( osrfMethodContext* ctx ) {
2268         if(osrfMethodVerifyContext( ctx )) {
2269                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2270                 return -1;
2271         }
2272
2273         if( enforce_pcrud )
2274                 timeout_needs_resetting = 1;
2275
2276         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2277         jsonObject* target = NULL;
2278         jsonObject* options = NULL;
2279
2280         if( enforce_pcrud ) {
2281                 target = jsonObjectGetIndex( ctx->params, 1 );
2282                 options = jsonObjectGetIndex( ctx->params, 2 );
2283         } else {
2284                 target = jsonObjectGetIndex( ctx->params, 0 );
2285                 options = jsonObjectGetIndex( ctx->params, 1 );
2286         }
2287
2288         if( !verifyObjectClass( ctx, target )) {
2289                 osrfAppRespondComplete( ctx, NULL );
2290                 return -1;
2291         }
2292
2293         osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2294
2295         const char* trans_id = getXactId( ctx );
2296         if( !trans_id ) {
2297                 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2298
2299                 osrfAppSessionStatus(
2300                         ctx->session,
2301                         OSRF_STATUS_BADREQUEST,
2302                         "osrfMethodException",
2303                         ctx->request,
2304                         "No active transaction -- required for CREATE"
2305                 );
2306                 osrfAppRespondComplete( ctx, NULL );
2307                 return -1;
2308         }
2309
2310         // The following test is harmless but redundant.  If a class is
2311         // readonly, we don't register a create method for it.
2312         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2313                 osrfAppSessionStatus(
2314                         ctx->session,
2315                         OSRF_STATUS_BADREQUEST,
2316                         "osrfMethodException",
2317                         ctx->request,
2318                         "Cannot INSERT readonly class"
2319                 );
2320                 osrfAppRespondComplete( ctx, NULL );
2321                 return -1;
2322         }
2323
2324         // Set the last_xact_id
2325         int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2326         if( index > -1 ) {
2327                 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2328                         trans_id, target->classname, index);
2329                 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2330         }
2331
2332         osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2333
2334         dbhandle = writehandle;
2335
2336         osrfHash* fields = osrfHashGet( meta, "fields" );
2337         char* pkey       = osrfHashGet( meta, "primarykey" );
2338         char* seq        = osrfHashGet( meta, "sequence" );
2339
2340         growing_buffer* table_buf = buffer_init( 128 );
2341         growing_buffer* col_buf   = buffer_init( 128 );
2342         growing_buffer* val_buf   = buffer_init( 128 );
2343
2344         OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2345         OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2346         OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2347         buffer_add( val_buf,"VALUES (" );
2348
2349
2350         int first = 1;
2351         osrfHash* field = NULL;
2352         osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2353         while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2354
2355                 const char* field_name = osrfHashIteratorKey( field_itr );
2356
2357                 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2358                         continue;
2359
2360                 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2361
2362                 char* value;
2363                 if( field_object && field_object->classname ) {
2364                         value = oilsFMGetString(
2365                                 field_object,
2366                                 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2367                         );
2368                 } else if( field_object && JSON_BOOL == field_object->type ) {
2369                         if( jsonBoolIsTrue( field_object ) )
2370                                 value = strdup( "t" );
2371                         else
2372                                 value = strdup( "f" );
2373                 } else {
2374                         value = jsonObjectToSimpleString( field_object );
2375                 }
2376
2377                 if( first ) {
2378                         first = 0;
2379                 } else {
2380                         OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2381                         OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2382                 }
2383
2384                 buffer_add( col_buf, field_name );
2385
2386                 if( !field_object || field_object->type == JSON_NULL ) {
2387                         buffer_add( val_buf, "DEFAULT" );
2388
2389                 } else if( !strcmp( get_primitive( field ), "number" )) {
2390                         const char* numtype = get_datatype( field );
2391                         if( !strcmp( numtype, "INT8" )) {
2392                                 buffer_fadd( val_buf, "%lld", atoll( value ));
2393
2394                         } else if( !strcmp( numtype, "INT" )) {
2395                                 buffer_fadd( val_buf, "%d", atoi( value ));
2396
2397                         } else if( !strcmp( numtype, "NUMERIC" )) {
2398                                 buffer_fadd( val_buf, "%f", atof( value ));
2399                         }
2400                 } else {
2401                         if( dbi_conn_quote_string( writehandle, &value )) {
2402                                 OSRF_BUFFER_ADD( val_buf, value );
2403
2404                         } else {
2405                                 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2406                                 osrfAppSessionStatus(
2407                                         ctx->session,
2408                                         OSRF_STATUS_INTERNALSERVERERROR,
2409                                         "osrfMethodException",
2410                                         ctx->request,
2411                                         "Error quoting string -- please see the error log for more details"
2412                                 );
2413                                 free( value );
2414                                 buffer_free( table_buf );
2415                                 buffer_free( col_buf );
2416                                 buffer_free( val_buf );
2417                                 osrfAppRespondComplete( ctx, NULL );
2418                                 return -1;
2419                         }
2420                 }
2421
2422                 free( value );
2423         }
2424
2425         osrfHashIteratorFree( field_itr );
2426
2427         OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2428         OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2429
2430         char* table_str = buffer_release( table_buf );
2431         char* col_str   = buffer_release( col_buf );
2432         char* val_str   = buffer_release( val_buf );
2433         growing_buffer* sql = buffer_init( 128 );
2434         buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2435         free( table_str );
2436         free( col_str );
2437         free( val_str );
2438
2439         char* query = buffer_release( sql );
2440
2441         osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2442
2443         jsonObject* obj = NULL;
2444         int rc = 0;
2445
2446         dbi_result result = dbi_conn_query( writehandle, query );
2447         if( !result ) {
2448                 obj = jsonNewObject( NULL );
2449                 const char* msg;
2450                 int errnum = dbi_conn_error( writehandle, &msg );
2451                 osrfLogError(
2452                         OSRF_LOG_MARK,
2453                         "%s ERROR inserting %s object using query [%s]: %d %s",
2454                         modulename,
2455                         osrfHashGet(meta, "fieldmapper"),
2456                         query,
2457                         errnum,
2458                         msg ? msg : "(No description available)"
2459                 );
2460                 osrfAppSessionStatus(
2461                         ctx->session,
2462                         OSRF_STATUS_INTERNALSERVERERROR,
2463                         "osrfMethodException",
2464                         ctx->request,
2465                         "INSERT error -- please see the error log for more details"
2466                 );
2467                 if( !oilsIsDBConnected( writehandle ))
2468                         osrfAppSessionPanic( ctx->session );
2469                 rc = -1;
2470         } else {
2471                 dbi_result_free( result );
2472
2473                 char* id = oilsFMGetString( target, pkey );
2474                 if( !id ) {
2475                         unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2476                         growing_buffer* _id = buffer_init( 10 );
2477                         buffer_fadd( _id, "%lld", new_id );
2478                         id = buffer_release( _id );
2479                 }
2480
2481                 // Find quietness specification, if present
2482                 const char* quiet_str = NULL;
2483                 if( options ) {
2484                         const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2485                         if( quiet_obj )
2486                                 quiet_str = jsonObjectGetString( quiet_obj );
2487                 }
2488
2489                 if( str_is_true( quiet_str )) {  // if quietness is specified
2490                         obj = jsonNewObject( id );
2491                 }
2492                 else {
2493
2494                         // Fetch the row that we just inserted, so that we can return it to the client
2495                         jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2496                         jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2497
2498                         int err = 0;
2499                         jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2500                         if( err )
2501                                 rc = -1;
2502                         else
2503                                 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2504
2505                         jsonObjectFree( list );
2506                         jsonObjectFree( where_clause );
2507                 }
2508
2509                 free( id );
2510         }
2511
2512         free( query );
2513         osrfAppRespondComplete( ctx, obj );
2514         jsonObjectFree( obj );
2515         return rc;
2516 }
2517
2518 /**
2519         @brief Implement the retrieve method.
2520         @param ctx Pointer to the method context.
2521         @param err Pointer through which to return an error code.
2522         @return If successful, a pointer to the result to be returned to the client;
2523         otherwise NULL.
2524
2525         From the method's class, fetch a row with a specified value in the primary key.  This
2526         method relies on the database design convention that a primary key consists of a single
2527         column.
2528
2529         Method parameters:
2530         - authkey (PCRUD only)
2531         - value of the primary key for the desired row, for building the WHERE clause
2532         - a JSON_HASH containing any other SQL clauses: select, join, etc.
2533
2534         Return to client: One row from the query.
2535 */
2536 int doRetrieve( osrfMethodContext* ctx ) {
2537         if(osrfMethodVerifyContext( ctx )) {
2538                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2539                 return -1;
2540         }
2541
2542         if( enforce_pcrud )
2543                 timeout_needs_resetting = 1;
2544
2545         int id_pos = 0;
2546         int order_pos = 1;
2547
2548         if( enforce_pcrud ) {
2549                 id_pos = 1;
2550                 order_pos = 2;
2551         }
2552
2553         // Get the class metadata
2554         osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2555
2556         // Get the value of the primary key, from a method parameter
2557         const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2558
2559         osrfLogDebug(
2560                 OSRF_LOG_MARK,
2561                 "%s retrieving %s object with primary key value of %s",
2562                 modulename,
2563                 osrfHashGet( class_def, "fieldmapper" ),
2564                 jsonObjectGetString( id_obj )
2565         );
2566
2567         // Build a WHERE clause based on the key value
2568         jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2569         jsonObjectSetKey(
2570                 where_clause,
2571                 osrfHashGet( class_def, "primarykey" ),  // name of key column
2572                 jsonObjectClone( id_obj )                // value of key column
2573         );
2574
2575         jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2576
2577         // Do the query
2578         int err = 0;
2579         jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2580
2581         jsonObjectFree( where_clause );
2582         if( err ) {
2583                 osrfAppRespondComplete( ctx, NULL );
2584                 return -1;
2585         }
2586
2587         jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2588         jsonObjectFree( list );
2589
2590         if( enforce_pcrud ) {
2591                 // no result, skip this entirely
2592                 if(NULL != obj && !verifyObjectPCRUD( ctx, class_def, obj, 1 )) {
2593                         jsonObjectFree( obj );
2594
2595                         growing_buffer* msg = buffer_init( 128 );
2596                         OSRF_BUFFER_ADD( msg, modulename );
2597                         OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2598
2599                         char* m = buffer_release( msg );
2600                         osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2601                                         ctx->request, m );
2602                         free( m );
2603
2604                         osrfAppRespondComplete( ctx, NULL );
2605                         return -1;
2606                 }
2607         }
2608
2609         // doFieldmapperSearch() now does the responding for us
2610         //osrfAppRespondComplete( ctx, obj );
2611         osrfAppRespondComplete( ctx, NULL );
2612
2613         jsonObjectFree( obj );
2614         return 0;
2615 }
2616
2617 /**
2618         @brief Translate a numeric value to a string representation for the database.
2619         @param field Pointer to the IDL field definition.
2620         @param value Pointer to a jsonObject holding the value of a field.
2621         @return Pointer to a newly allocated string.
2622
2623         The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2624         its contents are numeric.  A non-numeric string is likely to result in invalid SQL.
2625
2626         If the datatype of the receiving field is not numeric, wrap the value in quotes.
2627
2628         The calling code is responsible for freeing the resulting string by calling free().
2629 */
2630 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2631         growing_buffer* val_buf = buffer_init( 32 );
2632
2633         // If the value is a number and the DB field is numeric, no quotes needed
2634         if( value->type == JSON_NUMBER && !strcmp( get_primitive( field ), "number") ) {
2635                 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2636         } else {
2637                 // Presumably this was really intended to be a string, so quote it
2638                 char* str = jsonObjectToSimpleString( value );
2639                 if( dbi_conn_quote_string( dbhandle, &str )) {
2640                         OSRF_BUFFER_ADD( val_buf, str );
2641                         free( str );
2642                 } else {
2643                         osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2644                         free( str );
2645                         buffer_free( val_buf );
2646                         return NULL;
2647                 }
2648         }
2649
2650         return buffer_release( val_buf );
2651 }
2652
2653 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2654                 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2655         growing_buffer* sql_buf = buffer_init( 32 );
2656
2657         buffer_fadd(
2658                 sql_buf,
2659                 "\"%s\".%s ",
2660                 class_alias,
2661                 osrfHashGet( field, "name" )
2662         );
2663
2664         if( !op ) {
2665                 buffer_add( sql_buf, "IN (" );
2666         } else if( !strcasecmp( op,"not in" )) {
2667                 buffer_add( sql_buf, "NOT IN (" );
2668         } else {
2669                 buffer_add( sql_buf, "IN (" );
2670         }
2671
2672         if( node->type == JSON_HASH ) {
2673                 // subquery predicate
2674                 char* subpred = buildQuery( ctx, node, SUBSELECT );
2675                 if( ! subpred ) {
2676                         buffer_free( sql_buf );
2677                         return NULL;
2678                 }
2679
2680                 buffer_add( sql_buf, subpred );
2681                 free( subpred );
2682
2683         } else if( node->type == JSON_ARRAY ) {
2684                 // literal value list
2685                 int in_item_index = 0;
2686                 int in_item_first = 1;
2687                 const jsonObject* in_item;
2688                 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2689
2690                         if( in_item_first )
2691                                 in_item_first = 0;
2692                         else
2693                                 buffer_add( sql_buf, ", " );
2694
2695                         // Sanity check
2696                         if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2697                                 osrfLogError( OSRF_LOG_MARK,
2698                                                 "%s: Expected string or number within IN list; found %s",
2699                                                 modulename, json_type( in_item->type ) );
2700                                 buffer_free( sql_buf );
2701                                 return NULL;
2702                         }
2703
2704                         // Append the literal value -- quoted if not a number
2705                         if( JSON_NUMBER == in_item->type ) {
2706                                 char* val = jsonNumberToDBString( field, in_item );
2707                                 OSRF_BUFFER_ADD( sql_buf, val );
2708                                 free( val );
2709
2710                         } else if( !strcmp( get_primitive( field ), "number" )) {
2711                                 char* val = jsonNumberToDBString( field, in_item );
2712                                 OSRF_BUFFER_ADD( sql_buf, val );
2713                                 free( val );
2714
2715                         } else {
2716                                 char* key_string = jsonObjectToSimpleString( in_item );
2717                                 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2718                                         OSRF_BUFFER_ADD( sql_buf, key_string );
2719                                         free( key_string );
2720                                 } else {
2721                                         osrfLogError( OSRF_LOG_MARK,
2722                                                         "%s: Error quoting key string [%s]", modulename, key_string );
2723                                         free( key_string );
2724                                         buffer_free( sql_buf );
2725                                         return NULL;
2726                                 }
2727                         }
2728                 }
2729
2730                 if( in_item_first ) {
2731                         osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2732                         buffer_free( sql_buf );
2733                         return NULL;
2734                 }
2735         } else {
2736                 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2737                         modulename, json_type( node->type ));
2738                 buffer_free( sql_buf );
2739                 return NULL;
2740         }
2741
2742         OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2743
2744         return buffer_release( sql_buf );
2745 }
2746
2747 // Receive a JSON_ARRAY representing a function call.  The first
2748 // entry in the array is the function name.  The rest are parameters.
2749 static char* searchValueTransform( const jsonObject* array ) {
2750
2751         if( array->size < 1 ) {
2752                 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2753                 return NULL;
2754         }
2755
2756         // Get the function name
2757         jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2758         if( func_item->type != JSON_STRING ) {
2759                 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2760                         modulename, json_type( func_item->type ));
2761                 return NULL;
2762         }
2763
2764         growing_buffer* sql_buf = buffer_init( 32 );
2765
2766         OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2767         OSRF_BUFFER_ADD( sql_buf, "( " );
2768
2769         // Get the parameters
2770         int func_item_index = 1;   // We already grabbed the zeroth entry
2771         while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2772
2773                 // Add a separator comma, if we need one
2774                 if( func_item_index > 2 )
2775                         buffer_add( sql_buf, ", " );
2776
2777                 // Add the current parameter
2778                 if( func_item->type == JSON_NULL ) {
2779                         buffer_add( sql_buf, "NULL" );
2780                 } else {
2781                         if( func_item->type == JSON_BOOL ) {
2782                                 if( jsonBoolIsTrue(func_item) ) {
2783                                         buffer_add( sql_buf, "TRUE" );
2784                                 } else {
2785                                         buffer_add( sql_buf, "FALSE" );
2786                                 }
2787                         } else {
2788                                 char* val = jsonObjectToSimpleString( func_item );
2789                                 if( dbi_conn_quote_string( dbhandle, &val )) {
2790                                         OSRF_BUFFER_ADD( sql_buf, val );
2791                                         free( val );
2792                                 } else {
2793                                         osrfLogError( OSRF_LOG_MARK, 
2794                                                 "%s: Error quoting key string [%s]", modulename, val );
2795                                         buffer_free( sql_buf );
2796                                         free( val );
2797                                         return NULL;
2798                                 }
2799                         }
2800                 }
2801         }
2802
2803         buffer_add( sql_buf, " )" );
2804
2805         return buffer_release( sql_buf );
2806 }
2807
2808 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2809                 const jsonObject* node, const char* op ) {
2810
2811         if( ! is_good_operator( op ) ) {
2812                 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2813                 return NULL;
2814         }
2815
2816         char* val = searchValueTransform( node );
2817         if( !val )
2818                 return NULL;
2819
2820         const char* right_percent = "";
2821         const char* real_op       = op;
2822
2823         if( !strcasecmp( op, "startwith") ) {
2824                 real_op = "like";
2825                 right_percent = "|| '%'";
2826         }
2827
2828         growing_buffer* sql_buf = buffer_init( 32 );
2829         buffer_fadd(
2830                 sql_buf,
2831                 "\"%s\".%s %s %s%s",
2832                 class_alias,
2833                 osrfHashGet( field, "name" ),
2834                 real_op,
2835                 val,
2836                 right_percent
2837         );
2838
2839         free( val );
2840
2841         return buffer_release( sql_buf );
2842 }
2843
2844 // class_alias is a class name or other table alias
2845 // field is a field definition as stored in the IDL
2846 // node comes from the method parameter, and may represent an entry in the SELECT list
2847 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2848                 const jsonObject* node ) {
2849         growing_buffer* sql_buf = buffer_init( 32 );
2850
2851         const char* field_transform = jsonObjectGetString(
2852                 jsonObjectGetKeyConst( node, "transform" ) );
2853         const char* transform_subcolumn = jsonObjectGetString(
2854                 jsonObjectGetKeyConst( node, "result_field" ) );
2855
2856         if( transform_subcolumn ) {
2857                 if( ! is_identifier( transform_subcolumn ) ) {
2858                         osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2859                                         modulename, transform_subcolumn );
2860                         buffer_free( sql_buf );
2861                         return NULL;
2862                 }
2863                 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' );    // enclose transform in parentheses
2864         }
2865
2866         if( field_transform ) {
2867
2868                 if( ! is_identifier( field_transform ) ) {
2869                         osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2870                                         modulename, field_transform );
2871                         buffer_free( sql_buf );
2872                         return NULL;
2873                 }
2874
2875                 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2876                         buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2877                                 field_transform, class_alias, osrfHashGet( field, "name" ));
2878                 } else {
2879                         buffer_fadd( sql_buf, "%s(\"%s\".%s",
2880                                 field_transform, class_alias, osrfHashGet( field, "name" ));
2881                 }
2882
2883                 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2884
2885                 if( array ) {
2886                         if( array->type != JSON_ARRAY ) {
2887                                 osrfLogError( OSRF_LOG_MARK,
2888                                         "%s: Expected JSON_ARRAY for function params; found %s",
2889                                         modulename, json_type( array->type ) );
2890                                 buffer_free( sql_buf );
2891                                 return NULL;
2892                         }
2893                         int func_item_index = 0;
2894                         jsonObject* func_item;
2895                         while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2896
2897                                 char* val = jsonObjectToSimpleString( func_item );
2898
2899                                 if( !val ) {
2900                                         buffer_add( sql_buf, ",NULL" );
2901                                 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2902                                         OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2903                                         OSRF_BUFFER_ADD( sql_buf, val );
2904                                 } else {
2905                                         osrfLogError( OSRF_LOG_MARK,
2906                                                         "%s: Error quoting key string [%s]", modulename, val );
2907                                         free( val );
2908                                         buffer_free( sql_buf );
2909                                         return NULL;
2910                                 }
2911                                 free( val );
2912                         }
2913                 }
2914
2915                 buffer_add( sql_buf, " )" );
2916
2917         } else {
2918                 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2919         }
2920
2921         if( transform_subcolumn )
2922                 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2923
2924         return buffer_release( sql_buf );
2925 }
2926
2927 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2928                 const jsonObject* node, const char* op ) {
2929
2930         if( ! is_good_operator( op ) ) {
2931                 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2932                 return NULL;
2933         }
2934
2935         char* field_transform = searchFieldTransform( class_info->alias, field, node );
2936         if( ! field_transform )
2937                 return NULL;
2938         char* value = NULL;
2939         int extra_parens = 0;   // boolean
2940
2941         const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2942         if( ! value_obj ) {
2943                 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2944                 if( !value ) {
2945                         osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2946                                 modulename );
2947                         free( field_transform );
2948                         return NULL;
2949                 }
2950                 extra_parens = 1;
2951         } else if( value_obj->type == JSON_ARRAY ) {
2952                 value = searchValueTransform( value_obj );
2953                 if( !value ) {
2954                         osrfLogError( OSRF_LOG_MARK,
2955                                 "%s: Error building value transform for field transform", modulename );
2956                         free( field_transform );
2957                         return NULL;
2958                 }
2959         } else if( value_obj->type == JSON_HASH ) {
2960                 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2961                 if( !value ) {
2962                         osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2963                                 modulename );
2964                         free( field_transform );
2965                         return NULL;
2966                 }
2967                 extra_parens = 1;
2968         } else if( value_obj->type == JSON_NUMBER ) {
2969                 value = jsonNumberToDBString( field, value_obj );
2970         } else if( value_obj->type == JSON_NULL ) {
2971                 osrfLogError( OSRF_LOG_MARK,
2972                         "%s: Error building predicate for field transform: null value", modulename );
2973                 free( field_transform );
2974                 return NULL;
2975         } else if( value_obj->type == JSON_BOOL ) {
2976                 osrfLogError( OSRF_LOG_MARK,
2977                         "%s: Error building predicate for field transform: boolean value", modulename );
2978                 free( field_transform );
2979                 return NULL;
2980         } else {
2981                 if( !strcmp( get_primitive( field ), "number") ) {
2982                         value = jsonNumberToDBString( field, value_obj );
2983                 } else {
2984                         value = jsonObjectToSimpleString( value_obj );
2985                         if( !dbi_conn_quote_string( dbhandle, &value )) {
2986                                 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2987                                         modulename, value );
2988                                 free( value );
2989                                 free( field_transform );
2990                                 return NULL;
2991                         }
2992                 }
2993         }
2994
2995         const char* left_parens  = "";
2996         const char* right_parens = "";
2997
2998         if( extra_parens ) {
2999                 left_parens  = "(";
3000                 right_parens = ")";
3001         }
3002
3003         const char* right_percent = "";
3004         const char* real_op       = op;
3005
3006         if( !strcasecmp( op, "startwith") ) {
3007                 real_op = "like";
3008                 right_percent = "|| '%'";
3009         }
3010
3011         growing_buffer* sql_buf = buffer_init( 32 );
3012
3013         buffer_fadd(
3014                 sql_buf,
3015                 "%s%s %s %s %s%s %s%s",
3016                 left_parens,
3017                 field_transform,
3018                 real_op,
3019                 left_parens,
3020                 value,
3021                 right_percent,
3022                 right_parens,
3023                 right_parens
3024         );
3025
3026         free( value );
3027         free( field_transform );
3028
3029         return buffer_release( sql_buf );
3030 }
3031
3032 static char* searchSimplePredicate( const char* op, const char* class_alias,
3033                 osrfHash* field, const jsonObject* node ) {
3034
3035         if( ! is_good_operator( op ) ) {
3036                 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
3037                 return NULL;
3038         }
3039
3040         char* val = NULL;
3041
3042         // Get the value to which we are comparing the specified column
3043         if( node->type != JSON_NULL ) {
3044                 if( node->type == JSON_NUMBER ) {
3045                         val = jsonNumberToDBString( field, node );
3046                 } else if( !strcmp( get_primitive( field ), "number" ) ) {
3047                         val = jsonNumberToDBString( field, node );
3048                 } else {
3049                         val = jsonObjectToSimpleString( node );
3050                 }
3051         }
3052
3053         if( val ) {
3054                 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
3055                         // Value is not numeric; enclose it in quotes
3056                         if( !dbi_conn_quote_string( dbhandle, &val ) ) {
3057                                 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
3058                                         modulename, val );
3059                                 free( val );
3060                                 return NULL;
3061                         }
3062                 }
3063         } else {
3064                 // Compare to a null value
3065                 val = strdup( "NULL" );
3066                 if( strcmp( op, "=" ))
3067                         op = "IS NOT";
3068                 else
3069                         op = "IS";
3070         }
3071
3072         const char* right_percent = "";
3073         const char* real_op       = op;
3074
3075         if( !strcasecmp( op, "startwith") ) {
3076                 real_op = "like";
3077                 right_percent = "|| '%'";
3078         }
3079
3080         growing_buffer* sql_buf = buffer_init( 32 );
3081         buffer_fadd( sql_buf, "\"%s\".%s %s %s%s", class_alias, osrfHashGet(field, "name"), real_op, val, right_percent );
3082         char* pred = buffer_release( sql_buf );
3083
3084         free( val );
3085
3086         return pred;
3087 }
3088
3089 static char* searchBETWEENPredicate( const char* class_alias,
3090                 osrfHash* field, const jsonObject* node ) {
3091
3092         const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
3093         const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
3094
3095         if( NULL == y_node ) {
3096                 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
3097                 return NULL;
3098         }
3099         else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
3100                 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
3101                 return NULL;
3102         }
3103
3104         char* x_string;
3105         char* y_string;
3106
3107         if( !strcmp( get_primitive( field ), "number") ) {
3108                 x_string = jsonNumberToDBString( field, x_node );
3109                 y_string = jsonNumberToDBString( field, y_node );
3110
3111         } else {
3112                 x_string = jsonObjectToSimpleString( x_node );
3113                 y_string = jsonObjectToSimpleString( y_node );
3114                 if( !(dbi_conn_quote_string( dbhandle, &x_string )
3115                         && dbi_conn_quote_string( dbhandle, &y_string )) ) {
3116                         osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
3117                                         modulename, x_string, y_string );
3118                         free( x_string );
3119                         free( y_string );
3120                         return NULL;
3121                 }
3122         }
3123
3124         growing_buffer* sql_buf = buffer_init( 32 );
3125         buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
3126                         class_alias, osrfHashGet( field, "name" ), x_string, y_string );
3127         free( x_string );
3128         free( y_string );
3129
3130         return buffer_release( sql_buf );
3131 }
3132
3133 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
3134                                                           jsonObject* node, osrfMethodContext* ctx ) {
3135
3136         char* pred = NULL;
3137         if( node->type == JSON_ARRAY ) { // equality IN search
3138                 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
3139         } else if( node->type == JSON_HASH ) { // other search
3140                 jsonIterator* pred_itr = jsonNewIterator( node );
3141                 if( !jsonIteratorHasNext( pred_itr ) ) {
3142                         osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
3143                                         modulename, osrfHashGet(field, "name" ));
3144                 } else {
3145                         jsonObject* pred_node = jsonIteratorNext( pred_itr );
3146
3147                         // Verify that there are no additional predicates
3148                         if( jsonIteratorHasNext( pred_itr ) ) {
3149                                 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
3150                                                 modulename, osrfHashGet(field, "name" ));
3151                         } else if( !(strcasecmp( pred_itr->key,"between" )) )
3152                                 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
3153                         else if( !(strcasecmp( pred_itr->key,"in" ))
3154                                         || !(strcasecmp( pred_itr->key,"not in" )) )
3155                                 pred = searchINPredicate(
3156                                         class_info->alias, field, pred_node, pred_itr->key, ctx );
3157                         else if( pred_node->type == JSON_ARRAY )
3158                                 pred = searchFunctionPredicate(
3159                                         class_info->alias, field, pred_node, pred_itr->key );
3160                         else if( pred_node->type == JSON_HASH )
3161                                 pred = searchFieldTransformPredicate(
3162                                         class_info, field, pred_node, pred_itr->key );
3163                         else
3164                                 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
3165                 }
3166                 jsonIteratorFree( pred_itr );
3167
3168         } else if( node->type == JSON_NULL ) { // IS NULL search
3169                 growing_buffer* _p = buffer_init( 64 );
3170                 buffer_fadd(
3171                         _p,
3172                         "\"%s\".%s IS NULL",
3173                         class_info->alias,
3174                         osrfHashGet( field, "name" )
3175                 );
3176                 pred = buffer_release( _p );
3177         } else { // equality search
3178                 pred = searchSimplePredicate( "=", class_info->alias, field, node );
3179         }
3180
3181         return pred;
3182
3183 }
3184
3185
3186 /*
3187
3188 join : {
3189         acn : {
3190                 field : record,
3191                 fkey : id
3192                 type : left
3193                 filter_op : or
3194                 filter : { ... },
3195                 join : {
3196                         acp : {
3197                                 field : call_number,
3198                                 fkey : id,
3199                                 filter : { ... },
3200                         },
3201                 },
3202         },
3203         mrd : {
3204                 field : record,
3205                 type : inner
3206                 fkey : id,
3207                 filter : { ... },
3208         }
3209 }
3210
3211 */
3212
3213 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3214
3215         const jsonObject* working_hash;
3216         jsonObject* freeable_hash = NULL;
3217
3218         if( join_hash->type == JSON_HASH ) {
3219                 working_hash = join_hash;
3220         } else if( join_hash->type == JSON_STRING ) {
3221                 // turn it into a JSON_HASH by creating a wrapper
3222                 // around a copy of the original
3223                 const char* _tmp = jsonObjectGetString( join_hash );
3224                 freeable_hash = jsonNewObjectType( JSON_HASH );
3225                 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3226                 working_hash = freeable_hash;
3227         } else {
3228                 osrfLogError(
3229                         OSRF_LOG_MARK,
3230                         "%s: JOIN failed; expected JSON object type not found",
3231                         modulename
3232                 );
3233                 return NULL;
3234         }
3235
3236         growing_buffer* join_buf = buffer_init( 128 );
3237         const char* leftclass = left_info->class_name;
3238
3239         jsonObject* snode = NULL;
3240         jsonIterator* search_itr = jsonNewIterator( working_hash );
3241
3242         while ( (snode = jsonIteratorNext( search_itr )) ) {
3243                 const char* right_alias = search_itr->key;
3244                 const char* class =
3245                                 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3246                 if( ! class )
3247                         class = right_alias;
3248
3249                 const ClassInfo* right_info = add_joined_class( right_alias, class );
3250                 if( !right_info ) {
3251                         osrfLogError(
3252                                 OSRF_LOG_MARK,
3253                                 "%s: JOIN failed.  Class \"%s\" not resolved in IDL",
3254                                 modulename,
3255                                 search_itr->key
3256                         );
3257                         jsonIteratorFree( search_itr );
3258                         buffer_free( join_buf );
3259                         if( freeable_hash )
3260                                 jsonObjectFree( freeable_hash );
3261                         return NULL;
3262                 }
3263                 osrfHash* links    = right_info->links;
3264                 const char* table  = right_info->source_def;
3265
3266                 const char* fkey  = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3267                 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3268
3269                 if( field && !fkey ) {
3270                         // Look up the corresponding join column in the IDL.
3271                         // The link must be defined in the child table,
3272                         // and point to the right parent table.
3273                         osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3274                         const char* reltype = NULL;
3275                         const char* other_class = NULL;
3276                         reltype = osrfHashGet( idl_link, "reltype" );
3277                         if( reltype && strcmp( reltype, "has_many" ) )
3278                                 other_class = osrfHashGet( idl_link, "class" );
3279                         if( other_class && !strcmp( other_class, leftclass ) )
3280                                 fkey = osrfHashGet( idl_link, "key" );
3281                         if( !fkey ) {
3282                                 osrfLogError(
3283                                         OSRF_LOG_MARK,
3284                                         "%s: JOIN failed.  No link defined from %s.%s to %s",
3285                                         modulename,
3286                                         class,
3287                                         field,
3288                                         leftclass
3289                                 );
3290                                 buffer_free( join_buf );
3291                                 if( freeable_hash )
3292                                         jsonObjectFree( freeable_hash );
3293                                 jsonIteratorFree( search_itr );
3294                                 return NULL;
3295                         }
3296
3297                 } else if( !field && fkey ) {
3298                         // Look up the corresponding join column in the IDL.
3299                         // The link must be defined in the child table,
3300                         // and point to the right parent table.
3301                         osrfHash* left_links = left_info->links;
3302                         osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3303                         const char* reltype = NULL;
3304                         const char* other_class = NULL;
3305                         reltype = osrfHashGet( idl_link, "reltype" );
3306                         if( reltype && strcmp( reltype, "has_many" ) )
3307                                 other_class = osrfHashGet( idl_link, "class" );
3308                         if( other_class && !strcmp( other_class, class ) )
3309                                 field = osrfHashGet( idl_link, "key" );
3310                         if( !field ) {
3311                                 osrfLogError(
3312                                         OSRF_LOG_MARK,
3313                                         "%s: JOIN failed.  No link defined from %s.%s to %s",
3314                                         modulename,
3315                                         leftclass,
3316                                         fkey,
3317                                         class
3318                                 );
3319                                 buffer_free( join_buf );
3320                                 if( freeable_hash )
3321                                         jsonObjectFree( freeable_hash );
3322                                 jsonIteratorFree( search_itr );
3323                                 return NULL;
3324                         }
3325
3326                 } else if( !field && !fkey ) {
3327                         osrfHash* left_links = left_info->links;
3328
3329                         // For each link defined for the left class:
3330                         // see if the link references the joined class
3331                         osrfHashIterator* itr = osrfNewHashIterator( left_links );
3332                         osrfHash* curr_link = NULL;
3333                         while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3334                                 const char* other_class = osrfHashGet( curr_link, "class" );
3335                                 if( other_class && !strcmp( other_class, class ) ) {
3336
3337                                         // In the IDL, the parent class doesn't always know then names of the child
3338                                         // columns that are pointing to it, so don't use that end of the link
3339                                         const char* reltype = osrfHashGet( curr_link, "reltype" );
3340                                         if( reltype && strcmp( reltype, "has_many" ) ) {
3341                                                 // Found a link between the classes
3342                                                 fkey = osrfHashIteratorKey( itr );
3343                                                 field = osrfHashGet( curr_link, "key" );
3344                                                 break;
3345                                         }
3346                                 }
3347                         }
3348                         osrfHashIteratorFree( itr );
3349
3350                         if( !field || !fkey ) {
3351                                 // Do another such search, with the classes reversed
3352
3353                                 // For each link defined for the joined class:
3354                                 // see if the link references the left class
3355                                 osrfHashIterator* itr = osrfNewHashIterator( links );
3356                                 osrfHash* curr_link = NULL;
3357                                 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3358                                         const char* other_class = osrfHashGet( curr_link, "class" );
3359                                         if( other_class && !strcmp( other_class, leftclass ) ) {
3360
3361                                                 // In the IDL, the parent class doesn't know then names of the child
3362                                                 // columns that are pointing to it, so don't use that end of the link
3363                                                 const char* reltype = osrfHashGet( curr_link, "reltype" );
3364                                                 if( reltype && strcmp( reltype, "has_many" ) ) {
3365                                                         // Found a link between the classes
3366                                                         field = osrfHashIteratorKey( itr );
3367                                                         fkey = osrfHashGet( curr_link, "key" );
3368                                                         break;
3369                                                 }
3370                                         }
3371                                 }
3372                                 osrfHashIteratorFree( itr );
3373                         }
3374
3375                         if( !field || !fkey ) {
3376                                 osrfLogError(
3377                                         OSRF_LOG_MARK,
3378                                         "%s: JOIN failed.  No link defined between %s and %s",
3379                                         modulename,
3380                                         leftclass,
3381                                         class
3382                                 );
3383                                 buffer_free( join_buf );
3384                                 if( freeable_hash )
3385                                         jsonObjectFree( freeable_hash );
3386                                 jsonIteratorFree( search_itr );
3387                                 return NULL;
3388                         }
3389                 }
3390
3391                 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3392                 if( type ) {
3393                         if( !strcasecmp( type,"left" )) {
3394                                 buffer_add( join_buf, " LEFT JOIN" );
3395                         } else if( !strcasecmp( type,"right" )) {
3396                                 buffer_add( join_buf, " RIGHT JOIN" );
3397                         } else if( !strcasecmp( type,"full" )) {
3398                                 buffer_add( join_buf, " FULL JOIN" );
3399                         } else {
3400                                 buffer_add( join_buf, " INNER JOIN" );
3401                         }
3402                 } else {
3403                         buffer_add( join_buf, " INNER JOIN" );
3404                 }
3405
3406                 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3407                                         table, right_alias, right_alias, field, left_info->alias, fkey );
3408
3409                 // Add any other join conditions as specified by "filter"
3410                 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3411                 if( filter ) {
3412                         const char* filter_op = jsonObjectGetString(
3413                                 jsonObjectGetKeyConst( snode, "filter_op" ) );
3414                         if( filter_op && !strcasecmp( "or",filter_op )) {
3415                                 buffer_add( join_buf, " OR " );
3416                         } else {
3417                                 buffer_add( join_buf, " AND " );
3418                         }
3419
3420                         char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3421                         if( jpred ) {
3422                                 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3423                                 OSRF_BUFFER_ADD( join_buf, jpred );
3424                                 free( jpred );
3425                         } else {
3426                                 osrfLogError(
3427                                         OSRF_LOG_MARK,
3428                                         "%s: JOIN failed.  Invalid conditional expression.",
3429                                         modulename
3430                                 );
3431                                 jsonIteratorFree( search_itr );
3432                                 buffer_free( join_buf );
3433                                 if( freeable_hash )
3434                                         jsonObjectFree( freeable_hash );
3435                                 return NULL;
3436                         }
3437                 }
3438
3439                 buffer_add( join_buf, " ) " );
3440
3441                 // Recursively add a nested join, if one is present
3442                 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3443                 if( join_filter ) {
3444                         char* jpred = searchJOIN( join_filter, right_info );
3445                         if( jpred ) {
3446                                 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3447                                 OSRF_BUFFER_ADD( join_buf, jpred );
3448                                 free( jpred );
3449                         } else {
3450                                 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3451                                 jsonIteratorFree( search_itr );
3452                                 buffer_free( join_buf );
3453                                 if( freeable_hash )
3454                                         jsonObjectFree( freeable_hash );
3455                                 return NULL;
3456                         }
3457                 }
3458         }
3459
3460         if( freeable_hash )
3461                 jsonObjectFree( freeable_hash );
3462         jsonIteratorFree( search_itr );
3463
3464         return buffer_release( join_buf );
3465 }
3466
3467 /*
3468
3469 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3470 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3471 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3472
3473 Generate code to express a set of conditions, as for a WHERE clause.  Parameters:
3474
3475 search_hash is the JSON expression of the conditions.
3476 meta is the class definition from the IDL, for the relevant table.
3477 opjoin_type indicates whether multiple conditions, if present, should be
3478         connected by AND or OR.
3479 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3480         to pass it to other functions -- and all they do with it is to use the session
3481         and request members to send error messages back to the client.
3482
3483 */
3484
3485 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3486                 int opjoin_type, osrfMethodContext* ctx ) {
3487
3488         osrfLogDebug(
3489                 OSRF_LOG_MARK,
3490                 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3491                 "opjoin_type = %d, ctx addr = %p",
3492                 modulename,
3493                 search_hash,
3494                 class_info->class_def,
3495                 opjoin_type,
3496                 ctx
3497         );
3498
3499         growing_buffer* sql_buf = buffer_init( 128 );
3500
3501         jsonObject* node = NULL;
3502
3503         int first = 1;
3504         if( search_hash->type == JSON_ARRAY ) {
3505                 if( 0 == search_hash->size ) {
3506                         osrfLogError(
3507                                 OSRF_LOG_MARK,
3508                                 "%s: Invalid predicate structure: empty JSON array",
3509                                 modulename
3510                         );
3511                         buffer_free( sql_buf );
3512                         return NULL;
3513                 }
3514
3515                 unsigned long i = 0;
3516                 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3517                         if( first ) {
3518                                 first = 0;
3519                         } else {
3520                                 if( opjoin_type == OR_OP_JOIN )
3521                                         buffer_add( sql_buf, " OR " );
3522                                 else
3523                                         buffer_add( sql_buf, " AND " );
3524                         }
3525
3526                         char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3527                         if( ! subpred ) {
3528                                 buffer_free( sql_buf );
3529                                 return NULL;
3530                         }
3531
3532                         buffer_fadd( sql_buf, "( %s )", subpred );
3533                         free( subpred );
3534                 }
3535
3536         } else if( search_hash->type == JSON_HASH ) {
3537                 osrfLogDebug( OSRF_LOG_MARK,
3538                         "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3539                 jsonIterator* search_itr = jsonNewIterator( search_hash );
3540                 if( !jsonIteratorHasNext( search_itr ) ) {
3541                         osrfLogError(
3542                                 OSRF_LOG_MARK,
3543                                 "%s: Invalid predicate structure: empty JSON object",
3544                                 modulename
3545                         );
3546                         jsonIteratorFree( search_itr );
3547                         buffer_free( sql_buf );
3548                         return NULL;
3549                 }
3550
3551                 while( (node = jsonIteratorNext( search_itr )) ) {
3552
3553                         if( first ) {
3554                                 first = 0;
3555                         } else {
3556                                 if( opjoin_type == OR_OP_JOIN )
3557                                         buffer_add( sql_buf, " OR " );
3558                                 else
3559                                         buffer_add( sql_buf, " AND " );
3560                         }
3561
3562                         if( '+' == search_itr->key[ 0 ] ) {
3563
3564                                 // This plus sign prefixes a class name or other table alias;
3565                                 // make sure the table alias is in scope
3566                                 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3567                                 if( ! alias_info ) {
3568                                         osrfLogError(
3569                                                          OSRF_LOG_MARK,
3570                                                         "%s: Invalid table alias \"%s\" in WHERE clause",
3571                                                         modulename,
3572                                                         search_itr->key + 1
3573                                         );
3574                                         jsonIteratorFree( search_itr );
3575                                         buffer_free( sql_buf );
3576                                         return NULL;
3577                                 }
3578
3579                                 if( node->type == JSON_STRING ) {
3580                                         // It's the name of a column; make sure it belongs to the class
3581                                         const char* fieldname = jsonObjectGetString( node );
3582                                         if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3583                                                 osrfLogError(
3584                                                         OSRF_LOG_MARK,
3585                                                         "%s: Invalid column name \"%s\" in WHERE clause "
3586                                                         "for table alias \"%s\"",
3587                                                         modulename,
3588                                                         fieldname,
3589                                                         alias_info->alias
3590                                                 );
3591                                                 jsonIteratorFree( search_itr );
3592                                                 buffer_free( sql_buf );
3593                                                 return NULL;
3594                                         }
3595
3596                                         buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3597                                 } else {
3598                                         // It's something more complicated
3599                                         char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3600                                         if( ! subpred ) {
3601                                                 jsonIteratorFree( search_itr );
3602                                                 buffer_free( sql_buf );
3603                                                 return NULL;
3604                                         }
3605
3606                                         buffer_fadd( sql_buf, "( %s )", subpred );
3607                                         free( subpred );
3608                                 }
3609                         } else if( '-' == search_itr->key[ 0 ] ) {
3610                                 if( !strcasecmp( "-or", search_itr->key )) {
3611                                         char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3612                                         if( ! subpred ) {
3613                                                 jsonIteratorFree( search_itr );
3614                                                 buffer_free( sql_buf );
3615                                                 return NULL;
3616                                         }
3617
3618                                         buffer_fadd( sql_buf, "( %s )", subpred );
3619                                         free( subpred );
3620                                 } else if( !strcasecmp( "-and", search_itr->key )) {
3621                                         char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3622                                         if( ! subpred ) {
3623                                                 jsonIteratorFree( search_itr );
3624                                                 buffer_free( sql_buf );
3625                                                 return NULL;
3626                                         }
3627
3628                                         buffer_fadd( sql_buf, "( %s )", subpred );
3629                                         free( subpred );
3630                                 } else if( !strcasecmp("-not",search_itr->key) ) {
3631                                         char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3632                                         if( ! subpred ) {
3633                                                 jsonIteratorFree( search_itr );
3634                                                 buffer_free( sql_buf );
3635                                                 return NULL;
3636                                         }
3637
3638                                         buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3639                                         free( subpred );
3640                                 } else if( !strcasecmp( "-exists", search_itr->key )) {
3641                                         char* subpred = buildQuery( ctx, node, SUBSELECT );
3642                                         if( ! subpred ) {
3643                                                 jsonIteratorFree( search_itr );
3644                                                 buffer_free( sql_buf );
3645                                                 return NULL;
3646                                         }
3647
3648                                         buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3649                                         free( subpred );
3650                                 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3651                                         char* subpred = buildQuery( ctx, node, SUBSELECT );
3652                                         if( ! subpred ) {
3653                                                 jsonIteratorFree( search_itr );
3654                                                 buffer_free( sql_buf );
3655                                                 return NULL;
3656                                         }
3657
3658                                         buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3659                                         free( subpred );
3660                                 } else {     // Invalid "minus" operator
3661                                         osrfLogError(
3662                                                          OSRF_LOG_MARK,
3663                                                         "%s: Invalid operator \"%s\" in WHERE clause",
3664                                                         modulename,
3665                                                         search_itr->key
3666                                         );
3667                                         jsonIteratorFree( search_itr );
3668                                         buffer_free( sql_buf );
3669                                         return NULL;
3670                                 }
3671
3672                         } else {
3673
3674                                 const char* class = class_info->class_name;
3675                                 osrfHash* fields = class_info->fields;
3676                                 osrfHash* field = osrfHashGet( fields, search_itr->key );
3677
3678                                 if( !field ) {
3679                                         const char* table = class_info->source_def;
3680                                         osrfLogError(
3681                                                 OSRF_LOG_MARK,
3682                                                 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3683                                                 modulename,
3684                                                 search_itr->key,
3685                                                 table ? table : "?",
3686                                                 class ? class : "?"
3687                                         );
3688                                         jsonIteratorFree( search_itr );
3689                                         buffer_free( sql_buf );
3690                                         return NULL;
3691                                 }
3692
3693                                 char* subpred = searchPredicate( class_info, field, node, ctx );
3694                                 if( ! subpred ) {
3695                                         buffer_free( sql_buf );
3696                                         jsonIteratorFree( search_itr );
3697                                         return NULL;
3698                                 }
3699
3700                                 buffer_add( sql_buf, subpred );
3701                                 free( subpred );
3702                         }
3703                 }
3704                 jsonIteratorFree( search_itr );
3705
3706         } else {
3707                 // ERROR ... only hash and array allowed at this level
3708                 char* predicate_string = jsonObjectToJSON( search_hash );
3709                 osrfLogError(
3710                         OSRF_LOG_MARK,
3711                         "%s: Invalid predicate structure: %s",
3712                         modulename,
3713                         predicate_string
3714                 );
3715                 buffer_free( sql_buf );
3716                 free( predicate_string );
3717                 return NULL;
3718         }
3719
3720         return buffer_release( sql_buf );
3721 }
3722
3723 /* Build a JSON_ARRAY of field names for a given table alias
3724 */
3725 static jsonObject* defaultSelectList( const char* table_alias ) {
3726
3727         if( ! table_alias )
3728                 table_alias = "";
3729
3730         ClassInfo* class_info = search_all_alias( table_alias );
3731         if( ! class_info ) {
3732                 osrfLogError(
3733                         OSRF_LOG_MARK,
3734                         "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3735                         modulename,
3736                         table_alias
3737                 );
3738                 return NULL;
3739         }
3740
3741         jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3742         osrfHash* field_def = NULL;
3743         osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3744         while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3745                 const char* field_name = osrfHashIteratorKey( field_itr );
3746                 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3747                         jsonObjectPush( array, jsonNewObject( field_name ) );
3748                 }
3749         }
3750         osrfHashIteratorFree( field_itr );
3751
3752         return array;
3753 }
3754
3755 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3756 // The jsonObject must be a JSON_HASH with an single entry for "union",
3757 // "intersect", or "except".  The data associated with this key must be an
3758 // array of hashes, each hash being a query.
3759 // Also allowed but currently ignored: entries for "order_by" and "alias".
3760 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3761         // Sanity check
3762         if( ! combo || combo->type != JSON_HASH )
3763                 return NULL;      // should be impossible; validated by caller
3764
3765         const jsonObject* query_array = NULL;   // array of subordinate queries
3766         const char* op = NULL;     // name of operator, e.g. UNION
3767         const char* alias = NULL;  // alias for the query (needed for ORDER BY)
3768         int op_count = 0;          // for detecting conflicting operators
3769         int excepting = 0;         // boolean
3770         int all = 0;               // boolean
3771         jsonObject* order_obj = NULL;
3772
3773         // Identify the elements in the hash
3774         jsonIterator* query_itr = jsonNewIterator( combo );
3775         jsonObject* curr_obj = NULL;
3776         while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3777                 if( ! strcmp( "union", query_itr->key ) ) {
3778                         ++op_count;
3779                         op = " UNION ";
3780                         query_array = curr_obj;
3781                 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3782                         ++op_count;
3783                         op = " INTERSECT ";
3784                         query_array = curr_obj;
3785                 } else if( ! strcmp( "except", query_itr->key ) ) {
3786                         ++op_count;
3787                         op = " EXCEPT ";
3788                         excepting = 1;
3789                         query_array = curr_obj;
3790                 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3791                         osrfLogWarning(
3792                                 OSRF_LOG_MARK,
3793                                 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3794                                 modulename
3795                         );
3796                         order_obj = curr_obj;
3797                 } else if( ! strcmp( "alias", query_itr->key ) ) {
3798                         if( curr_obj->type != JSON_STRING ) {
3799                                 jsonIteratorFree( query_itr );
3800                                 return NULL;
3801                         }
3802                         alias = jsonObjectGetString( curr_obj );
3803                 } else if( ! strcmp( "all", query_itr->key ) ) {
3804                         if( obj_is_true( curr_obj ) )
3805                                 all = 1;
3806                 } else {
3807                         if( ctx )
3808                                 osrfAppSessionStatus(
3809                                         ctx->session,
3810                                         OSRF_STATUS_INTERNALSERVERERROR,
3811                                         "osrfMethodException",
3812                                         ctx->request,
3813                                         "Malformed query; unexpected entry in query object"
3814                                 );
3815                         osrfLogError(
3816                                 OSRF_LOG_MARK,
3817                                 "%s: Unexpected entry for \"%s\" in%squery",
3818                                 modulename,
3819                                 query_itr->key,
3820                                 op
3821                         );
3822                         jsonIteratorFree( query_itr );
3823                         return NULL;
3824                 }
3825         }
3826         jsonIteratorFree( query_itr );
3827
3828         // More sanity checks
3829         if( ! query_array ) {
3830                 if( ctx )
3831                         osrfAppSessionStatus(
3832                                 ctx->session,
3833                                 OSRF_STATUS_INTERNALSERVERERROR,
3834                                 "osrfMethodException",
3835                                 ctx->request,
3836                                 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3837                         );
3838                 osrfLogError(
3839                         OSRF_LOG_MARK,
3840                         "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3841                         modulename
3842                 );
3843                 return NULL;        // should be impossible...
3844         } else if( op_count > 1 ) {
3845                 if( ctx )
3846                                 osrfAppSessionStatus(
3847                                 ctx->session,
3848                                 OSRF_STATUS_INTERNALSERVERERROR,
3849                                 "osrfMethodException",
3850                                 ctx->request,
3851                                 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3852                         );
3853                 osrfLogError(
3854                         OSRF_LOG_MARK,
3855                         "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3856                         modulename
3857                 );
3858                 return NULL;
3859         } if( query_array->type != JSON_ARRAY ) {
3860                 if( ctx )
3861                                 osrfAppSessionStatus(
3862                                 ctx->session,
3863                                 OSRF_STATUS_INTERNALSERVERERROR,
3864                                 "osrfMethodException",
3865                                 ctx->request,
3866                                 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3867                         );
3868                 osrfLogError(
3869                         OSRF_LOG_MARK,
3870                         "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3871                         modulename,
3872                         op,
3873                         json_type( query_array->type )
3874                 );
3875                 return NULL;
3876         } if( query_array->size < 2 ) {
3877                 if( ctx )
3878                         osrfAppSessionStatus(
3879                                 ctx->session,
3880                                 OSRF_STATUS_INTERNALSERVERERROR,
3881                                 "osrfMethodException",
3882                                 ctx->request,
3883                                 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3884                         );
3885                 osrfLogError(
3886                         OSRF_LOG_MARK,
3887                         "%s:%srequires multiple queries as operands",
3888                         modulename,
3889                         op
3890                 );
3891                 return NULL;
3892         } else if( excepting && query_array->size > 2 ) {
3893                 if( ctx )
3894                         osrfAppSessionStatus(
3895                                 ctx->session,
3896                                 OSRF_STATUS_INTERNALSERVERERROR,
3897                                 "osrfMethodException",
3898                                 ctx->request,
3899                                 "EXCEPT operator has too many queries as operands"
3900                         );
3901                 osrfLogError(
3902                         OSRF_LOG_MARK,
3903                         "%s:EXCEPT operator has too many queries as operands",
3904                         modulename
3905                 );
3906                 return NULL;
3907         } else if( order_obj && ! alias ) {
3908                 if( ctx )
3909                         osrfAppSessionStatus(
3910                                 ctx->session,
3911                                 OSRF_STATUS_INTERNALSERVERERROR,
3912                                 "osrfMethodException",
3913                                 ctx->request,
3914                                 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3915                         );
3916                 osrfLogError(
3917                         OSRF_LOG_MARK,
3918                         "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3919                         modulename
3920                 );
3921                 return NULL;
3922         }
3923
3924         // So far so good.  Now build the SQL.
3925         growing_buffer* sql = buffer_init( 256 );
3926
3927         // If we nested inside another UNION, INTERSECT, or EXCEPT,
3928         // Add a layer of parentheses
3929         if( flags & SUBCOMBO )
3930                 OSRF_BUFFER_ADD( sql, "( " );
3931
3932         // Traverse the query array.  Each entry should be a hash.
3933         int first = 1;   // boolean
3934         int i = 0;
3935         jsonObject* query = NULL;
3936         while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3937                 if( query->type != JSON_HASH ) {
3938                         if( ctx )
3939                                 osrfAppSessionStatus(
3940                                         ctx->session,
3941                                         OSRF_STATUS_INTERNALSERVERERROR,
3942                                         "osrfMethodException",
3943                                         ctx->request,
3944                                         "Malformed query under UNION, INTERSECT or EXCEPT"
3945                                 );
3946                         osrfLogError(
3947                                 OSRF_LOG_MARK,
3948                                 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3949                                 modulename,
3950                                 op,
3951                                 json_type( query->type )
3952                         );
3953                         buffer_free( sql );
3954                         return NULL;
3955                 }
3956
3957                 if( first )
3958                         first = 0;
3959                 else {
3960                         OSRF_BUFFER_ADD( sql, op );
3961                         if( all )
3962                                 OSRF_BUFFER_ADD( sql, "ALL " );
3963                 }
3964
3965                 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3966                 if( ! query_str ) {
3967                         osrfLogError(
3968                                 OSRF_LOG_MARK,
3969                                 "%s: Error building query under%s",
3970                                 modulename,
3971                                 op
3972                         );
3973                         buffer_free( sql );
3974                         return NULL;
3975                 }
3976
3977                 OSRF_BUFFER_ADD( sql, query_str );
3978         }
3979
3980         if( flags & SUBCOMBO )
3981                 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3982
3983         if( !(flags & SUBSELECT) )
3984                 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3985
3986         return buffer_release( sql );
3987 }
3988
3989 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3990 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3991 // or "except" to indicate the type of query.
3992 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3993         // Sanity checks
3994         if( ! query ) {
3995                 if( ctx )
3996                         osrfAppSessionStatus(
3997                                 ctx->session,
3998                                 OSRF_STATUS_INTERNALSERVERERROR,
3999                                 "osrfMethodException",
4000                                 ctx->request,
4001                                 "Malformed query; no query object"
4002                         );
4003                 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
4004                 return NULL;
4005         } else if( query->type != JSON_HASH ) {
4006                 if( ctx )
4007                         osrfAppSessionStatus(
4008                                 ctx->session,
4009                                 OSRF_STATUS_INTERNALSERVERERROR,
4010                                 "osrfMethodException",
4011                                 ctx->request,
4012                                 "Malformed query object"
4013                         );
4014                 osrfLogError(
4015                         OSRF_LOG_MARK,
4016                         "%s: Query object is %s instead of JSON_HASH",
4017                         modulename,
4018                         json_type( query->type )
4019                 );
4020                 return NULL;
4021         }
4022
4023         // Determine what kind of query it purports to be, and dispatch accordingly.
4024         if( jsonObjectGetKeyConst( query, "union" ) ||
4025                 jsonObjectGetKeyConst( query, "intersect" ) ||
4026                 jsonObjectGetKeyConst( query, "except" )) {
4027                 return doCombo( ctx, query, flags );
4028         } else {
4029                 // It is presumably a SELECT query
4030
4031                 // Push a node onto the stack for the current query.  Every level of
4032                 // subquery gets its own QueryFrame on the Stack.
4033                 push_query_frame();
4034
4035                 // Build an SQL SELECT statement
4036                 char* sql = SELECT(
4037                         ctx,
4038                         jsonObjectGetKey( query, "select" ),
4039                         jsonObjectGetKeyConst( query, "from" ),
4040                         jsonObjectGetKeyConst( query, "where" ),
4041                         jsonObjectGetKeyConst( query, "having" ),
4042                         jsonObjectGetKeyConst( query, "order_by" ),
4043                         jsonObjectGetKeyConst( query, "limit" ),
4044                         jsonObjectGetKeyConst( query, "offset" ),
4045                         flags
4046                 );
4047                 pop_query_frame();
4048                 return sql;
4049         }
4050 }
4051
4052 char* SELECT (
4053                 /* method context */ osrfMethodContext* ctx,
4054
4055                 /* SELECT   */ jsonObject* selhash,
4056                 /* FROM     */ const jsonObject* join_hash,
4057                 /* WHERE    */ const jsonObject* search_hash,
4058                 /* HAVING   */ const jsonObject* having_hash,
4059                 /* ORDER BY */ const jsonObject* order_hash,
4060                 /* LIMIT    */ const jsonObject* limit,
4061                 /* OFFSET   */ const jsonObject* offset,
4062                 /* flags    */ int flags
4063 ) {
4064         const char* locale = osrf_message_get_last_locale();
4065
4066         // general tmp objects
4067         const jsonObject* tmp_const;
4068         jsonObject* selclass = NULL;
4069         jsonObject* snode = NULL;
4070         jsonObject* onode = NULL;
4071
4072         char* string = NULL;
4073         int from_function = 0;
4074         int first = 1;
4075         int gfirst = 1;
4076         //int hfirst = 1;
4077
4078         osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
4079
4080         // punt if there's no FROM clause
4081         if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
4082                 osrfLogError(
4083                         OSRF_LOG_MARK,
4084                         "%s: FROM clause is missing or empty",
4085                         modulename
4086                 );
4087                 if( ctx )
4088                         osrfAppSessionStatus(
4089                                 ctx->session,
4090                                 OSRF_STATUS_INTERNALSERVERERROR,
4091                                 "osrfMethodException",
4092                                 ctx->request,
4093                                 "FROM clause is missing or empty in JSON query"
4094                         );
4095                 return NULL;
4096         }
4097
4098         // the core search class
4099         const char* core_class = NULL;
4100
4101         // get the core class -- the only key of the top level FROM clause, or a string
4102         if( join_hash->type == JSON_HASH ) {
4103                 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
4104                 snode = jsonIteratorNext( tmp_itr );
4105
4106                 // Populate the current QueryFrame with information
4107                 // about the core class
4108                 if( add_query_core( NULL, tmp_itr->key ) ) {
4109                         if( ctx )
4110                                 osrfAppSessionStatus(
4111                                         ctx->session,
4112                                         OSRF_STATUS_INTERNALSERVERERROR,
4113                                         "osrfMethodException",
4114                                         ctx->request,
4115                                         "Unable to look up core class"
4116                                 );
4117                         return NULL;
4118                 }
4119                 core_class = curr_query->core.class_name;
4120                 join_hash = snode;
4121
4122                 jsonObject* extra = jsonIteratorNext( tmp_itr );
4123
4124                 jsonIteratorFree( tmp_itr );
4125                 snode = NULL;
4126
4127                 // There shouldn't be more than one entry in join_hash
4128                 if( extra ) {
4129                         osrfLogError(
4130                                 OSRF_LOG_MARK,
4131                                 "%s: Malformed FROM clause: extra entry in JSON_HASH",
4132                                 modulename
4133                         );
4134                         if( ctx )
4135                                 osrfAppSessionStatus(
4136                                         ctx->session,
4137                                         OSRF_STATUS_INTERNALSERVERERROR,
4138                                         "osrfMethodException",
4139                                         ctx->request,
4140                                         "Malformed FROM clause in JSON query"
4141                                 );
4142                         return NULL;    // Malformed join_hash; extra entry
4143                 }
4144         } else if( join_hash->type == JSON_ARRAY ) {
4145                 // We're selecting from a function, not from a table
4146                 from_function = 1;
4147                 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
4148                 selhash = NULL;
4149
4150         } else if( join_hash->type == JSON_STRING ) {
4151                 // Populate the current QueryFrame with information
4152                 // about the core class
4153                 core_class = jsonObjectGetString( join_hash );
4154                 join_hash = NULL;
4155                 if( add_query_core( NULL, core_class ) ) {
4156                         if( ctx )
4157                                 osrfAppSessionStatus(
4158                                         ctx->session,
4159                                         OSRF_STATUS_INTERNALSERVERERROR,
4160                                         "osrfMethodException",
4161                                         ctx->request,
4162                                         "Unable to look up core class"
4163                                 );
4164                         return NULL;
4165                 }
4166         }
4167         else {
4168                 osrfLogError(
4169                         OSRF_LOG_MARK,
4170                         "%s: FROM clause is unexpected JSON type: %s",
4171                         modulename,
4172                         json_type( join_hash->type )
4173                 );
4174                 if( ctx )
4175                         osrfAppSessionStatus(
4176                                 ctx->session,
4177                                 OSRF_STATUS_INTERNALSERVERERROR,
4178                                 "osrfMethodException",
4179                                 ctx->request,
4180                                 "Ill-formed FROM clause in JSON query"
4181                         );
4182                 return NULL;
4183         }
4184
4185         // Build the join clause, if any, while filling out the list
4186         // of joined classes in the current QueryFrame.
4187         char* join_clause = NULL;
4188         if( join_hash && ! from_function ) {
4189
4190                 join_clause = searchJOIN( join_hash, &curr_query->core );
4191                 if( ! join_clause ) {
4192                         if( ctx )
4193                                 osrfAppSessionStatus(
4194                                         ctx->session,
4195                                         OSRF_STATUS_INTERNALSERVERERROR,
4196                                         "osrfMethodException",
4197                                         ctx->request,
4198                                         "Unable to construct JOIN clause(s)"
4199                                 );
4200                         return NULL;
4201                 }
4202         }
4203
4204         // For in case we don't get a select list
4205         jsonObject* defaultselhash = NULL;
4206
4207         // if there is no select list, build a default select list ...
4208         if( !selhash && !from_function ) {
4209                 jsonObject* default_list = defaultSelectList( core_class );
4210                 if( ! default_list ) {
4211                         if( ctx ) {
4212                                 osrfAppSessionStatus(
4213                                         ctx->session,
4214                                         OSRF_STATUS_INTERNALSERVERERROR,
4215                                         "osrfMethodException",
4216                                         ctx->request,
4217                                         "Unable to build default SELECT clause in JSON query"
4218                                 );
4219                                 free( join_clause );
4220                                 return NULL;
4221                         }
4222                 }
4223
4224                 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
4225                 jsonObjectSetKey( selhash, core_class, default_list );
4226         }
4227
4228         // The SELECT clause can be encoded only by a hash
4229         if( !from_function && selhash->type != JSON_HASH ) {
4230                 osrfLogError(
4231                         OSRF_LOG_MARK,
4232                         "%s: Expected JSON_HASH for SELECT clause; found %s",
4233                         modulename,
4234                         json_type( selhash->type )
4235                 );
4236
4237                 if( ctx )
4238                         osrfAppSessionStatus(
4239                                 ctx->session,
4240                                 OSRF_STATUS_INTERNALSERVERERROR,
4241                                 "osrfMethodException",
4242                                 ctx->request,
4243                                 "Malformed SELECT clause in JSON query"
4244                         );
4245                 free( join_clause );
4246                 return NULL;
4247         }
4248
4249         // If you see a null or wild card specifier for the core class, or an
4250         // empty array, replace it with a default SELECT list
4251         tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4252         if( tmp_const ) {
4253                 int default_needed = 0;   // boolean
4254                 if( JSON_STRING == tmp_const->type
4255                         && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4256                                 default_needed = 1;
4257                 else if( JSON_NULL == tmp_const->type )
4258                         default_needed = 1;
4259
4260                 if( default_needed ) {
4261                         // Build a default SELECT list
4262                         jsonObject* default_list = defaultSelectList( core_class );
4263                         if( ! default_list ) {
4264                                 if( ctx ) {
4265                                         osrfAppSessionStatus(
4266                                                 ctx->session,
4267                                                 OSRF_STATUS_INTERNALSERVERERROR,
4268                                                 "osrfMethodException",
4269                                                 ctx->request,
4270                                                 "Can't build default SELECT clause in JSON query"
4271                                         );
4272                                         free( join_clause );
4273                                         return NULL;
4274                                 }
4275                         }
4276
4277                         jsonObjectSetKey( selhash, core_class, default_list );
4278                 }
4279         }
4280
4281         // temp buffers for the SELECT list and GROUP BY clause
4282         growing_buffer* select_buf = buffer_init( 128 );
4283         growing_buffer* group_buf  = buffer_init( 128 );
4284
4285         int aggregate_found = 0;     // boolean
4286
4287         // Build a select list
4288         if( from_function )   // From a function we select everything
4289                 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4290         else {
4291
4292                 // Build the SELECT list as SQL
4293             int sel_pos = 1;
4294             first = 1;
4295             gfirst = 1;
4296             jsonIterator* selclass_itr = jsonNewIterator( selhash );
4297             while ( (selclass = jsonIteratorNext( selclass_itr )) ) {    // For each class
4298
4299                         const char* cname = selclass_itr->key;
4300
4301                         // Make sure the target relation is in the FROM clause.
4302
4303                         // At this point join_hash is a step down from the join_hash we
4304                         // received as a parameter.  If the original was a JSON_STRING,
4305                         // then json_hash is now NULL.  If the original was a JSON_HASH,
4306                         // then json_hash is now the first (and only) entry in it,
4307                         // denoting the core class.  We've already excluded the
4308                         // possibility that the original was a JSON_ARRAY, because in
4309                         // that case from_function would be non-NULL, and we wouldn't
4310                         // be here.
4311
4312                         // If the current table alias isn't in scope, bail out
4313                         ClassInfo* class_info = search_alias( cname );
4314                         if( ! class_info ) {
4315                                 osrfLogError(
4316                                         OSRF_LOG_MARK,
4317                                         "%s: SELECT clause references class not in FROM clause: \"%s\"",
4318                                         modulename,
4319                                         cname
4320                                 );
4321                                 if( ctx )
4322                                         osrfAppSessionStatus(
4323                                                 ctx->session,
4324                                                 OSRF_STATUS_INTERNALSERVERERROR,
4325                                                 "osrfMethodException",
4326                                                 ctx->request,
4327                                                 "Selected class not in FROM clause in JSON query"
4328                                         );
4329                                 jsonIteratorFree( selclass_itr );
4330                                 buffer_free( select_buf );
4331                                 buffer_free( group_buf );
4332                                 if( defaultselhash )
4333                                         jsonObjectFree( defaultselhash );
4334                                 free( join_clause );
4335                                 return NULL;
4336                         }
4337
4338                         if( selclass->type != JSON_ARRAY ) {
4339                                 osrfLogError(
4340                                         OSRF_LOG_MARK,
4341                                         "%s: Malformed SELECT list for class \"%s\"; not an array",
4342                                         modulename,
4343                                         cname
4344                                 );
4345                                 if( ctx )
4346                                         osrfAppSessionStatus(
4347                                                 ctx->session,
4348                                                 OSRF_STATUS_INTERNALSERVERERROR,
4349                                                 "osrfMethodException",
4350                                                 ctx->request,
4351                                                 "Selected class not in FROM clause in JSON query"
4352                                         );
4353
4354                                 jsonIteratorFree( selclass_itr );
4355                                 buffer_free( select_buf );
4356                                 buffer_free( group_buf );
4357                                 if( defaultselhash )
4358                                         jsonObjectFree( defaultselhash );
4359                                 free( join_clause );
4360                                 return NULL;
4361                         }
4362
4363                         // Look up some attributes of the current class
4364                         osrfHash* idlClass        = class_info->class_def;
4365                         osrfHash* class_field_set = class_info->fields;
4366                         const char* class_pkey    = osrfHashGet( idlClass, "primarykey" );
4367                         const char* class_tname   = osrfHashGet( idlClass, "tablename" );
4368
4369                         if( 0 == selclass->size ) {
4370                                 osrfLogWarning(
4371                                         OSRF_LOG_MARK,
4372                                         "%s: No columns selected from \"%s\"",
4373                                         modulename,
4374                                         cname
4375                                 );
4376                         }
4377
4378                         // stitch together the column list for the current table alias...
4379                         unsigned long field_idx = 0;
4380                         jsonObject* selfield = NULL;
4381                         while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4382
4383                                 // If we need a separator comma, add one
4384                                 if( first ) {
4385                                         first = 0;
4386                                 } else {
4387                                         OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4388                                 }
4389
4390                                 // if the field specification is a string, add it to the list
4391                                 if( selfield->type == JSON_STRING ) {
4392
4393                                         // Look up the field in the IDL
4394                                         const char* col_name = jsonObjectGetString( selfield );
4395                                         osrfHash* field_def = NULL;
4396
4397                                         if (!osrfStringArrayContains(
4398                                                         osrfHashGet(
4399                                                                 osrfHashGet( class_field_set, col_name ),
4400                                                                 "suppress_controller"),
4401                                                         modulename
4402                                         ))
4403                                                 field_def = osrfHashGet( class_field_set, col_name );
4404
4405                                         if( !field_def ) {
4406                                                 // No such field in current class
4407                                                 osrfLogError(
4408                                                         OSRF_LOG_MARK,
4409                                                         "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4410                                                         modulename,
4411                                                         col_name,
4412                                                         cname
4413                                                 );
4414                                                 if( ctx )
4415                                                         osrfAppSessionStatus(
4416                                                                 ctx->session,
4417                                                                 OSRF_STATUS_INTERNALSERVERERROR,
4418                                                                 "osrfMethodException",
4419                                                                 ctx->request,
4420                                                                 "Selected column not defined in JSON query"
4421                                                         );
4422                                                 jsonIteratorFree( selclass_itr );
4423                                                 buffer_free( select_buf );
4424                                                 buffer_free( group_buf );
4425                                                 if( defaultselhash )
4426                                                         jsonObjectFree( defaultselhash );
4427                                                 free( join_clause );
4428                                                 return NULL;
4429                                         } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4430                                                 // Virtual field not allowed
4431                                                 osrfLogError(
4432                                                         OSRF_LOG_MARK,
4433                                                         "%s: Selected column \"%s\" for class \"%s\" is virtual",
4434                                                         modulename,
4435                                                         col_name,
4436                                                         cname
4437                                                 );
4438                                                 if( ctx )
4439                                                         osrfAppSessionStatus(
4440                                                                 ctx->session,
4441                                                                 OSRF_STATUS_INTERNALSERVERERROR,
4442                                                                 "osrfMethodException",
4443                                                                 ctx->request,
4444                                                                 "Selected column may not be virtual in JSON query"
4445                                                         );
4446                                                 jsonIteratorFree( selclass_itr );
4447                                                 buffer_free( select_buf );
4448                                                 buffer_free( group_buf );
4449                                                 if( defaultselhash )
4450                                                         jsonObjectFree( defaultselhash );
4451                                                 free( join_clause );
4452                                                 return NULL;
4453                                         }
4454
4455                                         if( locale ) {
4456                                                 const char* i18n;
4457                                                 if( flags & DISABLE_I18N )
4458                                                         i18n = NULL;
4459                                                 else
4460                                                         i18n = osrfHashGet( field_def, "i18n" );
4461
4462                                                 if( str_is_true( i18n ) ) {
4463                                                         buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4464                                                                 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4465                                                                 class_tname, cname, col_name, class_pkey,
4466                                                                 cname, class_pkey, locale, col_name );
4467                                                 } else {
4468                                                         buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4469                                                                 cname, col_name, col_name );
4470                                                 }
4471                                         } else {
4472                                                 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4473                                                                 cname, col_name, col_name );
4474                                         }
4475
4476                                 // ... but it could be an object, in which case we check for a Field Transform
4477                                 } else if( selfield->type == JSON_HASH ) {
4478
4479                                         const char* col_name = jsonObjectGetString(
4480                                                         jsonObjectGetKeyConst( selfield, "column" ) );
4481
4482                                         // Get the field definition from the IDL
4483                                         osrfHash* field_def = NULL;
4484                                         if (!osrfStringArrayContains(
4485                                                         osrfHashGet(
4486                                                                 osrfHashGet( class_field_set, col_name ),
4487                                                                 "suppress_controller"),
4488                                                         modulename
4489                                         ))
4490                                                 field_def = osrfHashGet( class_field_set, col_name );
4491
4492
4493                                         if( !field_def ) {
4494                                                 // No such field in current class
4495                                                 osrfLogError(
4496                                                         OSRF_LOG_MARK,
4497                                                         "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4498                                                         modulename,
4499                                                         col_name,
4500                                                         cname
4501                                                 );
4502                                                 if( ctx )
4503                                                         osrfAppSessionStatus(
4504                                                                 ctx->session,
4505                                                                 OSRF_STATUS_INTERNALSERVERERROR,
4506                                                                 "osrfMethodException",
4507                                                                 ctx->request,
4508                                                                 "Selected column is not defined in JSON query"
4509                                                         );
4510                                                 jsonIteratorFree( selclass_itr );
4511                                                 buffer_free( select_buf );
4512                                                 buffer_free( group_buf );
4513                                                 if( defaultselhash )
4514                                                         jsonObjectFree( defaultselhash );
4515                                                 free( join_clause );
4516                                                 return NULL;
4517                                         } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4518                                                 // No such field in current class
4519                                                 osrfLogError(
4520                                                         OSRF_LOG_MARK,
4521                                                         "%s: Selected column \"%s\" is virtual for class \"%s\"",
4522                                                         modulename,
4523                                                         col_name,
4524                                                         cname
4525                                                 );
4526                                                 if( ctx )
4527                                                         osrfAppSessionStatus(
4528                                                                 ctx->session,
4529                                                                 OSRF_STATUS_INTERNALSERVERERROR,
4530                                                                 "osrfMethodException",
4531                                                                 ctx->request,
4532                                                                 "Selected column is virtual in JSON query"
4533                                                         );
4534                                                 jsonIteratorFree( selclass_itr );
4535                                                 buffer_free( select_buf );
4536                                                 buffer_free( group_buf );
4537                                                 if( defaultselhash )
4538                                                         jsonObjectFree( defaultselhash );
4539                                                 free( join_clause );
4540                                                 return NULL;
4541                                         }
4542
4543                                         // Decide what to use as a column alias
4544                                         const char* _alias;
4545                                         if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4546                                                 _alias = jsonObjectGetString( tmp_const );
4547                                         } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4548                                                 _alias = jsonObjectGetString( tmp_const );
4549                                         } else {         // Use field name as the alias
4550                                                 _alias = col_name;
4551                                         }
4552
4553                                         if( jsonObjectGetKeyConst( selfield, "transform" )) {
4554                                                 char* transform_str = searchFieldTransform(
4555                                                         class_info->alias, field_def, selfield );
4556                                                 if( transform_str ) {
4557                                                         buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4558                                                         free( transform_str );
4559                                                 } else {
4560                                                         if( ctx )
4561                                                                 osrfAppSessionStatus(
4562                                                                         ctx->session,
4563                                                                         OSRF_STATUS_INTERNALSERVERERROR,
4564                                                                         "osrfMethodException",
4565                                                                         ctx->request,
4566                                                                         "Unable to generate transform function in JSON query"
4567                                                                 );
4568                                                         jsonIteratorFree( selclass_itr );
4569                                                         buffer_free( select_buf );
4570                                                         buffer_free( group_buf );
4571                                                         if( defaultselhash )
4572                                                                 jsonObjectFree( defaultselhash );
4573                                                         free( join_clause );
4574                                                         return NULL;
4575                                                 }
4576                                         } else {
4577
4578                                                 if( locale ) {
4579                                                         const char* i18n;
4580                                                         if( flags & DISABLE_I18N )
4581                                                                 i18n = NULL;
4582                                                         else
4583                                                                 i18n = osrfHashGet( field_def, "i18n" );
4584
4585                                                         if( str_is_true( i18n ) ) {
4586                                                                 buffer_fadd( select_buf,
4587                                                                         " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4588                                                                         "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4589                                                                         class_tname, cname, col_name, class_pkey, cname,
4590                                                                         class_pkey, locale, _alias );
4591                                                         } else {
4592                                                                 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4593                                                                         cname, col_name, _alias );
4594                                                         }
4595                                                 } else {
4596                                                         buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4597                                                                 cname, col_name, _alias );
4598                                                 }
4599                                         }
4600                                 }
4601                                 else {
4602                                         osrfLogError(
4603                                                 OSRF_LOG_MARK,
4604                                                 "%s: Selected item is unexpected JSON type: %s",
4605                                                 modulename,
4606                                                 json_type( selfield->type )
4607                                         );
4608                                         if( ctx )
4609                                                 osrfAppSessionStatus(
4610                                                         ctx->session,
4611                                                         OSRF_STATUS_INTERNALSERVERERROR,
4612                                                         "osrfMethodException",
4613                                                         ctx->request,
4614                                                         "Ill-formed SELECT item in JSON query"
4615                                                 );
4616                                         jsonIteratorFree( selclass_itr );
4617                                         buffer_free( select_buf );
4618                                         buffer_free( group_buf );
4619                                         if( defaultselhash )
4620                                                 jsonObjectFree( defaultselhash );
4621                                         free( join_clause );
4622                                         return NULL;
4623                                 }
4624
4625                                 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4626                                 if( obj_is_true( agg_obj ) )
4627                                         aggregate_found = 1;
4628                                 else {
4629                                         // Append a comma (except for the first one)
4630                                         // and add the column to a GROUP BY clause
4631                                         if( gfirst )
4632                                                 gfirst = 0;
4633                                         else
4634                                                 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4635
4636                                         buffer_fadd( group_buf, " %d", sel_pos );
4637                                 }
4638
4639 #if 0
4640                             if (is_agg->size || (flags & SELECT_DISTINCT)) {
4641
4642                                         const jsonObject* aggregate_obj = jsonObjectGetKeyConst( selfield, "aggregate");
4643                                     if ( ! obj_is_true( aggregate_obj ) ) {
4644                                             if (gfirst) {
4645                                                     gfirst = 0;
4646                                             } else {
4647                                                         OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4648                                             }
4649
4650                                             buffer_fadd(group_buf, " %d", sel_pos);
4651
4652                                         /*
4653                                     } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4654                                             if (gfirst) {
4655                                                     gfirst = 0;
4656                                             } else {
4657                                                         OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4658                                             }
4659
4660                                             _column = searchFieldTransform(class_info->alias, field, selfield);
4661                                                 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4662                                                 OSRF_BUFFER_ADD(group_buf, _column);
4663                                             _column = searchFieldTransform(class_info->alias, field, selfield);
4664                                         */
4665                                     }
4666                             }
4667 #endif
4668
4669                                 sel_pos++;
4670                         } // end while -- iterating across SELECT columns
4671
4672                 } // end while -- iterating across classes
4673
4674                 jsonIteratorFree( selclass_itr );
4675         }
4676
4677         char* col_list = buffer_release( select_buf );
4678
4679         // Make sure the SELECT list isn't empty.  This can happen, for example,
4680         // if we try to build a default SELECT clause from a non-core table.
4681
4682         if( ! *col_list ) {
4683                 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4684                 if( ctx )
4685                         osrfAppSessionStatus(
4686                                 ctx->session,
4687                                 OSRF_STATUS_INTERNALSERVERERROR,
4688                                 "osrfMethodException",
4689                                 ctx->request,
4690                                 "SELECT list is empty"
4691                 );
4692                 free( col_list );
4693                 buffer_free( group_buf );
4694                 if( defaultselhash )
4695                         jsonObjectFree( defaultselhash );
4696                 free( join_clause );
4697                 return NULL;
4698         }
4699
4700         char* table = NULL;
4701         if( from_function )
4702                 table = searchValueTransform( join_hash );
4703         else
4704                 table = strdup( curr_query->core.source_def );
4705
4706         if( !table ) {
4707                 if( ctx )
4708                         osrfAppSessionStatus(
4709                                 ctx->session,
4710                                 OSRF_STATUS_INTERNALSERVERERROR,
4711                                 "osrfMethodException",
4712                                 ctx->request,
4713                                 "Unable to identify table for core class"
4714                         );
4715                 free( col_list );
4716                 buffer_free( group_buf );
4717                 if( defaultselhash )
4718                         jsonObjectFree( defaultselhash );
4719                 free( join_clause );
4720                 return NULL;
4721         }
4722
4723         // Put it all together
4724         growing_buffer* sql_buf = buffer_init( 128 );
4725         buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4726         free( col_list );
4727         free( table );
4728
4729         // Append the join clause, if any
4730         if( join_clause ) {
4731                 buffer_add(sql_buf, join_clause );
4732                 free( join_clause );
4733         }
4734
4735         char* order_by_list = NULL;
4736         char* having_buf = NULL;
4737
4738         if( !from_function ) {
4739
4740                 // Build a WHERE clause, if there is one
4741                 if( search_hash ) {
4742                         buffer_add( sql_buf, " WHERE " );
4743
4744                         // and it's on the WHERE clause
4745                         char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4746                         if( ! pred ) {
4747                                 if( ctx ) {
4748                                         osrfAppSessionStatus(
4749                                                 ctx->session,
4750                                                 OSRF_STATUS_INTERNALSERVERERROR,
4751                                                 "osrfMethodException",
4752                                                 ctx->request,
4753                                                 "Severe query error in WHERE predicate -- see error log for more details"
4754                                         );
4755                                 }
4756                                 buffer_free( group_buf );
4757                                 buffer_free( sql_buf );
4758                                 if( defaultselhash )
4759                                         jsonObjectFree( defaultselhash );
4760                                 return NULL;
4761                         }
4762
4763                         buffer_add( sql_buf, pred );
4764                         free( pred );
4765                 }
4766
4767                 // Build a HAVING clause, if there is one
4768                 if( having_hash ) {
4769
4770                         // and it's on the the WHERE clause
4771                         having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4772
4773                         if( ! having_buf ) {
4774                                 if( ctx ) {
4775                                                 osrfAppSessionStatus(
4776                                                 ctx->session,
4777                                                 OSRF_STATUS_INTERNALSERVERERROR,
4778                                                 "osrfMethodException",
4779                                                 ctx->request,
4780                                                 "Severe query error in HAVING predicate -- see error log for more details"
4781                                         );
4782                                 }
4783                                 buffer_free( group_buf );
4784                                 buffer_free( sql_buf );
4785                                 if( defaultselhash )
4786                                         jsonObjectFree( defaultselhash );
4787                                 return NULL;
4788                         }
4789                 }
4790
4791                 // Build an ORDER BY clause, if there is one
4792                 if( NULL == order_hash )
4793                         ;  // No ORDER BY? do nothing
4794                 else if( JSON_ARRAY == order_hash->type ) {
4795                         order_by_list = buildOrderByFromArray( ctx, order_hash );
4796                         if( !order_by_list ) {
4797                                 free( having_buf );
4798                                 buffer_free( group_buf );
4799                                 buffer_free( sql_buf );
4800                                 if( defaultselhash )
4801                                         jsonObjectFree( defaultselhash );
4802                                 return NULL;
4803                         }
4804                 } else if( JSON_HASH == order_hash->type ) {
4805                         // This hash is keyed on class alias.  Each class has either
4806                         // an array of field names or a hash keyed on field name.
4807                         growing_buffer* order_buf = NULL;  // to collect ORDER BY list
4808                         jsonIterator* class_itr = jsonNewIterator( order_hash );
4809                         while( (snode = jsonIteratorNext( class_itr )) ) {
4810
4811                                 ClassInfo* order_class_info = search_alias( class_itr->key );
4812                                 if( ! order_class_info ) {
4813                                         osrfLogError( OSRF_LOG_MARK,
4814                                                 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4815                                                 modulename, class_itr->key );
4816                                         if( ctx )
4817                                                 osrfAppSessionStatus(
4818                                                         ctx->session,
4819                                                         OSRF_STATUS_INTERNALSERVERERROR,
4820                                                         "osrfMethodException",
4821                                                         ctx->request,
4822                                                         "Invalid class referenced in ORDER BY clause -- "
4823                                                                 "see error log for more details"
4824                                                 );
4825                                         jsonIteratorFree( class_itr );
4826                                         buffer_free( order_buf );
4827                                         free( having_buf );
4828                                         buffer_free( group_buf );
4829                                         buffer_free( sql_buf );
4830                                         if( defaultselhash )
4831                                                 jsonObjectFree( defaultselhash );
4832                                         return NULL;
4833                                 }
4834
4835                                 osrfHash* field_list_def = order_class_info->fields;
4836
4837                                 if( snode->type == JSON_HASH ) {
4838
4839                                         // Hash is keyed on field names from the current class.  For each field
4840                                         // there is another layer of hash to define the sorting details, if any,
4841                                         // or a string to indicate direction of sorting.
4842                                         jsonIterator* order_itr = jsonNewIterator( snode );
4843                                         while( (onode = jsonIteratorNext( order_itr )) ) {
4844
4845                                                 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4846                                                 if( !field_def ) {
4847                                                         osrfLogError( OSRF_LOG_MARK,
4848                                                                 "%s: Invalid field \"%s\" in ORDER BY clause",
4849                                                                 modulename, order_itr->key );
4850                                                         if( ctx )
4851                                                                 osrfAppSessionStatus(
4852                                                                         ctx->session,
4853                                                                         OSRF_STATUS_INTERNALSERVERERROR,
4854                                                                         "osrfMethodException",
4855                                                                         ctx->request,
4856                                                                         "Invalid field in ORDER BY clause -- "
4857                                                                         "see error log for more details"
4858                                                                 );
4859                                                         jsonIteratorFree( order_itr );
4860                                                         jsonIteratorFree( class_itr );
4861                                                         buffer_free( order_buf );
4862                                                         free( having_buf );
4863                                                         buffer_free( group_buf );
4864                                                         buffer_free( sql_buf );
4865                                                         if( defaultselhash )
4866                                                                 jsonObjectFree( defaultselhash );
4867                                                         return NULL;
4868                                                 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4869                                                         osrfLogError( OSRF_LOG_MARK,
4870                                                                 "%s: Virtual field \"%s\" in ORDER BY clause",
4871                                                                 modulename, order_itr->key );
4872                                                         if( ctx )
4873                                                                 osrfAppSessionStatus(
4874                                                                         ctx->session,
4875                                                                         OSRF_STATUS_INTERNALSERVERERROR,
4876                                                                         "osrfMethodException",
4877                                                                         ctx->request,
4878                                                                         "Virtual field in ORDER BY clause -- "
4879                                                                         "see error log for more details"
4880                                                         );
4881                                                         jsonIteratorFree( order_itr );
4882                                                         jsonIteratorFree( class_itr );
4883                                                         buffer_free( order_buf );
4884                                                         free( having_buf );
4885                                                         buffer_free( group_buf );
4886                                                         buffer_free( sql_buf );
4887                                                         if( defaultselhash )
4888                                                                 jsonObjectFree( defaultselhash );
4889                                                         return NULL;
4890                                                 }
4891
4892                                                 const char* direction = NULL;
4893                                                 if( onode->type == JSON_HASH ) {
4894                                                         if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4895                                                                 string = searchFieldTransform(
4896                                                                         class_itr->key,
4897                                                                         osrfHashGet( field_list_def, order_itr->key ),
4898                                                                         onode
4899                                                                 );
4900                                                                 if( ! string ) {
4901                                                                         if( ctx ) osrfAppSessionStatus(
4902                                                                                 ctx->session,
4903                                                                                 OSRF_STATUS_INTERNALSERVERERROR,
4904                                                                                 "osrfMethodException",
4905                                                                                 ctx->request,
4906                                                                                 "Severe query error in ORDER BY clause -- "
4907                                                                                 "see error log for more details"
4908                                                                         );
4909                                                                         jsonIteratorFree( order_itr );
4910                                                                         jsonIteratorFree( class_itr );
4911                                                                         free( having_buf );
4912                                                                         buffer_free( group_buf );
4913                                                                         buffer_free( order_buf);
4914                                                                         buffer_free( sql_buf );
4915                                                                         if( defaultselhash )
4916                                                                                 jsonObjectFree( defaultselhash );
4917                                                                         return NULL;
4918                                                                 }
4919                                                         } else {
4920                                                                 growing_buffer* field_buf = buffer_init( 16 );
4921                                                                 buffer_fadd( field_buf, "\"%s\".%s",
4922                                                                         class_itr->key, order_itr->key );
4923                                                                 string = buffer_release( field_buf );
4924                                                         }
4925
4926                                                         if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4927                                                                 const char* dir = jsonObjectGetString( tmp_const );
4928                                                                 if(!strncasecmp( dir, "d", 1 )) {
4929                                                                         direction = " DESC";
4930                                                                 } else {
4931                                                                         direction = " ASC";
4932                                                                 }
4933                                                         }
4934
4935                                                 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4936                                                         osrfLogError( OSRF_LOG_MARK,
4937                                                                 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4938                                                                 modulename, json_type( onode->type ) );
4939                                                         if( ctx )
4940                                                                 osrfAppSessionStatus(
4941                                                                         ctx->session,
4942                                                                         OSRF_STATUS_INTERNALSERVERERROR,
4943                                                                         "osrfMethodException",
4944                                                                         ctx->request,
4945                                                                         "Malformed ORDER BY clause -- see error log for more details"
4946                                                                 );
4947                                                         jsonIteratorFree( order_itr );
4948                                                         jsonIteratorFree( class_itr );
4949                                                         free( having_buf );
4950                                                         buffer_free( group_buf );
4951                                                         buffer_free( order_buf );
4952                                                         buffer_free( sql_buf );
4953                                                         if( defaultselhash )
4954                                                                 jsonObjectFree( defaultselhash );
4955                                                         return NULL;
4956
4957                                                 } else {
4958                                                         string = strdup( order_itr->key );
4959                                                         const char* dir = jsonObjectGetString( onode );
4960                                                         if( !strncasecmp( dir, "d", 1 )) {
4961                                                                 direction = " DESC";
4962                                                         } else {
4963                                                                 direction = " ASC";
4964                                                         }
4965                                                 }
4966
4967                                                 if( order_buf )
4968                                                         OSRF_BUFFER_ADD( order_buf, ", " );
4969                                                 else
4970                                                         order_buf = buffer_init( 128 );
4971
4972                                                 OSRF_BUFFER_ADD( order_buf, string );
4973                                                 free( string );
4974
4975                                                 if( direction ) {
4976                                                          OSRF_BUFFER_ADD( order_buf, direction );
4977                                                 }
4978
4979                                         } // end while
4980                                         jsonIteratorFree( order_itr );
4981
4982                                 } else if( snode->type == JSON_ARRAY ) {
4983
4984                                         // Array is a list of fields from the current class
4985                                         unsigned long order_idx = 0;
4986                                         while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4987
4988                                                 const char* _f = jsonObjectGetString( onode );
4989
4990                                                 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4991                                                 if( !field_def ) {
4992                                                         osrfLogError( OSRF_LOG_MARK,
4993                                                                         "%s: Invalid field \"%s\" in ORDER BY clause",
4994                                                                         modulename, _f );
4995                                                         if( ctx )
4996                                                                 osrfAppSessionStatus(
4997                                                                         ctx->session,
4998                                                                         OSRF_STATUS_INTERNALSERVERERROR,
4999                                                                         "osrfMethodException",
5000                                                                         ctx->request,
5001                                                                         "Invalid field in ORDER BY clause -- "
5002                                                                         "see error log for more details"
5003                                                                 );
5004                                                         jsonIteratorFree( class_itr );
5005                                                         buffer_free( order_buf );
5006                                                         free( having_buf );
5007                                                         buffer_free( group_buf );
5008                                                         buffer_free( sql_buf );
5009                                                         if( defaultselhash )
5010                                                                 jsonObjectFree( defaultselhash );
5011                                                         return NULL;
5012                                                 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5013                                                         osrfLogError( OSRF_LOG_MARK,
5014                                                                 "%s: Virtual field \"%s\" in ORDER BY clause",
5015                                                                 modulename, _f );
5016                                                         if( ctx )
5017                                                                 osrfAppSessionStatus(
5018                                                                         ctx->session,
5019                                                                         OSRF_STATUS_INTERNALSERVERERROR,
5020                                                                         "osrfMethodException",
5021                                                                         ctx->request,
5022                                                                         "Virtual field in ORDER BY clause -- "
5023                                                                         "see error log for more details"
5024                                                                 );
5025                                                         jsonIteratorFree( class_itr );
5026                                                         buffer_free( order_buf );
5027                                                         free( having_buf );
5028                                                         buffer_free( group_buf );
5029                                                         buffer_free( sql_buf );
5030                                                         if( defaultselhash )
5031                                                                 jsonObjectFree( defaultselhash );
5032                                                         return NULL;
5033                                                 }
5034
5035                                                 if( order_buf )
5036                                                         OSRF_BUFFER_ADD( order_buf, ", " );
5037                                                 else
5038                                                         order_buf = buffer_init( 128 );
5039
5040                                                 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
5041
5042                                         } // end while
5043
5044                                 // IT'S THE OOOOOOOOOOOLD STYLE!
5045                                 } else {
5046                                         osrfLogError( OSRF_LOG_MARK,
5047                                                 "%s: Possible SQL injection attempt; direct order by is not allowed",
5048                                                 modulename );
5049                                         if(ctx) {
5050                                                 osrfAppSessionStatus(
5051                                                         ctx->session,
5052                                                         OSRF_STATUS_INTERNALSERVERERROR,
5053                                                         "osrfMethodException",
5054                                                         ctx->request,
5055                                                         "Severe query error -- see error log for more details"
5056                                                 );
5057                                         }
5058
5059                                         free( having_buf );
5060                                         buffer_free( group_buf );
5061                                         buffer_free( order_buf );
5062                                         buffer_free( sql_buf );
5063                                         if( defaultselhash )
5064                                                 jsonObjectFree( defaultselhash );
5065                                         jsonIteratorFree( class_itr );
5066                                         return NULL;
5067                                 }
5068                         } // end while
5069                         jsonIteratorFree( class_itr );
5070                         if( order_buf )
5071                                 order_by_list = buffer_release( order_buf );
5072                 } else {
5073                         osrfLogError( OSRF_LOG_MARK,
5074                                 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
5075                                 modulename, json_type( order_hash->type ) );
5076                         if( ctx )
5077                                 osrfAppSessionStatus(
5078                                         ctx->session,
5079                                         OSRF_STATUS_INTERNALSERVERERROR,
5080                                         "osrfMethodException",
5081                                         ctx->request,
5082                                         "Malformed ORDER BY clause -- see error log for more details"
5083                                 );
5084                         free( having_buf );
5085                         buffer_free( group_buf );
5086                         buffer_free( sql_buf );
5087                         if( defaultselhash )
5088                                 jsonObjectFree( defaultselhash );
5089                         return NULL;
5090                 }
5091         }
5092
5093         string = buffer_release( group_buf );
5094
5095         if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
5096                 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
5097                 OSRF_BUFFER_ADD( sql_buf, string );
5098         }
5099
5100         free( string );
5101
5102         if( having_buf && *having_buf ) {
5103                 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
5104                 OSRF_BUFFER_ADD( sql_buf, having_buf );
5105                 free( having_buf );
5106         }
5107
5108         if( order_by_list ) {
5109
5110                 if( *order_by_list ) {
5111                         OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5112                         OSRF_BUFFER_ADD( sql_buf, order_by_list );
5113                 }
5114
5115                 free( order_by_list );
5116         }
5117
5118         if( limit ){
5119                 const char* str = jsonObjectGetString( limit );
5120                 if (str) { // limit could be JSON_NULL, etc.
5121                         buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
5122                 }
5123         }
5124
5125         if( offset ) {
5126                 const char* str = jsonObjectGetString( offset );
5127                 if (str) {
5128                         buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
5129                 }
5130         }
5131
5132         if( !(flags & SUBSELECT) )
5133                 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5134
5135         if( defaultselhash )
5136                  jsonObjectFree( defaultselhash );
5137
5138         return buffer_release( sql_buf );
5139
5140 } // end of SELECT()
5141
5142 /**
5143         @brief Build a list of ORDER BY expressions.
5144         @param ctx Pointer to the method context.
5145         @param order_array Pointer to a JSON_ARRAY of field specifications.
5146         @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
5147         Each expression may be either a column reference or a function call whose first parameter
5148         is a column reference.
5149
5150         Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
5151         It may optionally include entries for "direction" and/or "transform".
5152
5153         The calling code is responsible for freeing the returned string.
5154 */
5155 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
5156         if( ! order_array ) {
5157                 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
5158                         modulename );
5159                 if( ctx )
5160                         osrfAppSessionStatus(
5161                                 ctx->session,
5162                                 OSRF_STATUS_INTERNALSERVERERROR,
5163                                 "osrfMethodException",
5164                                 ctx->request,
5165                                 "Logic error: ORDER BY clause expected, not found; "
5166                                         "see error log for more details"
5167                         );
5168                 return NULL;
5169         } else if( order_array->type != JSON_ARRAY ) {
5170                 osrfLogError( OSRF_LOG_MARK,
5171                         "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
5172                 if( ctx )
5173                         osrfAppSessionStatus(
5174                         ctx->session,
5175                         OSRF_STATUS_INTERNALSERVERERROR,
5176                         "osrfMethodException",
5177                         ctx->request,
5178                         "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
5179                 return NULL;
5180         }
5181
5182         growing_buffer* order_buf = buffer_init( 128 );
5183         int first = 1;        // boolean
5184         int order_idx = 0;
5185         jsonObject* order_spec;
5186         while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
5187
5188                 if( JSON_HASH != order_spec->type ) {
5189                         osrfLogError( OSRF_LOG_MARK,
5190                                 "%s: Malformed field specification in ORDER BY clause; "
5191                                 "expected JSON_HASH, found %s",
5192                                 modulename, json_type( order_spec->type ) );
5193                         if( ctx )
5194                                 osrfAppSessionStatus(
5195                                          ctx->session,
5196                                         OSRF_STATUS_INTERNALSERVERERROR,
5197                                         "osrfMethodException",
5198                                         ctx->request,
5199                                         "Malformed ORDER BY clause -- see error log for more details"
5200                                 );
5201                         buffer_free( order_buf );
5202                         return NULL;
5203                 }
5204
5205                 const char* class_alias =
5206                         jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
5207                 const char* field =
5208                         jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
5209
5210                 jsonObject* compare_to = jsonObjectGetKey( order_spec, "compare" );
5211
5212                 if( !field || !class_alias ) {
5213                         osrfLogError( OSRF_LOG_MARK,
5214                                 "%s: Missing class or field name in field specification of ORDER BY clause",
5215                                 modulename );
5216                         if( ctx )
5217                                 osrfAppSessionStatus(
5218                                         ctx->session,
5219                                         OSRF_STATUS_INTERNALSERVERERROR,
5220                                         "osrfMethodException",
5221                                         ctx->request,
5222                                         "Malformed ORDER BY clause -- see error log for more details"
5223                                 );
5224                         buffer_free( order_buf );
5225                         return NULL;
5226                 }
5227
5228                 const ClassInfo* order_class_info = search_alias( class_alias );
5229                 if( ! order_class_info ) {
5230                         osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
5231                                 "not in FROM clause, skipping it", modulename, class_alias );
5232                         continue;
5233                 }
5234
5235                 // Add a separating comma, except at the beginning
5236                 if( first )
5237                         first = 0;
5238                 else
5239                         OSRF_BUFFER_ADD( order_buf, ", " );
5240
5241                 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
5242                 if( !field_def ) {
5243                         osrfLogError( OSRF_LOG_MARK,
5244                                 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
5245                                 modulename, class_alias, field );
5246                         if( ctx )
5247                                 osrfAppSessionStatus(
5248                                         ctx->session,
5249                                         OSRF_STATUS_INTERNALSERVERERROR,
5250                                         "osrfMethodException",
5251                                         ctx->request,
5252                                         "Invalid field referenced in ORDER BY clause -- "
5253                                         "see error log for more details"
5254                                 );
5255                         free( order_buf );
5256                         return NULL;
5257                 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5258                         osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5259                                 modulename, field );
5260                         if( ctx )
5261                                 osrfAppSessionStatus(
5262                                         ctx->session,
5263                                         OSRF_STATUS_INTERNALSERVERERROR,
5264                                         "osrfMethodException",
5265                                         ctx->request,
5266                                         "Virtual field in ORDER BY clause -- see error log for more details"
5267                                 );
5268                         buffer_free( order_buf );
5269                         return NULL;
5270                 }
5271
5272                 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5273                         char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5274                         if( ! transform_str ) {
5275                                 if( ctx )
5276                                         osrfAppSessionStatus(
5277                                                 ctx->session,
5278                                                 OSRF_STATUS_INTERNALSERVERERROR,
5279                                                 "osrfMethodException",
5280                                                 ctx->request,
5281                                                 "Severe query error in ORDER BY clause -- "
5282                                                 "see error log for more details"
5283                                         );
5284                                 buffer_free( order_buf );
5285                                 return NULL;
5286                         }
5287
5288                         OSRF_BUFFER_ADD( order_buf, transform_str );
5289                         free( transform_str );
5290                 } else if( compare_to ) {
5291                         char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
5292                         if( ! compare_str ) {
5293                                 if( ctx )
5294                                         osrfAppSessionStatus(
5295                                                 ctx->session,
5296                                                 OSRF_STATUS_INTERNALSERVERERROR,
5297                                                 "osrfMethodException",
5298                                                 ctx->request,
5299                                                 "Severe query error in ORDER BY clause -- "
5300                                                 "see error log for more details"
5301                                         );
5302                                 buffer_free( order_buf );
5303                                 return NULL;
5304                         }
5305
5306                         buffer_fadd( order_buf, "(%s)", compare_str );
5307                         free( compare_str );
5308                 }
5309                 else
5310                         buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5311
5312                 const char* direction =
5313                         jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5314                 if( direction ) {
5315                         if( direction[ 0 ] && ( 'd' == direction[ 0 ] || 'D' == direction[ 0 ] ) )
5316                                 OSRF_BUFFER_ADD( order_buf, " DESC" );
5317                         else
5318                                 OSRF_BUFFER_ADD( order_buf, " ASC" );
5319                 }
5320         }
5321
5322         return buffer_release( order_buf );
5323 }
5324
5325 /**
5326         @brief Build a SELECT statement.
5327         @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5328         @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5329         @param meta Pointer to the class metadata for the core class.
5330         @param ctx Pointer to the method context.
5331         @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5332
5333         Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5334         "order_by", "limit", and "offset".
5335
5336         The SELECT statements built here are distinct from those built for the json_query method.
5337 */
5338 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5339         osrfHash* meta, osrfMethodContext* ctx ) {
5340
5341         const char* locale = osrf_message_get_last_locale();
5342
5343         osrfHash* fields = osrfHashGet( meta, "fields" );
5344         const char* core_class = osrfHashGet( meta, "classname" );
5345
5346         const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5347
5348         jsonObject* selhash = NULL;
5349         jsonObject* defaultselhash = NULL;
5350
5351         growing_buffer* sql_buf = buffer_init( 128 );
5352         growing_buffer* select_buf = buffer_init( 128 );
5353
5354         if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5355                 defaultselhash = jsonNewObjectType( JSON_HASH );
5356                 selhash = defaultselhash;
5357         }
5358
5359         // If there's no SELECT list for the core class, build one
5360         if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5361                 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5362
5363                 // Add every non-virtual field to the field list
5364                 osrfHash* field_def = NULL;
5365                 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5366                 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5367                         if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5368                                 const char* field = osrfHashIteratorKey( field_itr );
5369                                 jsonObjectPush( field_list, jsonNewObject( field ) );
5370                         }
5371                 }
5372                 osrfHashIteratorFree( field_itr );
5373                 jsonObjectSetKey( selhash, core_class, field_list );
5374         }
5375
5376         // Build a list of columns for the SELECT clause
5377         int first = 1;
5378         const jsonObject* snode = NULL;
5379         jsonIterator* class_itr = jsonNewIterator( selhash );
5380         while( (snode = jsonIteratorNext( class_itr )) ) {        // For each class
5381
5382                 // If the class isn't in the IDL, ignore it
5383                 const char* cname = class_itr->key;
5384                 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5385                 if( !idlClass )
5386                         continue;
5387
5388                 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5389                 if( strcmp( core_class, class_itr->key )) {
5390                         if( !join_hash )
5391                                 continue;
5392
5393                         jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5394                         if( !found->size ) {
5395                                 jsonObjectFree( found );
5396                                 continue;
5397                         }
5398
5399                         jsonObjectFree( found );
5400                 }
5401
5402                 const jsonObject* node = NULL;
5403                 jsonIterator* select_itr = jsonNewIterator( snode );
5404                 while( (node = jsonIteratorNext( select_itr )) ) {
5405                         const char* item_str = jsonObjectGetString( node );
5406                         osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5407                         char* fname = osrfHashGet( field, "name" );
5408
5409                         if( !field )
5410                                 continue;
5411
5412                         if (osrfStringArrayContains( osrfHashGet(field, "suppress_controller"), modulename ))
5413                                 continue;
5414
5415                         if( first ) {
5416                                 first = 0;
5417                         } else {
5418                                 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5419                         }
5420
5421                         if( locale ) {
5422                                 const char* i18n;
5423                                 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5424                                 if( obj_is_true( no_i18n_obj ) )    // Suppress internationalization?
5425                                         i18n = NULL;
5426                                 else
5427                                         i18n = osrfHashGet( field, "i18n" );
5428
5429                                 if( str_is_true( i18n ) ) {
5430                                         char* pkey = osrfHashGet( idlClass, "primarykey" );
5431                                         char* tname = osrfHashGet( idlClass, "tablename" );
5432
5433                                         buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5434                                                         "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5435                                                         tname, cname, fname, pkey, cname, pkey, locale, fname );
5436                                 } else {
5437                                         buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5438                                 }
5439                         } else {
5440                                 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5441                         }
5442                 }
5443
5444                 jsonIteratorFree( select_itr );
5445         }
5446
5447         jsonIteratorFree( class_itr );
5448
5449         char* col_list = buffer_release( select_buf );
5450         char* table = oilsGetRelation( meta );
5451         if( !table )
5452                 table = strdup( "(null)" );
5453
5454         buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5455         free( col_list );
5456         free( table );
5457
5458         // Clear the query stack (as a fail-safe precaution against possible
5459         // leftover garbage); then push the first query frame onto the stack.
5460         clear_query_stack();
5461         push_query_frame();
5462         if( add_query_core( NULL, core_class ) ) {
5463                 if( ctx )
5464                         osrfAppSessionStatus(
5465                                 ctx->session,
5466                                 OSRF_STATUS_INTERNALSERVERERROR,
5467                                 "osrfMethodException",
5468                                 ctx->request,
5469                                 "Unable to build query frame for core class"
5470                         );
5471                 buffer_free( sql_buf );
5472                 if( defaultselhash )
5473                         jsonObjectFree( defaultselhash );
5474                 return NULL;
5475         }
5476
5477         // Add the JOIN clauses, if any
5478         if( join_hash ) {
5479                 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5480                 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5481                 OSRF_BUFFER_ADD( sql_buf, join_clause );
5482                 free( join_clause );
5483         }
5484
5485         osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL =  %s",
5486                 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5487
5488         OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5489
5490         // Add the conditions in the WHERE clause
5491         char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5492         if( !pred ) {
5493                 osrfAppSessionStatus(
5494                         ctx->session,
5495                         OSRF_STATUS_INTERNALSERVERERROR,
5496                                 "osrfMethodException",
5497                                 ctx->request,
5498                                 "Severe query error -- see error log for more details"
5499                         );
5500                 buffer_free( sql_buf );
5501                 if( defaultselhash )
5502                         jsonObjectFree( defaultselhash );
5503                 clear_query_stack();
5504                 return NULL;
5505         } else {
5506                 buffer_add( sql_buf, pred );
5507                 free( pred );
5508         }
5509
5510         // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5511         if( rest_of_query ) {
5512                 const jsonObject* order_by = NULL;
5513                 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5514
5515                         char* order_by_list = NULL;
5516
5517                         if( JSON_ARRAY == order_by->type ) {
5518                                 order_by_list = buildOrderByFromArray( ctx, order_by );
5519                                 if( !order_by_list ) {
5520                                         buffer_free( sql_buf );
5521                                         if( defaultselhash )
5522                                                 jsonObjectFree( defaultselhash );
5523                                         clear_query_stack();
5524                                         return NULL;
5525                                 }
5526                         } else if( JSON_HASH == order_by->type ) {
5527                                 // We expect order_by to be a JSON_HASH keyed on class names.  Traverse it
5528                                 // and build a list of ORDER BY expressions.
5529                                 growing_buffer* order_buf = buffer_init( 128 );
5530                                 first = 1;
5531                                 jsonIterator* class_itr = jsonNewIterator( order_by );
5532                                 while( (snode = jsonIteratorNext( class_itr )) ) {  // For each class:
5533
5534                                         ClassInfo* order_class_info = search_alias( class_itr->key );
5535                                         if( ! order_class_info )
5536                                                 continue;    // class not referenced by FROM clause?  Ignore it.
5537
5538                                         if( JSON_HASH == snode->type ) {
5539
5540                                                 // If the data for the current class is a JSON_HASH, then it is
5541                                                 // keyed on field name.
5542
5543                                                 const jsonObject* onode = NULL;
5544                                                 jsonIterator* order_itr = jsonNewIterator( snode );
5545                                                 while( (onode = jsonIteratorNext( order_itr )) ) {  // For each field
5546
5547                                                         osrfHash* field_def = osrfHashGet(
5548                                                                 order_class_info->fields, order_itr->key );
5549                                                         if( !field_def )
5550                                                                 continue;    // Field not defined in IDL?  Ignore it.
5551                                                         if( str_is_true( osrfHashGet( field_def, "virtual")))
5552                                                                 continue;    // Field is virtual?  Ignore it.
5553
5554                                                         char* field_str = NULL;
5555                                                         char* direction = NULL;
5556                                                         if( onode->type == JSON_HASH ) {
5557                                                                 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5558                                                                         field_str = searchFieldTransform(
5559                                                                                 class_itr->key, field_def, onode );
5560                                                                         if( ! field_str ) {
5561                                                                                 osrfAppSessionStatus(
5562                                                                                         ctx->session,
5563                                                                                         OSRF_STATUS_INTERNALSERVERERROR,
5564                                                                                         "osrfMethodException",
5565                                                                                         ctx->request,
5566                                                                                         "Severe query error in ORDER BY clause -- "
5567                                                                                         "see error log for more details"
5568                                                                                 );
5569                                                                                 jsonIteratorFree( order_itr );
5570                                                                                 jsonIteratorFree( class_itr );
5571                                                                                 buffer_free( order_buf );
5572                                                                                 buffer_free( sql_buf );
5573                                                                                 if( defaultselhash )
5574                                                                                         jsonObjectFree( defaultselhash );
5575                                                                                 clear_query_stack();
5576                                                                                 return NULL;
5577                                                                         }
5578                                                                 } else {
5579                                                                         growing_buffer* field_buf = buffer_init( 16 );
5580                                                                         buffer_fadd( field_buf, "\"%s\".%s",
5581                                                                                 class_itr->key, order_itr->key );
5582                                                                         field_str = buffer_release( field_buf );
5583                                                                 }
5584
5585                                                                 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5586                                                                         const char* dir = jsonObjectGetString( order_by );
5587                                                                         if(!strncasecmp( dir, "d", 1 )) {
5588                                                                                 direction = " DESC";
5589                                                                         }
5590                                                                 }
5591                                                         } else {
5592                                                                 field_str = strdup( order_itr->key );
5593                                                                 const char* dir = jsonObjectGetString( onode );
5594                                                                 if( !strncasecmp( dir, "d", 1 )) {
5595                                                                         direction = " DESC";
5596                                                                 } else {
5597                                                                         direction = " ASC";
5598                                                                 }
5599                                                         }
5600
5601                                                         if( first ) {
5602                                                                 first = 0;
5603                                                         } else {
5604                                                                 buffer_add( order_buf, ", " );
5605                                                         }
5606
5607                                                         buffer_add( order_buf, field_str );
5608                                                         free( field_str );
5609
5610                                                         if( direction ) {
5611                                                                 buffer_add( order_buf, direction );
5612                                                         }
5613                                                 } // end while; looping over ORDER BY expressions
5614
5615                                                 jsonIteratorFree( order_itr );
5616
5617                                         } else if( JSON_STRING == snode->type ) {
5618                                                 // We expect a comma-separated list of sort fields.
5619                                                 const char* str = jsonObjectGetString( snode );
5620                                                 if( strchr( str, ';' )) {
5621                                                         // No semicolons allowed.  It is theoretically possible for a
5622                                                         // legitimate semicolon to occur within quotes, but it's not likely
5623                                                         // to occur in practice in the context of an ORDER BY list.
5624                                                         osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5625                                                                 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5626                                                         if( ctx ) {
5627                                                                 osrfAppSessionStatus(
5628                                                                         ctx->session,
5629                                                                         OSRF_STATUS_INTERNALSERVERERROR,
5630                                                                         "osrfMethodException",
5631                                                                         ctx->request,
5632                                                                         "Possible attempt at SOL injection -- "
5633                                                                                 "semicolon found in ORDER BY list"
5634                                                                 );
5635                                                         }
5636                                                         jsonIteratorFree( class_itr );
5637                                                         buffer_free( order_buf );
5638                                                         buffer_free( sql_buf );
5639                                                         if( defaultselhash )
5640                                                                 jsonObjectFree( defaultselhash );
5641                                                         clear_query_stack();
5642                                                         return NULL;
5643                                                 }
5644                                                 buffer_add( order_buf, str );
5645                                                 break;
5646                                         }
5647
5648                                 } // end while; looping over order_by classes
5649
5650                                 jsonIteratorFree( class_itr );
5651                                 order_by_list = buffer_release( order_buf );
5652
5653                         } else {
5654                                 osrfLogWarning( OSRF_LOG_MARK,
5655                                         "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5656                                         "no ORDER BY generated" );
5657                         }
5658
5659                         if( order_by_list && *order_by_list ) {
5660                                 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5661                                 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5662                         }
5663
5664                         free( order_by_list );
5665                 }
5666
5667                 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5668                 if( limit ) {
5669                         const char* str = jsonObjectGetString( limit );
5670                         if (str) {
5671                                 buffer_fadd(
5672                                         sql_buf,
5673                                         " LIMIT %d",
5674                                         atoi(str)
5675                                 );
5676                         }
5677                 }
5678
5679                 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5680                 if( offset ) {
5681                         const char* str = jsonObjectGetString( offset );
5682                         if (str) {
5683                                 buffer_fadd(
5684                                         sql_buf,
5685                                         " OFFSET %d",
5686                                         atoi( str )
5687                                 );
5688                         }
5689                 }
5690         }
5691
5692         if( defaultselhash )
5693                 jsonObjectFree( defaultselhash );
5694         clear_query_stack();
5695
5696         OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5697         return buffer_release( sql_buf );
5698 }
5699
5700 int doJSONSearch ( osrfMethodContext* ctx ) {
5701         if(osrfMethodVerifyContext( ctx )) {
5702                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
5703                 return -1;
5704         }
5705
5706         osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5707
5708         int err = 0;
5709
5710         jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5711
5712         int flags = 0;
5713
5714         if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5715                 flags |= SELECT_DISTINCT;
5716
5717         if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5718                 flags |= DISABLE_I18N;
5719
5720         osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5721         clear_query_stack();       // a possibly needless precaution
5722         char* sql = buildQuery( ctx, hash, flags );
5723         clear_query_stack();
5724
5725         if( !sql ) {
5726                 err = -1;
5727                 return err;
5728         }
5729
5730         osrfLogDebug( OSRF_LOG_MARK, "%s SQL =  %s", modulename, sql );
5731
5732         // XXX for now...
5733         dbhandle = writehandle;
5734
5735         dbi_result result = dbi_conn_query( dbhandle, sql );
5736
5737         if( result ) {
5738                 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5739
5740                 if( dbi_result_first_row( result )) {
5741                         /* JSONify the result */
5742                         osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5743
5744                         do {
5745                                 jsonObject* return_val = oilsMakeJSONFromResult( result );
5746                                 osrfAppRespond( ctx, return_val );
5747                                 jsonObjectFree( return_val );
5748                         } while( dbi_result_next_row( result ));
5749
5750                 } else {
5751                         osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5752                 }
5753
5754                 osrfAppRespondComplete( ctx, NULL );
5755
5756                 /* clean up the query */
5757                 dbi_result_free( result );
5758
5759         } else {
5760                 err = -1;
5761                 const char* msg;
5762                 int errnum = dbi_conn_error( dbhandle, &msg );
5763                 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5764                         modulename, sql, errnum, msg ? msg : "(No description available)" );
5765                 osrfAppSessionStatus(
5766                         ctx->session,
5767                         OSRF_STATUS_INTERNALSERVERERROR,
5768                         "osrfMethodException",
5769                         ctx->request,
5770                         "Severe query error -- see error log for more details"
5771                 );
5772                 if( !oilsIsDBConnected( dbhandle ))
5773                         osrfAppSessionPanic( ctx->session );
5774         }
5775
5776         free( sql );
5777         return err;
5778 }
5779
5780 // The last parameter, err, is used to report an error condition by updating an int owned by
5781 // the calling code.
5782
5783 // In case of an error, we set *err to -1.  If there is no error, *err is left unchanged.
5784 // It is the responsibility of the calling code to initialize *err before the
5785 // call, so that it will be able to make sense of the result.
5786
5787 // Note also that we return NULL if and only if we set *err to -1.  So the err parameter is
5788 // redundant anyway.
5789 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5790                 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5791
5792         // XXX for now...
5793         dbhandle = writehandle;
5794
5795         char* core_class = osrfHashGet( class_meta, "classname" );
5796         osrfLogDebug( OSRF_LOG_MARK, "entering doFieldmapperSearch() with core_class %s", core_class );
5797
5798         char* pkey = osrfHashGet( class_meta, "primarykey" );
5799
5800         if (!ctx->session->userData)
5801                 (void) initSessionCache( ctx );
5802
5803         char *methodtype = osrfHashGet( (osrfHash *) ctx->method->userData, "methodtype" );
5804         char *inside_verify = osrfHashGet( (osrfHash*) ctx->session->userData, "inside_verify" );
5805         int need_to_verify = (inside_verify ? !atoi(inside_verify) : 1);
5806
5807         int i_respond_directly = 0;
5808         int flesh_depth = 0;
5809
5810         char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5811         if( !sql ) {
5812                 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5813                 *err = -1;
5814                 return NULL;
5815         }
5816
5817         osrfLogDebug( OSRF_LOG_MARK, "%s SQL =  %s", modulename, sql );
5818
5819         dbi_result result = dbi_conn_query( dbhandle, sql );
5820         if( NULL == result ) {
5821                 const char* msg;
5822                 int errnum = dbi_conn_error( dbhandle, &msg );
5823                 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5824                         modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5825                         msg ? msg : "(No description available)" );
5826                 if( !oilsIsDBConnected( dbhandle ))
5827                         osrfAppSessionPanic( ctx->session );
5828                 osrfAppSessionStatus(
5829                         ctx->session,
5830                         OSRF_STATUS_INTERNALSERVERERROR,
5831                         "osrfMethodException",
5832                         ctx->request,
5833                         "Severe query error -- see error log for more details"
5834                 );
5835                 *err = -1;
5836                 free( sql );
5837                 return NULL;
5838
5839         } else {
5840                 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5841         }
5842
5843         jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5844         jsonObject* row_obj = NULL;
5845
5846         // The following two steps are for verifyObjectPCRUD()'s benefit.
5847         // 1. get the flesh depth
5848         const jsonObject* _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5849         if( _tmp ) {
5850                 flesh_depth = (int) jsonObjectGetNumber( _tmp );
5851                 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5852                         flesh_depth = max_flesh_depth;
5853         }
5854
5855         // 2. figure out one consistent rs_size for verifyObjectPCRUD to use
5856         // over the whole life of this request.  This means if we've already set
5857         // up a rs_size_req_%d, do nothing.
5858         //      a. Incidentally, we can also use this opportunity to set i_respond_directly
5859         int *rs_size = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
5860         if( !rs_size ) {        // pointer null, so value not set in hash
5861                 // i_respond_directly can only be true at the /top/ of a recursive search, if even that.
5862                 i_respond_directly = ( *methodtype == 'r' || *methodtype == 'i' || *methodtype == 's' );
5863
5864                 rs_size = (int *) safe_malloc( sizeof(int) );   // will be freed by sessionDataFree()
5865                 unsigned long long result_count = dbi_result_get_numrows( result );
5866                 *rs_size = (int) result_count * (flesh_depth + 1);      // yes, we could lose some bits, but come on
5867                 osrfHashSet( (osrfHash *) ctx->session->userData, rs_size, "rs_size_req_%d", ctx->request );
5868         }
5869
5870         if( dbi_result_first_row( result )) {
5871
5872                 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5873                 // in a JSON_ARRAY of rows.  If two or more rows have the same key value, then
5874                 // eliminate the duplicates.
5875                 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5876                 osrfHash* dedup = osrfNewHash();
5877                 do {
5878                         row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5879                         char* pkey_val = oilsFMGetString( row_obj, pkey );
5880                         if( osrfHashGet( dedup, pkey_val ) ) {
5881                                 jsonObjectFree( row_obj );
5882                                 free( pkey_val );
5883                         } else {
5884                                 if( !enforce_pcrud || !need_to_verify ||
5885                                                 verifyObjectPCRUD( ctx, class_meta, row_obj, 0 /* means check user data for rs_size */ )) {
5886                                         osrfHashSet( dedup, pkey_val, pkey_val );
5887                                         jsonObjectPush( res_list, row_obj );
5888                                 }
5889                         }
5890                 } while( dbi_result_next_row( result ));
5891                 osrfHashFree( dedup );
5892
5893         } else {
5894                 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5895                         modulename, sql );
5896         }
5897
5898         /* clean up the query */
5899         dbi_result_free( result );
5900         free( sql );
5901
5902         // If we're asked to flesh, and there's anything to flesh, then flesh it
5903         // (formerly we would skip fleshing if in pcrud mode, but now we support
5904         // fleshing even in PCRUD).
5905         if( res_list->size ) {
5906                 jsonObject* temp_blob;  // We need a non-zero flesh depth, and a list of fields to flesh
5907                 jsonObject* flesh_fields; 
5908                 jsonObject* flesh_blob = NULL;
5909                 osrfStringArray* link_fields = NULL;
5910                 osrfHash* links = NULL;
5911                 int want_flesh = 0;
5912
5913                 if( query_hash ) {
5914                         temp_blob = jsonObjectGetKey( query_hash, "flesh_fields" );
5915                         if( temp_blob && flesh_depth > 0 ) {
5916
5917                                 flesh_blob = jsonObjectClone( temp_blob );
5918                                 flesh_fields = jsonObjectGetKey( flesh_blob, core_class );
5919
5920                                 links = osrfHashGet( class_meta, "links" );
5921
5922                                 // Make an osrfStringArray of the names of fields to be fleshed
5923                                 if( flesh_fields ) {
5924                                         if( flesh_fields->size == 1 ) {
5925                                                 const char* _t = jsonObjectGetString(
5926                                                         jsonObjectGetIndex( flesh_fields, 0 ) );
5927                                                 if( !strcmp( _t, "*" ))
5928                                                         link_fields = osrfHashKeys( links );
5929                                         }
5930
5931                                         if( !link_fields ) {
5932                                                 jsonObject* _f;
5933                                                 link_fields = osrfNewStringArray( 1 );
5934                                                 jsonIterator* _i = jsonNewIterator( flesh_fields );
5935                                                 while ((_f = jsonIteratorNext( _i ))) {
5936                                                         osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5937                                                 }
5938                                                 jsonIteratorFree( _i );
5939                                         }
5940                                 }
5941                                 want_flesh = link_fields ? 1 : 0;
5942                         }
5943                 }
5944
5945                 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5946
5947                 // Iterate over the JSON_ARRAY of rows
5948                 jsonObject* cur;
5949                 unsigned long res_idx = 0;
5950                 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5951
5952                         int i = 0;
5953                         const char* link_field;
5954
5955                         // Iterate over the list of fleshable fields
5956                         if ( want_flesh ) {
5957                                 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5958
5959                                         osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5960
5961                                         osrfHash* kid_link = osrfHashGet( links, link_field );
5962                                         if( !kid_link )
5963                                                 continue;     // Not a link field; skip it
5964
5965                                         osrfHash* field = osrfHashGet( fields, link_field );
5966                                         if( !field )
5967                                                 continue;     // Not a field at all; skip it (IDL is ill-formed)
5968
5969                                         osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5970                                                 osrfHashGet( kid_link, "class" ));
5971                                         if( !kid_idl )
5972                                                 continue;   // The class it links to doesn't exist; skip it
5973
5974                                         const char* reltype = osrfHashGet( kid_link, "reltype" );
5975                                         if( !reltype )
5976                                                 continue;   // No reltype; skip it (IDL is ill-formed)
5977
5978                                         osrfHash* value_field = field;
5979
5980                                         if(    !strcmp( reltype, "has_many" )
5981                                                 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5982                                                 value_field = osrfHashGet(
5983                                                         fields, osrfHashGet( class_meta, "primarykey" ) );
5984                                         }
5985
5986                                         int kid_has_controller = osrfStringArrayContains( osrfHashGet(kid_idl, "controller"), modulename );
5987                                         // fleshing pcrud case: we require the controller in need_to_verify mode
5988                                         if ( !kid_has_controller && enforce_pcrud && need_to_verify ) {
5989                                                 osrfLogInfo( OSRF_LOG_MARK, "%s is not listed as a controller for %s; moving on", modulename, core_class );
5990
5991                                                 jsonObjectSetIndex(
5992                                                         cur,
5993                                                         (unsigned long) atoi( osrfHashGet(field, "array_position") ),
5994                                                         jsonNewObjectType(
5995                                                                 !strcmp( reltype, "has_many" ) ? JSON_ARRAY : JSON_NULL
5996                                                         )
5997                                                 );
5998                                                 continue;
5999                                         }
6000
6001                                         osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
6002
6003                                         if( link_map->size > 0 ) {
6004                                                 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
6005                                                 jsonObjectPush(
6006                                                         _kid_key,
6007                                                         jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
6008                                                 );
6009
6010                                                 jsonObjectSetKey(
6011                                                         flesh_blob,
6012                                                         osrfHashGet( kid_link, "class" ),
6013                                                         _kid_key
6014                                                 );
6015                                         };
6016
6017                                         osrfLogDebug(
6018                                                 OSRF_LOG_MARK,
6019                                                 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
6020                                                 osrfHashGet( kid_link, "field" ),
6021                                                 osrfHashGet( kid_link, "class" ),
6022                                                 osrfHashGet( kid_link, "key" ),
6023                                                 osrfHashGet( kid_link, "reltype" )
6024                                         );
6025
6026                                         const char* search_key = jsonObjectGetString(
6027                                                 jsonObjectGetIndex( cur,
6028                                                         atoi( osrfHashGet( value_field, "array_position" ) )
6029                                                 )
6030                                         );
6031
6032                                         if( !search_key ) {
6033                                                 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
6034                                                 continue;
6035                                         }
6036
6037                                         osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
6038
6039                                         // construct WHERE clause
6040                                         jsonObject* where_clause  = jsonNewObjectType( JSON_HASH );
6041                                         jsonObjectSetKey(
6042                                                 where_clause,
6043                                                 osrfHashGet( kid_link, "key" ),
6044                                                 jsonNewObject( search_key )
6045                                         );
6046
6047                                         // construct the rest of the query, mostly
6048                                         // by copying pieces of the previous level of query
6049                                         jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
6050                                         jsonObjectSetKey( rest_of_query, "flesh",
6051                                                 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
6052                                         );
6053
6054                                         if( flesh_blob )
6055                                                 jsonObjectSetKey( rest_of_query, "flesh_fields",
6056                                                         jsonObjectClone( flesh_blob ));
6057
6058                                         if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
6059                                                 jsonObjectSetKey( rest_of_query, "order_by",
6060                                                         jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
6061                                                 );
6062                                         }
6063
6064                                         if( jsonObjectGetKeyConst( query_hash, "select" )) {
6065                                                 jsonObjectSetKey( rest_of_query, "select",
6066                                                         jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
6067                                                 );
6068                                         }
6069
6070                                         // do the query, recursively, to expand the fleshable field
6071                                         jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
6072                                                 where_clause, rest_of_query, err );
6073
6074                                         jsonObjectFree( where_clause );
6075                                         jsonObjectFree( rest_of_query );
6076
6077                                         if( *err ) {
6078                                                 osrfStringArrayFree( link_fields );
6079                                                 jsonObjectFree( res_list );
6080                                                 jsonObjectFree( flesh_blob );
6081                                                 return NULL;
6082                                         }
6083
6084                                         osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
6085                                                 osrfHashGet( kid_link, "class" ), kids->size );
6086
6087                                         // Traverse the result set
6088                                         jsonObject* X = NULL;
6089                                         if( link_map->size > 0 && kids->size > 0 ) {
6090                                                 X = kids;
6091                                                 kids = jsonNewObjectType( JSON_ARRAY );
6092
6093                                                 jsonObject* _k_node;
6094                                                 unsigned long res_idx = 0;
6095                                                 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
6096                                                         jsonObjectPush(
6097                                                                 kids,
6098                                                                 jsonObjectClone(
6099                                                                         jsonObjectGetIndex(
6100                                                                                 _k_node,
6101                                                                                 (unsigned long) atoi(
6102                                                                                         osrfHashGet(
6103                                                                                                 osrfHashGet(
6104                                                                                                         osrfHashGet(
6105                                                                                                                 osrfHashGet(
6106                                                                                                                         oilsIDL(),
6107                                                                                                                         osrfHashGet( kid_link, "class" )
6108                                                                                                                 ),
6109                                                                                                                 "fields"
6110                                                                                                         ),
6111                                                                                                         osrfStringArrayGetString( link_map, 0 )
6112                                                                                                 ),
6113                                                                                                 "array_position"
6114                                                                                         )
6115                                                                                 )
6116                                                                         )
6117                                                                 )
6118                                                         );
6119                                                 } // end while loop traversing X
6120                                         }
6121
6122                                         if (kids->size > 0) {
6123
6124                                                 if((   !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
6125                                                         || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" ))
6126                                                 ) {
6127                                                         osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
6128                                                                 osrfHashGet( kid_link, "field" ));
6129                                                         jsonObjectSetIndex(
6130                                                                 cur,
6131                                                                 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6132                                                                 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
6133                                                         );
6134                                                 }
6135                                         }
6136
6137                                         if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
6138                                                 // has_many
6139                                                 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
6140                                                         osrfHashGet( kid_link, "field" ) );
6141                                                 jsonObjectSetIndex(
6142                                                         cur,
6143                                                         (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6144                                                         jsonObjectClone( kids )
6145                                                 );
6146                                         }
6147
6148                                         if( X ) {
6149                                                 jsonObjectFree( kids );
6150                                                 kids = X;
6151                                         }
6152
6153                                         jsonObjectFree( kids );
6154
6155                                         osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
6156                                                 osrfHashGet( kid_link, "field" ) );
6157                                         osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
6158
6159                                 } // end while loop traversing list of fleshable fields
6160                         }
6161
6162                         if( i_respond_directly ) {
6163                                 if ( *methodtype == 'i' ) {
6164                                         osrfAppRespond( ctx,
6165                                                 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
6166                                 } else {
6167                                         osrfAppRespond( ctx, cur );
6168                                 }
6169                         }
6170                 } // end while loop traversing res_list
6171                 jsonObjectFree( flesh_blob );
6172                 osrfStringArrayFree( link_fields );
6173         }
6174
6175         if( i_respond_directly ) {
6176                 jsonObjectFree( res_list );
6177                 return jsonNewObjectType( JSON_ARRAY );
6178         } else {
6179                 return res_list;
6180         }
6181 }
6182
6183
6184 int doUpdate( osrfMethodContext* ctx ) {
6185         if( osrfMethodVerifyContext( ctx )) {
6186                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6187                 return -1;
6188         }
6189
6190         if( enforce_pcrud )
6191                 timeout_needs_resetting = 1;
6192
6193         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6194
6195         jsonObject* target = NULL;
6196         if( enforce_pcrud )
6197                 target = jsonObjectGetIndex( ctx->params, 1 );
6198         else
6199                 target = jsonObjectGetIndex( ctx->params, 0 );
6200
6201         if(!verifyObjectClass( ctx, target )) {
6202                 osrfAppRespondComplete( ctx, NULL );
6203                 return -1;
6204         }
6205
6206         if( getXactId( ctx ) == NULL ) {
6207                 osrfAppSessionStatus(
6208                         ctx->session,
6209                         OSRF_STATUS_BADREQUEST,
6210                         "osrfMethodException",
6211                         ctx->request,
6212                         "No active transaction -- required for UPDATE"
6213                 );
6214                 osrfAppRespondComplete( ctx, NULL );
6215                 return -1;
6216         }
6217
6218         // The following test is harmless but redundant.  If a class is
6219         // readonly, we don't register an update method for it.
6220         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6221                 osrfAppSessionStatus(
6222                         ctx->session,
6223                         OSRF_STATUS_BADREQUEST,
6224                         "osrfMethodException",
6225                         ctx->request,
6226                         "Cannot UPDATE readonly class"
6227                 );
6228                 osrfAppRespondComplete( ctx, NULL );
6229                 return -1;
6230         }
6231
6232         const char* trans_id = getXactId( ctx );
6233
6234         // Set the last_xact_id
6235         int index = oilsIDL_ntop( target->classname, "last_xact_id" );
6236         if( index > -1 ) {
6237                 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
6238                                 trans_id, target->classname, index );
6239                 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
6240         }
6241
6242         char* pkey = osrfHashGet( meta, "primarykey" );
6243         osrfHash* fields = osrfHashGet( meta, "fields" );
6244
6245         char* id = oilsFMGetString( target, pkey );
6246
6247         osrfLogDebug(
6248                 OSRF_LOG_MARK,
6249                 "%s updating %s object with %s = %s",
6250                 modulename,
6251                 osrfHashGet( meta, "fieldmapper" ),
6252                 pkey,
6253                 id
6254         );
6255
6256         dbhandle = writehandle;
6257         growing_buffer* sql = buffer_init( 128 );
6258         buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
6259
6260         int first = 1;
6261         osrfHash* field_def = NULL;
6262         osrfHashIterator* field_itr = osrfNewHashIterator( fields );
6263         while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
6264
6265                 // Skip virtual fields, and the primary key
6266                 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
6267                         continue;
6268
6269                 if (osrfStringArrayContains( osrfHashGet(field_def, "suppress_controller"), modulename ))
6270                         continue;
6271
6272
6273                 const char* field_name = osrfHashIteratorKey( field_itr );
6274                 if( ! strcmp( field_name, pkey ) )
6275                         continue;
6276
6277                 const jsonObject* field_object = oilsFMGetObject( target, field_name );
6278
6279                 int value_is_numeric = 0;    // boolean
6280                 char* value;
6281                 if( field_object && field_object->classname ) {
6282                         value = oilsFMGetString(
6283                                 field_object,
6284                                 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
6285                         );
6286                 } else if( field_object && JSON_BOOL == field_object->type ) {
6287                         if( jsonBoolIsTrue( field_object ) )
6288                                 value = strdup( "t" );
6289                         else
6290                                 value = strdup( "f" );
6291                 } else {
6292                         value = jsonObjectToSimpleString( field_object );
6293                         if( field_object && JSON_NUMBER == field_object->type )
6294                                 value_is_numeric = 1;
6295                 }
6296
6297                 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
6298                                 osrfHashGet( meta, "fieldmapper" ), field_name, value);
6299
6300                 if( !field_object || field_object->type == JSON_NULL ) {
6301                         if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
6302                                         && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
6303                                 if( first )
6304                                         first = 0;
6305                                 else
6306                                         OSRF_BUFFER_ADD_CHAR( sql, ',' );
6307                                 buffer_fadd( sql, " %s = NULL", field_name );
6308                         }
6309
6310                 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
6311                         if( first )
6312                                 first = 0;
6313                         else
6314                                 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6315
6316                         const char* numtype = get_datatype( field_def );
6317                         if( !strncmp( numtype, "INT", 3 ) ) {
6318                                 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
6319                         } else if( !strcmp( numtype, "NUMERIC" ) ) {
6320                                 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
6321                         } else {
6322                                 // Must really be intended as a string, so quote it
6323                                 if( dbi_conn_quote_string( dbhandle, &value )) {
6324                                         buffer_fadd( sql, " %s = %s", field_name, value );
6325                                 } else {
6326                                         osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
6327                                                 modulename, value );
6328                                         osrfAppSessionStatus(
6329                                                 ctx->session,
6330                                                 OSRF_STATUS_INTERNALSERVERERROR,
6331                                                 "osrfMethodException",
6332                                                 ctx->request,
6333                                                 "Error quoting string -- please see the error log for more details"
6334                                         );
6335                                         free( value );
6336                                         free( id );
6337                                         osrfHashIteratorFree( field_itr );
6338                                         buffer_free( sql );
6339                                         osrfAppRespondComplete( ctx, NULL );
6340                                         return -1;
6341                                 }
6342                         }
6343
6344                         osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
6345
6346                 } else {
6347                         if( dbi_conn_quote_string( dbhandle, &value ) ) {
6348                                 if( first )
6349                                         first = 0;
6350                                 else
6351                                         OSRF_BUFFER_ADD_CHAR( sql, ',' );
6352                                 buffer_fadd( sql, " %s = %s", field_name, value );
6353                         } else {
6354                                 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
6355                                 osrfAppSessionStatus(
6356                                         ctx->session,
6357                                         OSRF_STATUS_INTERNALSERVERERROR,
6358                                         "osrfMethodException",
6359                                         ctx->request,
6360                                         "Error quoting string -- please see the error log for more details"
6361                                 );
6362                                 free( value );
6363                                 free( id );
6364                                 osrfHashIteratorFree( field_itr );
6365                                 buffer_free( sql );
6366                                 osrfAppRespondComplete( ctx, NULL );
6367                                 return -1;
6368                         }
6369                 }
6370
6371                 free( value );
6372
6373         } // end while
6374
6375         osrfHashIteratorFree( field_itr );
6376
6377         jsonObject* obj = jsonNewObject( id );
6378
6379         if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6380                 dbi_conn_quote_string( dbhandle, &id );
6381
6382         buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6383
6384         char* query = buffer_release( sql );
6385         osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6386
6387         dbi_result result = dbi_conn_query( dbhandle, query );
6388         free( query );
6389
6390         int rc = 0;
6391         if( !result ) {
6392                 jsonObjectFree( obj );
6393                 obj = jsonNewObject( NULL );
6394                 const char* msg;
6395                 int errnum = dbi_conn_error( dbhandle, &msg );
6396                 osrfLogError(
6397                         OSRF_LOG_MARK,
6398                         "%s ERROR updating %s object with %s = %s: %d %s",
6399                         modulename,
6400                         osrfHashGet( meta, "fieldmapper" ),
6401                         pkey,
6402                         id,
6403                         errnum,
6404                         msg ? msg : "(No description available)"
6405                 );
6406                 osrfAppSessionStatus(
6407                         ctx->session,
6408                         OSRF_STATUS_INTERNALSERVERERROR,
6409                         "osrfMethodException",
6410                         ctx->request,
6411                         "Error in updating a row -- please see the error log for more details"
6412                 );
6413                 if( !oilsIsDBConnected( dbhandle ))
6414                         osrfAppSessionPanic( ctx->session );
6415                 rc = -1;
6416         } else
6417                 dbi_result_free( result );
6418
6419         free( id );
6420         osrfAppRespondComplete( ctx, obj );
6421         jsonObjectFree( obj );
6422         return rc;
6423 }
6424
6425 int doDelete( osrfMethodContext* ctx ) {
6426         if( osrfMethodVerifyContext( ctx )) {
6427                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6428                 return -1;
6429         }
6430
6431         if( enforce_pcrud )
6432                 timeout_needs_resetting = 1;
6433
6434         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6435
6436         if( getXactId( ctx ) == NULL ) {
6437                 osrfAppSessionStatus(
6438                         ctx->session,
6439                         OSRF_STATUS_BADREQUEST,
6440                         "osrfMethodException",
6441                         ctx->request,
6442                         "No active transaction -- required for DELETE"
6443                 );
6444                 osrfAppRespondComplete( ctx, NULL );
6445                 return -1;
6446         }
6447
6448         // The following test is harmless but redundant.  If a class is
6449         // readonly, we don't register a delete method for it.
6450         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6451                 osrfAppSessionStatus(
6452                         ctx->session,
6453                         OSRF_STATUS_BADREQUEST,
6454                         "osrfMethodException",
6455                         ctx->request,
6456                         "Cannot DELETE readonly class"
6457                 );
6458                 osrfAppRespondComplete( ctx, NULL );
6459                 return -1;
6460         }
6461
6462         dbhandle = writehandle;
6463
6464         char* pkey = osrfHashGet( meta, "primarykey" );
6465
6466         int _obj_pos = 0;
6467         if( enforce_pcrud )
6468                 _obj_pos = 1;
6469
6470         char* id;
6471         if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6472                 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6473                         osrfAppRespondComplete( ctx, NULL );
6474                         return -1;
6475                 }
6476
6477                 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6478         } else {
6479                 if( enforce_pcrud && !verifyObjectPCRUD( ctx, meta, NULL, 1 )) {
6480                         osrfAppRespondComplete( ctx, NULL );
6481                         return -1;
6482                 }
6483                 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6484         }
6485
6486         osrfLogDebug(
6487                 OSRF_LOG_MARK,
6488                 "%s deleting %s object with %s = %s",
6489                 modulename,
6490                 osrfHashGet( meta, "fieldmapper" ),
6491                 pkey,
6492                 id
6493         );
6494
6495         jsonObject* obj = jsonNewObject( id );
6496
6497         if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6498                 dbi_conn_quote_string( writehandle, &id );
6499
6500         dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6501                 osrfHashGet( meta, "tablename" ), pkey, id );
6502
6503         int rc = 0;
6504         if( !result ) {
6505                 rc = -1;
6506                 jsonObjectFree( obj );
6507                 obj = jsonNewObject( NULL );
6508                 const char* msg;
6509                 int errnum = dbi_conn_error( writehandle, &msg );
6510                 osrfLogError(
6511                         OSRF_LOG_MARK,
6512                         "%s ERROR deleting %s object with %s = %s: %d %s",
6513                         modulename,
6514                         osrfHashGet( meta, "fieldmapper" ),
6515                         pkey,
6516                         id,
6517                         errnum,
6518                         msg ? msg : "(No description available)"
6519                 );
6520                 osrfAppSessionStatus(
6521                         ctx->session,
6522                         OSRF_STATUS_INTERNALSERVERERROR,
6523                         "osrfMethodException",
6524                         ctx->request,
6525                         "Error in deleting a row -- please see the error log for more details"
6526                 );
6527                 if( !oilsIsDBConnected( writehandle ))
6528                         osrfAppSessionPanic( ctx->session );
6529         } else
6530                 dbi_result_free( result );
6531
6532         free( id );
6533
6534         osrfAppRespondComplete( ctx, obj );
6535         jsonObjectFree( obj );
6536         return rc;
6537 }
6538
6539 /**
6540         @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6541         @param result An iterator for a result set; we only look at the current row.
6542         @param @meta Pointer to the class metadata for the core class.
6543         @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6544
6545         If a column is not defined in the IDL, or if it has no array_position defined for it in
6546         the IDL, or if it is defined as virtual, ignore it.
6547
6548         Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6549         or JSON_STRING.  Then insert this jsonObject into the JSON_ARRAY according to its
6550         array_position in the IDL.
6551
6552         A field defined in the IDL but not represented in the returned row will leave a hole
6553         in the JSON_ARRAY.  In effect it will be treated as a null value.
6554
6555         In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6556         regardless of their sequence in the SELECT statement.  The JSON_ARRAY is assigned the
6557         classname corresponding to the @a meta argument.
6558
6559         The calling code is responsible for freeing the the resulting jsonObject by calling
6560         jsonObjectFree().
6561 */
6562 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6563         if( !( result && meta )) return NULL;
6564
6565         jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6566         jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6567         osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6568
6569         osrfHash* fields = osrfHashGet( meta, "fields" );
6570
6571         int columnIndex = 1;
6572         const char* columnName;
6573
6574         /* cycle through the columns in the row returned from the database */
6575         while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6576
6577                 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6578
6579                 int fmIndex = -1;  // Will be set to the IDL's sequence number for this field
6580
6581                 /* determine the field type and storage attributes */
6582                 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6583                 int attr            = dbi_result_get_field_attribs_idx( result, columnIndex );
6584
6585                 // Fetch the IDL's sequence number for the field.  If the field isn't in the IDL,
6586                 // or if it has no sequence number there, or if it's virtual, skip it.
6587                 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6588                 if( _f ) {
6589
6590                         if( str_is_true( osrfHashGet( _f, "virtual" )))
6591                                 continue;   // skip this column: IDL says it's virtual
6592
6593                         const char* pos = (char*) osrfHashGet( _f, "array_position" );
6594                         if( !pos )      // IDL has no sequence number for it.  This shouldn't happen,
6595                                 continue;    // since we assign sequence numbers dynamically as we load the IDL.
6596
6597                         fmIndex = atoi( pos );
6598                         osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6599                 } else {
6600                         continue;     // This field is not defined in the IDL
6601                 }
6602
6603                 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6604                 // sequence number from the IDL (which is likely to be different from the sequence
6605                 // of columns in the SELECT clause).
6606                 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6607                         jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6608                 } else {
6609
6610                         switch( type ) {
6611
6612                                 case DBI_TYPE_INTEGER :
6613
6614                                         if( attr & DBI_INTEGER_SIZE8 )
6615                                                 jsonObjectSetIndex( object, fmIndex,
6616                                                         jsonNewNumberObject(
6617                                                                 dbi_result_get_longlong_idx( result, columnIndex )));
6618                                         else
6619                                                 jsonObjectSetIndex( object, fmIndex,
6620                                                         jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6621
6622                                         break;
6623
6624                                 case DBI_TYPE_DECIMAL :
6625                                         jsonObjectSetIndex( object, fmIndex,
6626                                                         jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6627                                         break;
6628
6629                                 case DBI_TYPE_STRING :
6630
6631                                         jsonObjectSetIndex(
6632                                                 object,
6633                                                 fmIndex,
6634                                                 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6635                                         );
6636
6637                                         break;
6638
6639                                 case DBI_TYPE_DATETIME : {
6640
6641                                         char dt_string[ 256 ] = "";
6642                                         struct tm gmdt;
6643
6644                                         // Fetch the date column as a time_t
6645                                         time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6646
6647                                         // Translate the time_t to a human-readable string
6648                                         if( !( attr & DBI_DATETIME_DATE )) {
6649                                                 gmtime_r( &_tmp_dt, &gmdt );
6650                                                 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6651                                         } else if( !( attr & DBI_DATETIME_TIME )) {
6652                                                 localtime_r( &_tmp_dt, &gmdt );
6653                                                 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6654                                         } else {
6655                                                 localtime_r( &_tmp_dt, &gmdt );
6656                                                 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6657                                         }
6658
6659                                         jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6660
6661                                         break;
6662                                 }
6663                                 case DBI_TYPE_BINARY :
6664                                         osrfLogError( OSRF_LOG_MARK,
6665                                                 "Can't do binary at column %s : index %d", columnName, columnIndex );
6666                         } // End switch
6667                 }
6668                 ++columnIndex;
6669         } // End while
6670
6671         return object;
6672 }
6673
6674 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6675         if( !result ) return NULL;
6676
6677         jsonObject* object = jsonNewObject( NULL );
6678
6679         time_t _tmp_dt;
6680         char dt_string[ 256 ];
6681         struct tm gmdt;
6682
6683         int fmIndex;
6684         int columnIndex = 1;
6685         int attr;
6686         unsigned short type;
6687         const char* columnName;
6688
6689         /* cycle through the column list */
6690         while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6691
6692                 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6693
6694                 fmIndex = -1; // reset the position
6695
6696                 /* determine the field type and storage attributes */
6697                 type = dbi_result_get_field_type_idx( result, columnIndex );
6698                 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6699
6700                 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6701                         jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6702                 } else {
6703
6704                         switch( type ) {
6705
6706                                 case DBI_TYPE_INTEGER :
6707
6708                                         if( attr & DBI_INTEGER_SIZE8 )
6709                                                 jsonObjectSetKey( object, columnName,
6710                                                                 jsonNewNumberObject( dbi_result_get_longlong_idx(
6711                                                                                 result, columnIndex )) );
6712                                         else
6713                                                 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6714                                                                 dbi_result_get_int_idx( result, columnIndex )) );
6715                                         break;
6716
6717                                 case DBI_TYPE_DECIMAL :
6718                                         jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6719                                                 dbi_result_get_double_idx( result, columnIndex )) );
6720                                         break;
6721
6722                                 case DBI_TYPE_STRING :
6723                                         jsonObjectSetKey( object, columnName,
6724                                                 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6725                                         break;
6726
6727                                 case DBI_TYPE_DATETIME :
6728
6729                                         memset( dt_string, '\0', sizeof( dt_string ));
6730                                         memset( &gmdt, '\0', sizeof( gmdt ));
6731
6732                                         _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6733
6734                                         if( !( attr & DBI_DATETIME_DATE )) {
6735                                                 gmtime_r( &_tmp_dt, &gmdt );
6736                                                 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6737                                         } else if( !( attr & DBI_DATETIME_TIME )) {
6738                                                 localtime_r( &_tmp_dt, &gmdt );
6739                                                 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6740                                         } else {
6741                                                 localtime_r( &_tmp_dt, &gmdt );
6742                                                 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6743                                         }
6744
6745                                         jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6746                                         break;
6747
6748                                 case DBI_TYPE_BINARY :
6749                                         osrfLogError( OSRF_LOG_MARK,
6750                                                 "Can't do binary at column %s : index %d", columnName, columnIndex );
6751                         }
6752                 }
6753                 ++columnIndex;
6754         } // end while loop traversing result
6755
6756         return object;
6757 }
6758
6759 // Interpret a string as true or false
6760 int str_is_true( const char* str ) {
6761         if( NULL == str || strcasecmp( str, "true" ) )
6762                 return 0;
6763         else
6764                 return 1;
6765 }
6766
6767 // Interpret a jsonObject as true or false
6768 static int obj_is_true( const jsonObject* obj ) {
6769         if( !obj )
6770                 return 0;
6771         else switch( obj->type )
6772         {
6773                 case JSON_BOOL :
6774                         if( obj->value.b )
6775                                 return 1;
6776                         else
6777                                 return 0;
6778                 case JSON_STRING :
6779                         if( strcasecmp( obj->value.s, "true" ) )
6780                                 return 0;
6781                         else
6782                                 return 1;
6783                 case JSON_NUMBER :          // Support 1/0 for perl's sake
6784                         if( jsonObjectGetNumber( obj ) == 1.0 )
6785                                 return 1;
6786                         else
6787                                 return 0;
6788                 default :
6789                         return 0;
6790         }
6791 }
6792
6793 // Translate a numeric code into a text string identifying a type of
6794 // jsonObject.  To be used for building error messages.
6795 static const char* json_type( int code ) {
6796         switch ( code )
6797         {
6798                 case 0 :
6799                         return "JSON_HASH";
6800                 case 1 :
6801                         return "JSON_ARRAY";
6802                 case 2 :
6803                         return "JSON_STRING";
6804                 case 3 :
6805                         return "JSON_NUMBER";
6806                 case 4 :
6807                         return "JSON_NULL";
6808                 case 5 :
6809                         return "JSON_BOOL";
6810                 default :
6811                         return "(unrecognized)";
6812         }
6813 }
6814
6815 // Extract the "primitive" attribute from an IDL field definition.
6816 // If we haven't initialized the app, then we must be running in
6817 // some kind of testbed.  In that case, default to "string".
6818 static const char* get_primitive( osrfHash* field ) {
6819         const char* s = osrfHashGet( field, "primitive" );
6820         if( !s ) {
6821                 if( child_initialized )
6822                         osrfLogError(
6823                                 OSRF_LOG_MARK,
6824                                 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6825                                 modulename,
6826                                 osrfHashGet( field, "name" )
6827                         );
6828
6829                 s = "string";
6830         }
6831         return s;
6832 }
6833
6834 // Extract the "datatype" attribute from an IDL field definition.
6835 // If we haven't initialized the app, then we must be running in
6836 // some kind of testbed.  In that case, default to to NUMERIC,
6837 // since we look at the datatype only for numbers.
6838 static const char* get_datatype( osrfHash* field ) {
6839         const char* s = osrfHashGet( field, "datatype" );
6840         if( !s ) {
6841                 if( child_initialized )
6842                         osrfLogError(
6843                                 OSRF_LOG_MARK,
6844                                 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6845                                 modulename,
6846                                 osrfHashGet( field, "name" )
6847                         );
6848                 else
6849                         s = "NUMERIC";
6850         }
6851         return s;
6852 }
6853
6854 /**
6855         @brief Determine whether a string is potentially a valid SQL identifier.
6856         @param s The identifier to be tested.
6857         @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6858
6859         Purpose: to prevent certain kinds of SQL injection.  To that end we don't necessarily
6860         need to follow all the rules exactly, such as requiring that the first character not
6861         be a digit.
6862
6863         We allow leading and trailing white space.  In between, we do not allow punctuation
6864         (except for underscores and dollar signs), control characters, or embedded white space.
6865
6866         More pedantically we should allow quoted identifiers containing arbitrary characters, but
6867         for the foreseeable future such quoted identifiers are not likely to be an issue.
6868 */
6869 int is_identifier( const char* s) {
6870         if( !s )
6871                 return 0;
6872
6873         // Skip leading white space
6874         while( isspace( (unsigned char) *s ) )
6875                 ++s;
6876
6877         if( !s )
6878                 return 0;   // Nothing but white space?  Not okay.
6879
6880         // Check each character until we reach white space or
6881         // end-of-string.  Letters, digits, underscores, and
6882         // dollar signs are okay. With the exception of periods
6883         // (as in schema.identifier), control characters and other
6884         // punctuation characters are not okay.  Anything else
6885         // is okay -- it could for example be part of a multibyte
6886         // UTF8 character such as a letter with diacritical marks,
6887         // and those are allowed.
6888         do {
6889                 if( isalnum( (unsigned char) *s )
6890                         || '.' == *s
6891                         || '_' == *s
6892                         || '$' == *s )
6893                         ;  // Fine; keep going
6894                 else if(   ispunct( (unsigned char) *s )
6895                                 || iscntrl( (unsigned char) *s ) )
6896                         return 0;
6897                         ++s;
6898         } while( *s && ! isspace( (unsigned char) *s ) );
6899
6900         // If we found any white space in the above loop,
6901         // the rest had better be all white space.
6902
6903         while( isspace( (unsigned char) *s ) )
6904                 ++s;
6905
6906         if( *s )
6907                 return 0;   // White space was embedded within non-white space
6908
6909         return 1;
6910 }
6911
6912 /**
6913         @brief Determine whether to accept a character string as a comparison operator.
6914         @param op The candidate comparison operator.
6915         @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6916
6917         We don't validate the operator for real.  We just make sure that it doesn't contain
6918         any semicolons or white space (with special exceptions for a few specific operators).
6919         The idea is to block certain kinds of SQL injection.  If it has no semicolons or white
6920         space but it's still not a valid operator, then the database will complain.
6921
6922         Another approach would be to compare the string against a short list of approved operators.
6923         We don't do that because we want to allow custom operators like ">100*", which at this
6924         writing would be difficult or impossible to express otherwise in a JSON query.
6925 */
6926 int is_good_operator( const char* op ) {
6927         if( !op ) return 0;   // Sanity check
6928
6929         const char* s = op;
6930         while( *s ) {
6931                 if( isspace( (unsigned char) *s ) ) {
6932                         // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6933                         // and IS NOT DISTINCT FROM.
6934                         if( !strcasecmp( op, "similar to" ) )
6935                                 return 1;
6936                         else if( !strcasecmp( op, "is distinct from" ) )
6937                                 return 1;
6938                         else if( !strcasecmp( op, "is not distinct from" ) )
6939                                 return 1;
6940                         else
6941                                 return 0;
6942                 }
6943                 else if( ';' == *s )
6944                         return 0;
6945                 ++s;
6946         }
6947         return 1;
6948 }
6949
6950 /**
6951         @name Query Frame Management
6952
6953         The following machinery supports a stack of query frames for use by SELECT().
6954
6955         A query frame caches information about one level of a SELECT query.  When we enter
6956         a subquery, we push another query frame onto the stack, and pop it off when we leave.
6957
6958         The query frame stores information about the core class, and about any joined classes
6959         in the FROM clause.
6960
6961         The main purpose is to map table aliases to classes and tables, so that a query can
6962         join to the same table more than once.  A secondary goal is to reduce the number of
6963         lookups in the IDL by caching the results.
6964 */
6965 /*@{*/
6966
6967 #define STATIC_CLASS_INFO_COUNT 3
6968
6969 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6970
6971 /**
6972         @brief Allocate a ClassInfo as raw memory.
6973         @return Pointer to the newly allocated ClassInfo.
6974
6975         Except for the in_use flag, which is used only by the allocation and deallocation
6976         logic, we don't initialize the ClassInfo here.
6977 */
6978 static ClassInfo* allocate_class_info( void ) {
6979         // In order to reduce the number of mallocs and frees, we return a static
6980         // instance of ClassInfo, if we can find one that we're not already using.
6981         // We rely on the fact that the compiler will implicitly initialize the
6982         // static instances so that in_use == 0.
6983
6984         int i;
6985         for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6986                 if( ! static_class_info[ i ].in_use ) {
6987                         static_class_info[ i ].in_use = 1;
6988                         return static_class_info + i;
6989                 }
6990         }
6991
6992         // The static ones are all in use.  Malloc one.
6993
6994         return safe_malloc( sizeof( ClassInfo ) );
6995 }
6996
6997 /**
6998         @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6999         @param info Pointer to the ClassInfo to be cleared.
7000 */
7001 static void clear_class_info( ClassInfo* info ) {
7002         // Sanity check
7003         if( ! info )
7004                 return;
7005
7006         // Free any malloc'd strings
7007
7008         if( info->alias != info->alias_store )
7009                 free( info->alias );
7010
7011         if( info->class_name != info->class_name_store )
7012                 free( info->class_name );
7013
7014         free( info->source_def );
7015
7016         info->alias = info->class_name = info->source_def = NULL;
7017         info->next = NULL;
7018 }
7019
7020 /**
7021         @brief Free a ClassInfo and everything it owns.
7022         @param info Pointer to the ClassInfo to be freed.
7023 */
7024 static void free_class_info( ClassInfo* info ) {
7025         // Sanity check
7026         if( ! info )
7027                 return;
7028
7029         clear_class_info( info );
7030
7031         // If it's one of the static instances, just mark it as not in use
7032
7033         int i;
7034         for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
7035                 if( info == static_class_info + i ) {
7036                         static_class_info[ i ].in_use = 0;
7037                         return;
7038                 }
7039         }
7040
7041         // Otherwise it must have been malloc'd, so free it
7042
7043         free( info );
7044 }
7045
7046 /**
7047         @brief Populate an already-allocated ClassInfo.
7048         @param info Pointer to the ClassInfo to be populated.
7049         @param alias Alias for the class.  If it is NULL, or an empty string, use the class
7050         name for an alias.
7051         @param class Name of the class.
7052         @return Zero if successful, or 1 if not.
7053
7054         Populate the ClassInfo with copies of the alias and class name, and with pointers to
7055         the relevant portions of the IDL for the specified class.
7056 */
7057 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
7058         // Sanity checks
7059         if( ! info ){
7060                 osrfLogError( OSRF_LOG_MARK,
7061                                           "%s ERROR: No ClassInfo available to populate", modulename );
7062                 info->alias = info->class_name = info->source_def = NULL;
7063                 info->class_def = info->fields = info->links = NULL;
7064                 return 1;
7065         }
7066
7067         if( ! class ) {
7068                 osrfLogError( OSRF_LOG_MARK,
7069                                           "%s ERROR: No class name provided for lookup", modulename );
7070                 info->alias = info->class_name = info->source_def = NULL;
7071                 info->class_def = info->fields = info->links = NULL;
7072                 return 1;
7073         }
7074
7075         // Alias defaults to class name if not supplied
7076         if( ! alias || ! alias[ 0 ] )
7077                 alias = class;
7078
7079         // Look up class info in the IDL
7080         osrfHash* class_def = osrfHashGet( oilsIDL(), class );
7081         if( ! class_def ) {
7082                 osrfLogError( OSRF_LOG_MARK,
7083                                           "%s ERROR: Class %s not defined in IDL", modulename, class );
7084                 info->alias = info->class_name = info->source_def = NULL;
7085                 info->class_def = info->fields = info->links = NULL;
7086                 return 1;
7087         } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
7088                 osrfLogError( OSRF_LOG_MARK,
7089                                           "%s ERROR: Class %s is defined as virtual", modulename, class );
7090                 info->alias = info->class_name = info->source_def = NULL;
7091                 info->class_def = info->fields = info->links = NULL;
7092                 return 1;
7093         }
7094
7095         osrfHash* links = osrfHashGet( class_def, "links" );
7096         if( ! links ) {
7097                 osrfLogError( OSRF_LOG_MARK,
7098                                           "%s ERROR: No links defined in IDL for class %s", modulename, class );
7099                 info->alias = info->class_name = info->source_def = NULL;
7100                 info->class_def = info->fields = info->links = NULL;
7101                 return 1;
7102         }
7103
7104         osrfHash* fields = osrfHashGet( class_def, "fields" );
7105         if( ! fields ) {
7106                 osrfLogError( OSRF_LOG_MARK,
7107                                           "%s ERROR: No fields defined in IDL for class %s", modulename, class );
7108                 info->alias = info->class_name = info->source_def = NULL;
7109                 info->class_def = info->fields = info->links = NULL;
7110                 return 1;
7111         }
7112
7113         char* source_def = oilsGetRelation( class_def );
7114         if( ! source_def )
7115                 return 1;
7116
7117         // We got everything we need, so populate the ClassInfo
7118         if( strlen( alias ) > ALIAS_STORE_SIZE )
7119                 info->alias = strdup( alias );
7120         else {
7121                 strcpy( info->alias_store, alias );
7122                 info->alias = info->alias_store;
7123         }
7124
7125         if( strlen( class ) > CLASS_NAME_STORE_SIZE )
7126                 info->class_name = strdup( class );
7127         else {
7128                 strcpy( info->class_name_store, class );
7129                 info->class_name = info->class_name_store;
7130         }
7131
7132         info->source_def = source_def;
7133
7134         info->class_def = class_def;
7135         info->links     = links;
7136         info->fields    = fields;
7137
7138         return 0;
7139 }
7140
7141 #define STATIC_FRAME_COUNT 3
7142
7143 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
7144
7145 /**
7146         @brief Allocate a QueryFrame as raw memory.
7147         @return Pointer to the newly allocated QueryFrame.
7148
7149         Except for the in_use flag, which is used only by the allocation and deallocation
7150         logic, we don't initialize the QueryFrame here.
7151 */
7152 static QueryFrame* allocate_frame( void ) {
7153         // In order to reduce the number of mallocs and frees, we return a static
7154         // instance of QueryFrame, if we can find one that we're not already using.
7155         // We rely on the fact that the compiler will implicitly initialize the
7156         // static instances so that in_use == 0.
7157
7158         int i;
7159         for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7160                 if( ! static_frame[ i ].in_use ) {
7161                         static_frame[ i ].in_use = 1;
7162                         return static_frame + i;
7163                 }
7164         }
7165
7166         // The static ones are all in use.  Malloc one.
7167
7168         return safe_malloc( sizeof( QueryFrame ) );
7169 }
7170
7171 /**
7172         @brief Free a QueryFrame, and all the memory it owns.
7173         @param frame Pointer to the QueryFrame to be freed.
7174 */
7175 static void free_query_frame( QueryFrame* frame ) {
7176         // Sanity check
7177         if( ! frame )
7178                 return;
7179
7180         clear_class_info( &frame->core );
7181
7182         // Free the join list
7183         ClassInfo* temp;
7184         ClassInfo* info = frame->join_list;
7185         while( info ) {
7186                 temp = info->next;
7187                 free_class_info( info );
7188                 info = temp;
7189         }
7190
7191         frame->join_list = NULL;
7192         frame->next = NULL;
7193
7194         // If the frame is a static instance, just mark it as unused
7195         int i;
7196         for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7197                 if( frame == static_frame + i ) {
7198                         static_frame[ i ].in_use = 0;
7199                         return;
7200                 }
7201         }
7202
7203         // Otherwise it must have been malloc'd, so free it
7204
7205         free( frame );
7206 }
7207
7208 /**
7209         @brief Search a given QueryFrame for a specified alias.
7210         @param frame Pointer to the QueryFrame to be searched.
7211         @param target The alias for which to search.
7212         @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
7213 */
7214 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
7215         if( ! frame || ! target ) {
7216                 return NULL;
7217         }
7218
7219         ClassInfo* found_class = NULL;
7220
7221         if( !strcmp( target, frame->core.alias ) )
7222                 return &(frame->core);
7223         else {
7224                 ClassInfo* curr_class = frame->join_list;
7225                 while( curr_class ) {
7226                         if( strcmp( target, curr_class->alias ) )
7227                                 curr_class = curr_class->next;
7228                         else {
7229                                 found_class = curr_class;
7230                                 break;
7231                         }
7232                 }
7233         }
7234
7235         return found_class;
7236 }
7237
7238 /**
7239         @brief Push a new (blank) QueryFrame onto the stack.
7240 */
7241 static void push_query_frame( void ) {
7242         QueryFrame* frame = allocate_frame();
7243         frame->join_list = NULL;
7244         frame->next = curr_query;
7245
7246         // Initialize the ClassInfo for the core class
7247         ClassInfo* core = &frame->core;
7248         core->alias = core->class_name = core->source_def = NULL;
7249         core->class_def = core->fields = core->links = NULL;
7250
7251         curr_query = frame;
7252 }
7253
7254 /**
7255         @brief Pop a QueryFrame off the stack and destroy it.
7256 */
7257 static void pop_query_frame( void ) {
7258         // Sanity check
7259         if( ! curr_query )
7260                 return;
7261
7262         QueryFrame* popped = curr_query;
7263         curr_query = popped->next;
7264
7265         free_query_frame( popped );
7266 }
7267
7268 /**
7269         @brief Populate the ClassInfo for the core class.
7270         @param alias Alias for the core class.  If it is NULL or an empty string, we use the
7271         class name as an alias.
7272         @param class_name Name of the core class.
7273         @return Zero if successful, or 1 if not.
7274
7275         Populate the ClassInfo of the core class with copies of the alias and class name, and
7276         with pointers to the relevant portions of the IDL for the core class.
7277 */
7278 static int add_query_core( const char* alias, const char* class_name ) {
7279
7280         // Sanity checks
7281         if( ! curr_query ) {
7282                 osrfLogError( OSRF_LOG_MARK,
7283                                           "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
7284                 return 1;
7285         } else if( curr_query->core.alias ) {
7286                 osrfLogError( OSRF_LOG_MARK,
7287                                           "%s ERROR: Core class %s already populated as %s",
7288                                           modulename, curr_query->core.class_name, curr_query->core.alias );
7289                 return 1;
7290         }
7291
7292         build_class_info( &curr_query->core, alias, class_name );
7293         if( curr_query->core.alias )
7294                 return 0;
7295         else {
7296                 osrfLogError( OSRF_LOG_MARK,
7297                                           "%s ERROR: Unable to look up core class %s", modulename, class_name );
7298                 return 1;
7299         }
7300 }
7301
7302 /**
7303         @brief Search the current QueryFrame for a specified alias.
7304         @param target The alias for which to search.
7305         @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7306 */
7307 static inline ClassInfo* search_alias( const char* target ) {
7308         return search_alias_in_frame( curr_query, target );
7309 }
7310
7311 /**
7312         @brief Search all levels of query for a specified alias, starting with the current query.
7313         @param target The alias for which to search.
7314         @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7315 */
7316 static ClassInfo* search_all_alias( const char* target ) {
7317         ClassInfo* found_class = NULL;
7318         QueryFrame* curr_frame = curr_query;
7319
7320         while( curr_frame ) {
7321                 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
7322                         break;
7323                 else
7324                         curr_frame = curr_frame->next;
7325         }
7326
7327         return found_class;
7328 }
7329
7330 /**
7331         @brief Add a class to the list of classes joined to the current query.
7332         @param alias Alias of the class to be added.  If it is NULL or an empty string, we use
7333         the class name as an alias.
7334         @param classname The name of the class to be added.
7335         @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
7336 */
7337 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
7338
7339         if( ! classname || ! *classname ) {    // sanity check
7340                 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
7341                 return NULL;
7342         }
7343
7344         if( ! alias )
7345                 alias = classname;
7346
7347         const ClassInfo* conflict = search_alias( alias );
7348         if( conflict ) {
7349                 osrfLogError( OSRF_LOG_MARK,
7350                                           "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
7351                                           modulename, alias, conflict->class_name );
7352                 return NULL;
7353         }
7354
7355         ClassInfo* info = allocate_class_info();
7356
7357         if( build_class_info( info, alias, classname ) ) {
7358                 free_class_info( info );
7359                 return NULL;
7360         }
7361
7362         // Add the new ClassInfo to the join list of the current QueryFrame
7363         info->next = curr_query->join_list;
7364         curr_query->join_list = info;
7365
7366         return info;
7367 }
7368
7369 /**
7370         @brief Destroy all nodes on the query stack.
7371 */
7372 static void clear_query_stack( void ) {
7373         while( curr_query )
7374                 pop_query_frame();
7375 }
7376
7377 /**
7378         @brief Implement the set_audit_info method.
7379         @param ctx Pointer to the method context.
7380         @return Zero if successful, or -1 if not.
7381
7382         Issue a SAVEPOINT to the database server.
7383
7384         Method parameters:
7385         - authkey
7386         - user id (int)
7387         - workstation id (int)
7388
7389         If user id is not provided the authkey will be used.
7390         For PCRUD the authkey is always used, even if a user is provided.
7391 */
7392 int setAuditInfo( osrfMethodContext* ctx ) {
7393         if(osrfMethodVerifyContext( ctx )) {
7394                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
7395                 return -1;
7396         }
7397
7398         // Get the user id from the parameters
7399         const char* user_id = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 1) );
7400
7401         if( enforce_pcrud || !user_id ) {
7402                 timeout_needs_resetting = 1;
7403                 const jsonObject* user = verifyUserPCRUD( ctx );
7404                 if( !user )
7405                         return -1;
7406                 osrfAppRespondComplete( ctx, NULL );
7407                 return 0;
7408         }
7409
7410         // Not PCRUD and have a user_id?
7411         int result = writeAuditInfo( ctx, user_id, jsonObjectGetString( jsonObjectGetIndex(ctx->params, 2) ) );
7412         osrfAppRespondComplete( ctx, NULL );
7413         return result;
7414 }
7415
7416 /**
7417         @brief Save a audit info
7418         @param ctx Pointer to the method context.
7419         @param user_id User ID to write as a string
7420         @param ws_id Workstation ID to write as a string
7421 */
7422 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id) {
7423         if( ctx && ctx->session ) {
7424                 osrfAppSession* session = ctx->session;
7425
7426                 osrfHash* cache = session->userData;
7427
7428                 // If the session doesn't already have a hash, create one.  Make sure
7429                 // that the application session frees the hash when it terminates.
7430                 if( NULL == cache ) {
7431                         session->userData = cache = osrfNewHash();
7432                         osrfHashSetCallback( cache, &sessionDataFree );
7433                         ctx->session->userDataFree = &userDataFree;
7434                 }
7435
7436                 dbi_result result = dbi_conn_queryf( writehandle, "SELECT auditor.set_audit_info( %s, %s );", user_id, ws_id ? ws_id : "NULL" );
7437                 if( !result ) {
7438                         osrfLogWarning( OSRF_LOG_MARK, "BAD RESULT" );
7439                         const char* msg;
7440                         int errnum = dbi_conn_error( writehandle, &msg );
7441                         osrfLogError(
7442                                 OSRF_LOG_MARK,
7443                                 "%s: Error setting auditor information: %d %s",
7444                                 modulename,
7445                                 errnum,
7446                                 msg ? msg : "(No description available)"
7447                         );
7448                         osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
7449                                 "osrfMethodException", ctx->request, "Error setting auditor info" );
7450                         if( !oilsIsDBConnected( writehandle ))
7451                                 osrfAppSessionPanic( ctx->session );
7452                         return -1;
7453                 } else {
7454                         dbi_result_free( result );
7455                 }
7456         }
7457         return 0;
7458 }
7459
7460 /**
7461         @brief Remove all but safe character from savepoint name
7462         @param sp User-supplied savepoint name
7463         @return sanitized savepoint name, or NULL
7464
7465     The caller is expected to free the returned string.  Note that
7466     this function exists only because we can't use PQescapeLiteral
7467     without either forking libdbi or abandoning it.
7468 */
7469 static char* _sanitize_savepoint_name( const char* sp ) {
7470
7471         const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_";
7472
7473         // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7474         // and the default value of NAMEDATALEN is 64; that should be long enough
7475         // for our purposes, and it's unlikely that anyone is going to recompile
7476         // PostgreSQL to have a smaller value, so cap the identifier name
7477         // accordingly to avoid the remote chance that someone manages to pass in a
7478         // 12GB savepoint name
7479         const int MAX_LITERAL_NAMELEN = 63;
7480         int len = 0;
7481         len = strlen( sp );
7482         if (len > MAX_LITERAL_NAMELEN) {
7483                 len = MAX_LITERAL_NAMELEN;
7484         }
7485
7486         char* safeSpName = safe_malloc( len + 1 );
7487         int i = 0;
7488         int j;
7489         char* found;
7490         for (j = 0; j < len; j++) {
7491         found = strchr(safe_chars, sp[j]);
7492                 if (found) {
7493                         safeSpName[ i++ ] = found[0];
7494                 }
7495         }
7496         safeSpName[ i ] = '\0';
7497         return safeSpName;
7498 }
7499
7500 /*@}*/