]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/c-apps/oils_sql.c
LP#1373693: Speed up call number browse search at top level org.
[working/Evergreen.git] / Open-ILS / src / c-apps / oils_sql.c
1 /**
2         @file oils_sql.c
3         @brief Utility routines for translating JSON into SQL.
4 */
5
6 #include <stdlib.h>
7 #include <string.h>
8 #include <time.h>
9 #include <ctype.h>
10 #include <dbi/dbi.h>
11 #include "opensrf/utils.h"
12 #include "opensrf/log.h"
13 #include "opensrf/osrf_application.h"
14 #include "openils/oils_utils.h"
15 #include "openils/oils_sql.h"
16
17 // The next four macros are OR'd together as needed to form a set
18 // of bitflags.  SUBCOMBO enables an extra pair of parentheses when
19 // nesting one UNION, INTERSECT or EXCEPT inside another.
20 // SUBSELECT tells us we're in a subquery, so don't add the
21 // terminal semicolon yet.
22 #define SUBCOMBO    8
23 #define SUBSELECT   4
24 #define DISABLE_I18N    2
25 #define SELECT_DISTINCT 1
26
27 #define AND_OP_JOIN     0
28 #define OR_OP_JOIN      1
29
30 struct ClassInfoStruct;
31 typedef struct ClassInfoStruct ClassInfo;
32
33 #define ALIAS_STORE_SIZE 16
34 #define CLASS_NAME_STORE_SIZE 16
35
36 struct ClassInfoStruct {
37         char* alias;
38         char* class_name;
39         char* source_def;
40         osrfHash* class_def;      // Points into IDL
41         osrfHash* fields;         // Points into IDL
42         osrfHash* links;          // Points into IDL
43
44         // The remaining members are private and internal.  Client code should not
45         // access them directly.
46
47         ClassInfo* next;          // Supports linked list of joined classes
48         int in_use;               // boolean
49
50         // We usually store the alias and class name in the following arrays, and
51         // point the corresponding pointers at them.  When the string is too big
52         // for the array (which will probably never happen in practice), we strdup it.
53
54         char alias_store[ ALIAS_STORE_SIZE + 1 ];
55         char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
56 };
57
58 struct QueryFrameStruct;
59 typedef struct QueryFrameStruct QueryFrame;
60
61 struct QueryFrameStruct {
62         ClassInfo core;
63         ClassInfo* join_list;  // linked list of classes joined to the core class
64         QueryFrame* next;      // implements stack as linked list
65         int in_use;            // boolean
66 };
67
68 static int timeout_needs_resetting;
69 static time_t time_next_reset;
70
71 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
72
73 static void setXactId( osrfMethodContext* ctx );
74 static inline const char* getXactId( osrfMethodContext* ctx );
75 static inline void clearXactId( osrfMethodContext* ctx );
76
77 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
78                 jsonObject* where_hash, jsonObject* query_hash, int* err );
79 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
80 static jsonObject* oilsMakeJSONFromResult( dbi_result );
81
82 static char* searchSimplePredicate ( const char* op, const char* class_alias,
83                                 osrfHash* field, const jsonObject* node );
84 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
85 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject* );
86 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*,
87                 const char* );
88 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
89 static char* searchINPredicate ( const char*, osrfHash*,
90                                                                  jsonObject*, const char*, osrfMethodContext* );
91 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
92 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
93 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo*, int, osrfMethodContext* );
94 static char* buildSELECT( const jsonObject*, jsonObject* rest_of_query,
95         osrfHash* meta, osrfMethodContext* ctx );
96 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array );
97
98 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
99
100 char* SELECT ( osrfMethodContext*, jsonObject*, const jsonObject*, const jsonObject*,
101         const jsonObject*, const jsonObject*, const jsonObject*, const jsonObject*, int );
102
103 static osrfStringArray* getPermLocationCache( osrfMethodContext*, const char* );
104 static void setPermLocationCache( osrfMethodContext*, const char*, osrfStringArray* );
105
106 void userDataFree( void* );
107 static void sessionDataFree( char*, void* );
108 static void pcacheFree( char*, void* );
109 static int obj_is_true( const jsonObject* obj );
110 static const char* json_type( int code );
111 static const char* get_primitive( osrfHash* field );
112 static const char* get_datatype( osrfHash* field );
113 static void pop_query_frame( void );
114 static void push_query_frame( void );
115 static int add_query_core( const char* alias, const char* class_name );
116 static inline ClassInfo* search_alias( const char* target );
117 static ClassInfo* search_all_alias( const char* target );
118 static ClassInfo* add_joined_class( const char* alias, const char* classname );
119 static void clear_query_stack( void );
120
121 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
122 static 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                                                         osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1776                                                         _list = doFieldmapperSearch( ctx, foreign_class_meta,
1777                                                                         _tmp_params, NULL, &err );
1778                                                         osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1779
1780                                                         // Get the resulting row
1781                                                         jsonObjectFree( _fparam );
1782                                                         if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1783                                                                 _fparam = jsonObjectExtractIndex( _list, 0 );
1784                                                         else {
1785                                                                 // Referenced row not found
1786                                                                 _fparam = NULL;
1787                                                                 bad_class = osrfHashGet( foreign_link_hash, "class" );
1788                                                         }
1789
1790                                                         jsonObjectFree( _tmp_params );
1791                                                         jsonObjectFree( _list );
1792                                                 }
1793                                         }
1794
1795                                         if( bad_class ) {
1796
1797                                                 // We had a foreign key pointing to such-and-such a row, but then
1798                                                 // we couldn't fetch that row.  The data in the database are in an
1799                                                 // inconsistent state; the database itself may even be corrupted.
1800                                                 growing_buffer* msg = buffer_init( 128 );
1801                                                 buffer_fadd(
1802                                                         msg,
1803                                                         "%s: no object of class %s found with primary key %s of %s",
1804                                                         modulename,
1805                                                         bad_class,
1806                                                         foreign_pkey,
1807                                                         foreign_pkey_value ? foreign_pkey_value : "(null)"
1808                                                 );
1809
1810                                                 char* m = buffer_release( msg );
1811                                                 osrfAppSessionStatus(
1812                                                         ctx->session,
1813                                                         OSRF_STATUS_INTERNALSERVERERROR,
1814                                                         "osrfMethodException",
1815                                                         ctx->request,
1816                                                         m
1817                                                 );
1818
1819                                                 free( m );
1820                                                 osrfHashIteratorFree( class_itr );
1821                                                 free( foreign_pkey_value );
1822                                                 jsonObjectFree( param );
1823
1824                                                 return 0;
1825                                         }
1826
1827                                         free( foreign_pkey_value );
1828
1829                                         if( _fparam ) {
1830                                                 // Examine each context column of the foreign row,
1831                                                 // and add its value to the list of org units.
1832                                                 int j = 0;
1833                                                 const char* foreign_field = NULL;
1834                                                 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1835                                                 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1836                                                         osrfStringArrayAdd( context_org_array,
1837                                                                 oilsFMGetStringConst( _fparam, foreign_field ));
1838                                                         osrfLogDebug( OSRF_LOG_MARK,
1839                                                                 "adding foreign class %s field %s (value: %s) "
1840                                                                         "to the context org list",
1841                                                                 class_name,
1842                                                                 foreign_field,
1843                                                                 osrfStringArrayGetString(
1844                                                                         context_org_array, context_org_array->size - 1 )
1845                                                         );
1846                                                 }
1847
1848                                                 jsonObjectFree( _fparam );
1849                                         }
1850                                 }
1851
1852                                 osrfHashIteratorFree( class_itr );
1853                         }
1854                 }
1855         }
1856
1857     // If there is an owning_user attached to the action, we allow that user and users with
1858     // object perms on the object. CREATE can't use this. We only do this when there is no
1859     // context org for this action, and when we're not ignoring object perms.
1860     if (
1861         *method_type != 'c' &&
1862         !str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) && // Always honor
1863         context_org_array->size == 0
1864     ) {
1865         char* owning_user_field = osrfHashGet( pcrud, "owning_user" );
1866         if (owning_user_field) {
1867
1868             if (!param) { // We didn't get it during the context lookup
1869                             pkey = osrfHashGet( class, "primarykey" );
1870                 
1871                                 if( !pkey  ) {
1872                                         // There is no primary key, so we can't do a fresh lookup.  Use the row
1873                                         // image that we already have.  If it doesn't have everything we need, too bad.
1874                                         fetch = 0;
1875                                         param = jsonObjectClone( obj );
1876                                         osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1877                                 } else if( obj->classname ) {
1878                                         pkey_value = oilsFMGetStringConst( obj, pkey );
1879                                         if( !fetch )
1880                                                 param = jsonObjectClone( obj );
1881                                         osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1882                                                 pkey_value );
1883                                 } else {
1884                                         pkey_value = jsonObjectGetString( obj );
1885                                         fetch = 1;
1886                                         osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1887                                                 "of %s and retrieving from the database", pkey_value );
1888                                 }
1889                 
1890                                 if( fetch ) {
1891                                         // Fetch the row so that we can look at the foreign key(s)
1892                                         osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1893                                         jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1894                                         jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1895                                         jsonObjectFree( _tmp_params );
1896                                         osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1897                 
1898                                         param = jsonObjectExtractIndex( _list, 0 );
1899                                         jsonObjectFree( _list );
1900                                 }
1901             }
1902         
1903                         if( !param ) {
1904                                 // The row doesn't exist.  Complain, and deny access.
1905                                 osrfLogDebug( OSRF_LOG_MARK,
1906                                                 "Object not found in the database with primary key %s of %s",
1907                                                 pkey, pkey_value );
1908         
1909                                 growing_buffer* msg = buffer_init( 128 );
1910                                 buffer_fadd(
1911                                         msg,
1912                                         "%s: no object found with primary key %s of %s",
1913                                         modulename,
1914                                         pkey,
1915                                         pkey_value
1916                                 );
1917         
1918                                 char* m = buffer_release( msg );
1919                                 osrfAppSessionStatus(
1920                                         ctx->session,
1921                                         OSRF_STATUS_INTERNALSERVERERROR,
1922                                         "osrfMethodException",
1923                                         ctx->request,
1924                                         m
1925                                 );
1926         
1927                                 free( m );
1928                                 return 0;
1929                         } else {
1930
1931                 int ownerid = atoi( oilsFMGetStringConst( param, owning_user_field ) );
1932
1933                 // Allow the owner to do whatever
1934                 if (ownerid == userid)
1935                     OK = 1;
1936
1937                 i = 0;
1938                     while( !OK && (perm = osrfStringArrayGetString(permission, i++)) ) {
1939                         dbi_result result;
1940
1941                                         osrfLogDebug(
1942                                                 OSRF_LOG_MARK,
1943                                                 "Checking object permission [%s] for user %d "
1944                                                                 "on object %s (class %s)",
1945                                                 perm,
1946                                                 userid,
1947                                                 pkey_value,
1948                                                 osrfHashGet( class, "classname" )
1949                                         );
1950         
1951                                         result = dbi_conn_queryf(
1952                                                 writehandle,
1953                                                 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s') AS has_perm;",
1954                                                 userid,
1955                                                 perm,
1956                                                 osrfHashGet( class, "classname" ),
1957                                                 pkey_value
1958                                         );
1959         
1960                                         if( result ) {
1961                                                 osrfLogDebug(
1962                                                         OSRF_LOG_MARK,
1963                                                         "Received a result for object permission [%s] "
1964                                                                         "for user %d on object %s (class %s)",
1965                                                         perm,
1966                                                         userid,
1967                                                         pkey_value,
1968                                                         osrfHashGet( class, "classname" )
1969                                                 );
1970         
1971                                                 if( dbi_result_first_row( result )) {
1972                                                         jsonObject* return_val = oilsMakeJSONFromResult( result );
1973                                                         const char* has_perm = jsonObjectGetString(
1974                                                                         jsonObjectGetKeyConst( return_val, "has_perm" ));
1975         
1976                                                         osrfLogDebug(
1977                                                                 OSRF_LOG_MARK,
1978                                                                 "Status of object permission [%s] for user %d "
1979                                                                                 "on object %s (class %s) is %s",
1980                                                                 perm,
1981                                                                 userid,
1982                                                                 pkey_value,
1983                                                                 osrfHashGet(class, "classname"),
1984                                                                 has_perm
1985                                                         );
1986         
1987                                                         if( *has_perm == 't' )
1988                                                                 OK = 1;
1989                                                         jsonObjectFree( return_val );
1990                                                 }
1991         
1992                                                 dbi_result_free( result );
1993                                                 if( OK )
1994                             break;
1995                                         } else {
1996                                                 const char* msg;
1997                                                 int errnum = dbi_conn_error( writehandle, &msg );
1998                                                 osrfLogWarning( OSRF_LOG_MARK,
1999                                                         "Unable to call check object permissions: %d, %s",
2000                                                         errnum, msg ? msg : "(No description available)" );
2001                                                 if( !oilsIsDBConnected( writehandle ))
2002                                                         osrfAppSessionPanic( ctx->session );
2003                                         }
2004                                 }
2005             }
2006         }
2007     }
2008
2009         // For every combination of permission and context org unit: call a stored procedure
2010         // to determine if the user has this permission in the context of this org unit.
2011         // If the answer is yes at any point, then we're done, and the user has permission.
2012         // In other words permissions are additive.
2013         i = 0;
2014         while( !OK && (perm = osrfStringArrayGetString(permission, i++)) ) {
2015                 dbi_result result;
2016
2017         osrfStringArray* pcache = NULL;
2018         if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
2019                         pcache = getPermLocationCache(ctx, perm);
2020
2021                         if (!pcache) {
2022                         pcache = osrfNewStringArray(0);
2023         
2024                                 result = dbi_conn_queryf(
2025                                         writehandle,
2026                                         "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
2027                                         userid,
2028                                         perm
2029                                 );
2030                 
2031                                 if( result ) {
2032                                         osrfLogDebug(
2033                                                 OSRF_LOG_MARK,
2034                                                 "Received a result for permission [%s] for user %d",
2035                                                 perm,
2036                                                 userid
2037                                         );
2038                 
2039                                         if( dbi_result_first_row( result )) {
2040                             do {
2041                                                 jsonObject* return_val = oilsMakeJSONFromResult( result );
2042                                                 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
2043                                 jsonObjectFree( return_val );
2044                                             } while( dbi_result_next_row( result ));
2045
2046                                                 setPermLocationCache(ctx, perm, pcache);
2047                                         }
2048                 
2049                                         dbi_result_free( result );
2050                     }
2051                         }
2052         }
2053
2054                 int j = 0;
2055                 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
2056
2057             if (rs_size > perm_at_threshold) {
2058                 if (osrfStringArrayContains( pcache, context_org )) {
2059                     OK = 1;
2060                     break;
2061                 }
2062             }
2063
2064                         if(
2065                 pkey_value &&
2066                 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) && // Always honor
2067                 (
2068                     !str_is_true( osrfHashGet(pcrud, "global_required") ) ||
2069                     osrfHashGet(pcrud, "owning_user") 
2070                 )
2071             ) {
2072                                 osrfLogDebug(
2073                                         OSRF_LOG_MARK,
2074                                         "Checking object permission [%s] for user %d "
2075                                                         "on object %s (class %s) at org %d",
2076                                         perm,
2077                                         userid,
2078                                         pkey_value,
2079                                         osrfHashGet( class, "classname" ),
2080                                         atoi( context_org )
2081                                 );
2082
2083                                 result = dbi_conn_queryf(
2084                                         writehandle,
2085                                         "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
2086                                         userid,
2087                                         perm,
2088                                         osrfHashGet( class, "classname" ),
2089                                         pkey_value,
2090                                         atoi( context_org )
2091                                 );
2092
2093                                 if( result ) {
2094                                         osrfLogDebug(
2095                                                 OSRF_LOG_MARK,
2096                                                 "Received a result for object permission [%s] "
2097                                                                 "for user %d on object %s (class %s) at org %d",
2098                                                 perm,
2099                                                 userid,
2100                                                 pkey_value,
2101                                                 osrfHashGet( class, "classname" ),
2102                                                 atoi( context_org )
2103                                         );
2104
2105                                         if( dbi_result_first_row( result )) {
2106                                                 jsonObject* return_val = oilsMakeJSONFromResult( result );
2107                                                 const char* has_perm = jsonObjectGetString(
2108                                                                 jsonObjectGetKeyConst( return_val, "has_perm" ));
2109
2110                                                 osrfLogDebug(
2111                                                         OSRF_LOG_MARK,
2112                                                         "Status of object permission [%s] for user %d "
2113                                                                         "on object %s (class %s) at org %d is %s",
2114                                                         perm,
2115                                                         userid,
2116                                                         pkey_value,
2117                                                         osrfHashGet(class, "classname"),
2118                                                         atoi(context_org),
2119                                                         has_perm
2120                                                 );
2121
2122                                                 if( *has_perm == 't' )
2123                                                         OK = 1;
2124                                                 jsonObjectFree( return_val );
2125                                         }
2126
2127                                         dbi_result_free( result );
2128                                         if( OK )
2129                                                 break;
2130                                 } else {
2131                                         const char* msg;
2132                                         int errnum = dbi_conn_error( writehandle, &msg );
2133                                         osrfLogWarning( OSRF_LOG_MARK,
2134                                                 "Unable to call check object permissions: %d, %s",
2135                                                 errnum, msg ? msg : "(No description available)" );
2136                                         if( !oilsIsDBConnected( writehandle ))
2137                                                 osrfAppSessionPanic( ctx->session );
2138                                 }
2139                         }
2140
2141             if (rs_size > perm_at_threshold) break;
2142
2143                         osrfLogDebug( OSRF_LOG_MARK,
2144                                         "Checking non-object permission [%s] for user %d at org %d",
2145                                         perm, userid, atoi(context_org) );
2146                         result = dbi_conn_queryf(
2147                                 writehandle,
2148                                 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
2149                                 userid,
2150                                 perm,
2151                                 atoi( context_org )
2152                         );
2153
2154                         if( result ) {
2155                                 osrfLogDebug( OSRF_LOG_MARK,
2156                                         "Received a result for permission [%s] for user %d at org %d",
2157                                         perm, userid, atoi( context_org ));
2158                                 if( dbi_result_first_row( result )) {
2159                                         jsonObject* return_val = oilsMakeJSONFromResult( result );
2160                                         const char* has_perm = jsonObjectGetString(
2161                                                 jsonObjectGetKeyConst( return_val, "has_perm" ));
2162                                         osrfLogDebug( OSRF_LOG_MARK,
2163                                                 "Status of permission [%s] for user %d at org %d is [%s]",
2164                                                 perm, userid, atoi( context_org ), has_perm );
2165                                         if( *has_perm == 't' )
2166                                                 OK = 1;
2167                                         jsonObjectFree( return_val );
2168                                 }
2169
2170                                 dbi_result_free( result );
2171                                 if( OK )
2172                                         break;
2173                         } else {
2174                                 const char* msg;
2175                                 int errnum = dbi_conn_error( writehandle, &msg );
2176                                 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
2177                                         errnum, msg ? msg : "(No description available)" );
2178                                 if( !oilsIsDBConnected( writehandle ))
2179                                         osrfAppSessionPanic( ctx->session );
2180                         }
2181
2182                 }
2183
2184                 if( OK )
2185                         break;
2186         }
2187
2188         osrfStringArrayFree( context_org_array );
2189
2190         return OK;
2191 }
2192
2193 /**
2194         @brief Look up the root of the org_unit tree.
2195         @param ctx Pointer to the method context.
2196         @return The id of the root org unit, as a character string.
2197
2198         Query actor.org_unit where parent_ou is null, and return the id as a string.
2199
2200         This function assumes that there is only one root org unit, i.e. that we
2201         have a single tree, not a forest.
2202
2203         The calling code is responsible for freeing the returned string.
2204 */
2205 static const char* org_tree_root( osrfMethodContext* ctx ) {
2206
2207         static char cached_root_id[ 32 ] = "";  // extravagantly large buffer
2208         static time_t last_lookup_time = 0;
2209         time_t current_time = time( NULL );
2210
2211         if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
2212                 // We successfully looked this up less than an hour ago.
2213                 // It's not likely to have changed since then.
2214                 return strdup( cached_root_id );
2215         }
2216         last_lookup_time = current_time;
2217
2218         int err = 0;
2219         jsonObject* where_clause = single_hash( "parent_ou", NULL );
2220         jsonObject* result = doFieldmapperSearch(
2221                 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
2222         jsonObjectFree( where_clause );
2223
2224         jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
2225
2226         if( !tree_top ) {
2227                 jsonObjectFree( result );
2228
2229                 growing_buffer* msg = buffer_init( 128 );
2230                 OSRF_BUFFER_ADD( msg, modulename );
2231                 OSRF_BUFFER_ADD( msg,
2232                                 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2233
2234                 char* m = buffer_release( msg );
2235                 osrfAppSessionStatus( ctx->session,
2236                                 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2237                 free( m );
2238
2239                 cached_root_id[ 0 ] = '\0';
2240                 return NULL;
2241         }
2242
2243         const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2244         osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2245
2246         strcpy( cached_root_id, root_org_unit_id );
2247         jsonObjectFree( result );
2248         return cached_root_id;
2249 }
2250
2251 /**
2252         @brief Create a JSON_HASH with a single key/value pair.
2253         @param key The key of the key/value pair.
2254         @param value the value of the key/value pair.
2255         @return Pointer to a newly created jsonObject of type JSON_HASH.
2256
2257         The value of the key/value is either a string or (if @a value is NULL) a null.
2258 */
2259 static jsonObject* single_hash( const char* key, const char* value ) {
2260         // Sanity check
2261         if( ! key ) key = "";
2262
2263         jsonObject* hash = jsonNewObjectType( JSON_HASH );
2264         jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2265         return hash;
2266 }
2267
2268
2269 int doCreate( osrfMethodContext* ctx ) {
2270         if(osrfMethodVerifyContext( ctx )) {
2271                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2272                 return -1;
2273         }
2274
2275         if( enforce_pcrud )
2276                 timeout_needs_resetting = 1;
2277
2278         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2279         jsonObject* target = NULL;
2280         jsonObject* options = NULL;
2281
2282         if( enforce_pcrud ) {
2283                 target = jsonObjectGetIndex( ctx->params, 1 );
2284                 options = jsonObjectGetIndex( ctx->params, 2 );
2285         } else {
2286                 target = jsonObjectGetIndex( ctx->params, 0 );
2287                 options = jsonObjectGetIndex( ctx->params, 1 );
2288         }
2289
2290         if( !verifyObjectClass( ctx, target )) {
2291                 osrfAppRespondComplete( ctx, NULL );
2292                 return -1;
2293         }
2294
2295         osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2296
2297         const char* trans_id = getXactId( ctx );
2298         if( !trans_id ) {
2299                 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2300
2301                 osrfAppSessionStatus(
2302                         ctx->session,
2303                         OSRF_STATUS_BADREQUEST,
2304                         "osrfMethodException",
2305                         ctx->request,
2306                         "No active transaction -- required for CREATE"
2307                 );
2308                 osrfAppRespondComplete( ctx, NULL );
2309                 return -1;
2310         }
2311
2312         // The following test is harmless but redundant.  If a class is
2313         // readonly, we don't register a create method for it.
2314         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2315                 osrfAppSessionStatus(
2316                         ctx->session,
2317                         OSRF_STATUS_BADREQUEST,
2318                         "osrfMethodException",
2319                         ctx->request,
2320                         "Cannot INSERT readonly class"
2321                 );
2322                 osrfAppRespondComplete( ctx, NULL );
2323                 return -1;
2324         }
2325
2326         // Set the last_xact_id
2327         int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2328         if( index > -1 ) {
2329                 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2330                         trans_id, target->classname, index);
2331                 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2332         }
2333
2334         osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2335
2336         dbhandle = writehandle;
2337
2338         osrfHash* fields = osrfHashGet( meta, "fields" );
2339         char* pkey       = osrfHashGet( meta, "primarykey" );
2340         char* seq        = osrfHashGet( meta, "sequence" );
2341
2342         growing_buffer* table_buf = buffer_init( 128 );
2343         growing_buffer* col_buf   = buffer_init( 128 );
2344         growing_buffer* val_buf   = buffer_init( 128 );
2345
2346         OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2347         OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2348         OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2349         buffer_add( val_buf,"VALUES (" );
2350
2351
2352         int first = 1;
2353         osrfHash* field = NULL;
2354         osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2355         while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2356
2357                 const char* field_name = osrfHashIteratorKey( field_itr );
2358
2359                 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2360                         continue;
2361
2362                 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2363
2364                 char* value;
2365                 if( field_object && field_object->classname ) {
2366                         value = oilsFMGetString(
2367                                 field_object,
2368                                 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2369                         );
2370                 } else if( field_object && JSON_BOOL == field_object->type ) {
2371                         if( jsonBoolIsTrue( field_object ) )
2372                                 value = strdup( "t" );
2373                         else
2374                                 value = strdup( "f" );
2375                 } else {
2376                         value = jsonObjectToSimpleString( field_object );
2377                 }
2378
2379                 if( first ) {
2380                         first = 0;
2381                 } else {
2382                         OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2383                         OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2384                 }
2385
2386                 buffer_add( col_buf, field_name );
2387
2388                 if( !field_object || field_object->type == JSON_NULL ) {
2389                         buffer_add( val_buf, "DEFAULT" );
2390
2391                 } else if( !strcmp( get_primitive( field ), "number" )) {
2392                         const char* numtype = get_datatype( field );
2393                         if( !strcmp( numtype, "INT8" )) {
2394                                 buffer_fadd( val_buf, "%lld", atoll( value ));
2395
2396                         } else if( !strcmp( numtype, "INT" )) {
2397                                 buffer_fadd( val_buf, "%d", atoi( value ));
2398
2399                         } else if( !strcmp( numtype, "NUMERIC" )) {
2400                                 buffer_fadd( val_buf, "%f", atof( value ));
2401                         }
2402                 } else {
2403                         if( dbi_conn_quote_string( writehandle, &value )) {
2404                                 OSRF_BUFFER_ADD( val_buf, value );
2405
2406                         } else {
2407                                 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2408                                 osrfAppSessionStatus(
2409                                         ctx->session,
2410                                         OSRF_STATUS_INTERNALSERVERERROR,
2411                                         "osrfMethodException",
2412                                         ctx->request,
2413                                         "Error quoting string -- please see the error log for more details"
2414                                 );
2415                                 free( value );
2416                                 buffer_free( table_buf );
2417                                 buffer_free( col_buf );
2418                                 buffer_free( val_buf );
2419                                 osrfAppRespondComplete( ctx, NULL );
2420                                 return -1;
2421                         }
2422                 }
2423
2424                 free( value );
2425         }
2426
2427         osrfHashIteratorFree( field_itr );
2428
2429         OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2430         OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2431
2432         char* table_str = buffer_release( table_buf );
2433         char* col_str   = buffer_release( col_buf );
2434         char* val_str   = buffer_release( val_buf );
2435         growing_buffer* sql = buffer_init( 128 );
2436         buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2437         free( table_str );
2438         free( col_str );
2439         free( val_str );
2440
2441         char* query = buffer_release( sql );
2442
2443         osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2444
2445         jsonObject* obj = NULL;
2446         int rc = 0;
2447
2448         dbi_result result = dbi_conn_query( writehandle, query );
2449         if( !result ) {
2450                 obj = jsonNewObject( NULL );
2451                 const char* msg;
2452                 int errnum = dbi_conn_error( writehandle, &msg );
2453                 osrfLogError(
2454                         OSRF_LOG_MARK,
2455                         "%s ERROR inserting %s object using query [%s]: %d %s",
2456                         modulename,
2457                         osrfHashGet(meta, "fieldmapper"),
2458                         query,
2459                         errnum,
2460                         msg ? msg : "(No description available)"
2461                 );
2462                 osrfAppSessionStatus(
2463                         ctx->session,
2464                         OSRF_STATUS_INTERNALSERVERERROR,
2465                         "osrfMethodException",
2466                         ctx->request,
2467                         "INSERT error -- please see the error log for more details"
2468                 );
2469                 if( !oilsIsDBConnected( writehandle ))
2470                         osrfAppSessionPanic( ctx->session );
2471                 rc = -1;
2472         } else {
2473                 dbi_result_free( result );
2474
2475                 char* id = oilsFMGetString( target, pkey );
2476                 if( !id ) {
2477                         unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2478                         growing_buffer* _id = buffer_init( 10 );
2479                         buffer_fadd( _id, "%lld", new_id );
2480                         id = buffer_release( _id );
2481                 }
2482
2483                 // Find quietness specification, if present
2484                 const char* quiet_str = NULL;
2485                 if( options ) {
2486                         const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2487                         if( quiet_obj )
2488                                 quiet_str = jsonObjectGetString( quiet_obj );
2489                 }
2490
2491                 if( str_is_true( quiet_str )) {  // if quietness is specified
2492                         obj = jsonNewObject( id );
2493                 }
2494                 else {
2495
2496                         // Fetch the row that we just inserted, so that we can return it to the client
2497                         jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2498                         jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2499
2500                         int err = 0;
2501                         jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2502                         if( err )
2503                                 rc = -1;
2504                         else
2505                                 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2506
2507                         jsonObjectFree( list );
2508                         jsonObjectFree( where_clause );
2509                 }
2510
2511                 free( id );
2512         }
2513
2514         free( query );
2515         osrfAppRespondComplete( ctx, obj );
2516         jsonObjectFree( obj );
2517         return rc;
2518 }
2519
2520 /**
2521         @brief Implement the retrieve method.
2522         @param ctx Pointer to the method context.
2523         @param err Pointer through which to return an error code.
2524         @return If successful, a pointer to the result to be returned to the client;
2525         otherwise NULL.
2526
2527         From the method's class, fetch a row with a specified value in the primary key.  This
2528         method relies on the database design convention that a primary key consists of a single
2529         column.
2530
2531         Method parameters:
2532         - authkey (PCRUD only)
2533         - value of the primary key for the desired row, for building the WHERE clause
2534         - a JSON_HASH containing any other SQL clauses: select, join, etc.
2535
2536         Return to client: One row from the query.
2537 */
2538 int doRetrieve( osrfMethodContext* ctx ) {
2539         if(osrfMethodVerifyContext( ctx )) {
2540                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2541                 return -1;
2542         }
2543
2544         if( enforce_pcrud )
2545                 timeout_needs_resetting = 1;
2546
2547         int id_pos = 0;
2548         int order_pos = 1;
2549
2550         if( enforce_pcrud ) {
2551                 id_pos = 1;
2552                 order_pos = 2;
2553         }
2554
2555         // Get the class metadata
2556         osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2557
2558         // Get the value of the primary key, from a method parameter
2559         const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2560
2561         osrfLogDebug(
2562                 OSRF_LOG_MARK,
2563                 "%s retrieving %s object with primary key value of %s",
2564                 modulename,
2565                 osrfHashGet( class_def, "fieldmapper" ),
2566                 jsonObjectGetString( id_obj )
2567         );
2568
2569         // Build a WHERE clause based on the key value
2570         jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2571         jsonObjectSetKey(
2572                 where_clause,
2573                 osrfHashGet( class_def, "primarykey" ),  // name of key column
2574                 jsonObjectClone( id_obj )                // value of key column
2575         );
2576
2577         jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2578
2579         // Do the query
2580         int err = 0;
2581         jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2582
2583         jsonObjectFree( where_clause );
2584         if( err ) {
2585                 osrfAppRespondComplete( ctx, NULL );
2586                 return -1;
2587         }
2588
2589         jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2590         jsonObjectFree( list );
2591
2592         if( enforce_pcrud ) {
2593                 // no result, skip this entirely
2594                 if(NULL != obj && !verifyObjectPCRUD( ctx, class_def, obj, 1 )) {
2595                         jsonObjectFree( obj );
2596
2597                         growing_buffer* msg = buffer_init( 128 );
2598                         OSRF_BUFFER_ADD( msg, modulename );
2599                         OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2600
2601                         char* m = buffer_release( msg );
2602                         osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2603                                         ctx->request, m );
2604                         free( m );
2605
2606                         osrfAppRespondComplete( ctx, NULL );
2607                         return -1;
2608                 }
2609         }
2610
2611         // doFieldmapperSearch() now does the responding for us
2612         //osrfAppRespondComplete( ctx, obj );
2613         osrfAppRespondComplete( ctx, NULL );
2614
2615         jsonObjectFree( obj );
2616         return 0;
2617 }
2618
2619 /**
2620         @brief Translate a numeric value to a string representation for the database.
2621         @param field Pointer to the IDL field definition.
2622         @param value Pointer to a jsonObject holding the value of a field.
2623         @return Pointer to a newly allocated string.
2624
2625         The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2626         its contents are numeric.  A non-numeric string is likely to result in invalid SQL.
2627
2628         If the datatype of the receiving field is not numeric, wrap the value in quotes.
2629
2630         The calling code is responsible for freeing the resulting string by calling free().
2631 */
2632 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2633         growing_buffer* val_buf = buffer_init( 32 );
2634
2635         // If the value is a number and the DB field is numeric, no quotes needed
2636         if( value->type == JSON_NUMBER && !strcmp( get_primitive( field ), "number") ) {
2637                 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2638         } else {
2639                 // Presumably this was really intended to be a string, so quote it
2640                 char* str = jsonObjectToSimpleString( value );
2641                 if( dbi_conn_quote_string( dbhandle, &str )) {
2642                         OSRF_BUFFER_ADD( val_buf, str );
2643                         free( str );
2644                 } else {
2645                         osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2646                         free( str );
2647                         buffer_free( val_buf );
2648                         return NULL;
2649                 }
2650         }
2651
2652         return buffer_release( val_buf );
2653 }
2654
2655 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2656                 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2657         growing_buffer* sql_buf = buffer_init( 32 );
2658
2659         buffer_fadd(
2660                 sql_buf,
2661                 "\"%s\".%s ",
2662                 class_alias,
2663                 osrfHashGet( field, "name" )
2664         );
2665
2666         if( !op ) {
2667                 buffer_add( sql_buf, "IN (" );
2668         } else if( !strcasecmp( op,"not in" )) {
2669                 buffer_add( sql_buf, "NOT IN (" );
2670         } else {
2671                 buffer_add( sql_buf, "IN (" );
2672         }
2673
2674         if( node->type == JSON_HASH ) {
2675                 // subquery predicate
2676                 char* subpred = buildQuery( ctx, node, SUBSELECT );
2677                 if( ! subpred ) {
2678                         buffer_free( sql_buf );
2679                         return NULL;
2680                 }
2681
2682                 buffer_add( sql_buf, subpred );
2683                 free( subpred );
2684
2685         } else if( node->type == JSON_ARRAY ) {
2686                 // literal value list
2687                 int in_item_index = 0;
2688                 int in_item_first = 1;
2689                 const jsonObject* in_item;
2690                 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2691
2692                         if( in_item_first )
2693                                 in_item_first = 0;
2694                         else
2695                                 buffer_add( sql_buf, ", " );
2696
2697                         // Sanity check
2698                         if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2699                                 osrfLogError( OSRF_LOG_MARK,
2700                                                 "%s: Expected string or number within IN list; found %s",
2701                                                 modulename, json_type( in_item->type ) );
2702                                 buffer_free( sql_buf );
2703                                 return NULL;
2704                         }
2705
2706                         // Append the literal value -- quoted if not a number
2707                         if( JSON_NUMBER == in_item->type ) {
2708                                 char* val = jsonNumberToDBString( field, in_item );
2709                                 OSRF_BUFFER_ADD( sql_buf, val );
2710                                 free( val );
2711
2712                         } else if( !strcmp( get_primitive( field ), "number" )) {
2713                                 char* val = jsonNumberToDBString( field, in_item );
2714                                 OSRF_BUFFER_ADD( sql_buf, val );
2715                                 free( val );
2716
2717                         } else {
2718                                 char* key_string = jsonObjectToSimpleString( in_item );
2719                                 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2720                                         OSRF_BUFFER_ADD( sql_buf, key_string );
2721                                         free( key_string );
2722                                 } else {
2723                                         osrfLogError( OSRF_LOG_MARK,
2724                                                         "%s: Error quoting key string [%s]", modulename, key_string );
2725                                         free( key_string );
2726                                         buffer_free( sql_buf );
2727                                         return NULL;
2728                                 }
2729                         }
2730                 }
2731
2732                 if( in_item_first ) {
2733                         osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2734                         buffer_free( sql_buf );
2735                         return NULL;
2736                 }
2737         } else {
2738                 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2739                         modulename, json_type( node->type ));
2740                 buffer_free( sql_buf );
2741                 return NULL;
2742         }
2743
2744         OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2745
2746         return buffer_release( sql_buf );
2747 }
2748
2749 // Receive a JSON_ARRAY representing a function call.  The first
2750 // entry in the array is the function name.  The rest are parameters.
2751 static char* searchValueTransform( const jsonObject* array ) {
2752
2753         if( array->size < 1 ) {
2754                 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2755                 return NULL;
2756         }
2757
2758         // Get the function name
2759         jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2760         if( func_item->type != JSON_STRING ) {
2761                 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2762                         modulename, json_type( func_item->type ));
2763                 return NULL;
2764         }
2765
2766         growing_buffer* sql_buf = buffer_init( 32 );
2767
2768         OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2769         OSRF_BUFFER_ADD( sql_buf, "( " );
2770
2771         // Get the parameters
2772         int func_item_index = 1;   // We already grabbed the zeroth entry
2773         while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2774
2775                 // Add a separator comma, if we need one
2776                 if( func_item_index > 2 )
2777                         buffer_add( sql_buf, ", " );
2778
2779                 // Add the current parameter
2780                 if( func_item->type == JSON_NULL ) {
2781                         buffer_add( sql_buf, "NULL" );
2782                 } else {
2783                         if( func_item->type == JSON_BOOL ) {
2784                                 if( jsonBoolIsTrue(func_item) ) {
2785                                         buffer_add( sql_buf, "TRUE" );
2786                                 } else {
2787                                         buffer_add( sql_buf, "FALSE" );
2788                                 }
2789                         } else {
2790                                 char* val = jsonObjectToSimpleString( func_item );
2791                                 if( dbi_conn_quote_string( dbhandle, &val )) {
2792                                         OSRF_BUFFER_ADD( sql_buf, val );
2793                                         free( val );
2794                                 } else {
2795                                         osrfLogError( OSRF_LOG_MARK, 
2796                                                 "%s: Error quoting key string [%s]", modulename, val );
2797                                         buffer_free( sql_buf );
2798                                         free( val );
2799                                         return NULL;
2800                                 }
2801                         }
2802                 }
2803         }
2804
2805         buffer_add( sql_buf, " )" );
2806
2807         return buffer_release( sql_buf );
2808 }
2809
2810 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2811                 const jsonObject* node, const char* op ) {
2812
2813         if( ! is_good_operator( op ) ) {
2814                 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2815                 return NULL;
2816         }
2817
2818         char* val = searchValueTransform( node );
2819         if( !val )
2820                 return NULL;
2821
2822         const char* right_percent = "";
2823         const char* real_op       = op;
2824
2825         if( !strcasecmp( op, "startwith") ) {
2826                 real_op = "like";
2827                 right_percent = "|| '%'";
2828         }
2829
2830         growing_buffer* sql_buf = buffer_init( 32 );
2831         buffer_fadd(
2832                 sql_buf,
2833                 "\"%s\".%s %s %s%s",
2834                 class_alias,
2835                 osrfHashGet( field, "name" ),
2836                 real_op,
2837                 val,
2838                 right_percent
2839         );
2840
2841         free( val );
2842
2843         return buffer_release( sql_buf );
2844 }
2845
2846 // class_alias is a class name or other table alias
2847 // field is a field definition as stored in the IDL
2848 // node comes from the method parameter, and may represent an entry in the SELECT list
2849 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2850                 const jsonObject* node ) {
2851         growing_buffer* sql_buf = buffer_init( 32 );
2852
2853         const char* field_transform = jsonObjectGetString(
2854                 jsonObjectGetKeyConst( node, "transform" ) );
2855         const char* transform_subcolumn = jsonObjectGetString(
2856                 jsonObjectGetKeyConst( node, "result_field" ) );
2857
2858         if( transform_subcolumn ) {
2859                 if( ! is_identifier( transform_subcolumn ) ) {
2860                         osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2861                                         modulename, transform_subcolumn );
2862                         buffer_free( sql_buf );
2863                         return NULL;
2864                 }
2865                 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' );    // enclose transform in parentheses
2866         }
2867
2868         if( field_transform ) {
2869
2870                 if( ! is_identifier( field_transform ) ) {
2871                         osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2872                                         modulename, field_transform );
2873                         buffer_free( sql_buf );
2874                         return NULL;
2875                 }
2876
2877                 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2878                         buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2879                                 field_transform, class_alias, osrfHashGet( field, "name" ));
2880                 } else {
2881                         buffer_fadd( sql_buf, "%s(\"%s\".%s",
2882                                 field_transform, class_alias, osrfHashGet( field, "name" ));
2883                 }
2884
2885                 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2886
2887                 if( array ) {
2888                         if( array->type != JSON_ARRAY ) {
2889                                 osrfLogError( OSRF_LOG_MARK,
2890                                         "%s: Expected JSON_ARRAY for function params; found %s",
2891                                         modulename, json_type( array->type ) );
2892                                 buffer_free( sql_buf );
2893                                 return NULL;
2894                         }
2895                         int func_item_index = 0;
2896                         jsonObject* func_item;
2897                         while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2898
2899                                 char* val = jsonObjectToSimpleString( func_item );
2900
2901                                 if( !val ) {
2902                                         buffer_add( sql_buf, ",NULL" );
2903                                 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2904                                         OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2905                                         OSRF_BUFFER_ADD( sql_buf, val );
2906                                 } else {
2907                                         osrfLogError( OSRF_LOG_MARK,
2908                                                         "%s: Error quoting key string [%s]", modulename, val );
2909                                         free( val );
2910                                         buffer_free( sql_buf );
2911                                         return NULL;
2912                                 }
2913                                 free( val );
2914                         }
2915                 }
2916
2917                 buffer_add( sql_buf, " )" );
2918
2919         } else {
2920                 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2921         }
2922
2923         if( transform_subcolumn )
2924                 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2925
2926         return buffer_release( sql_buf );
2927 }
2928
2929 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2930                 const jsonObject* node, const char* op ) {
2931
2932         if( ! is_good_operator( op ) ) {
2933                 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2934                 return NULL;
2935         }
2936
2937         char* field_transform = searchFieldTransform( class_info->alias, field, node );
2938         if( ! field_transform )
2939                 return NULL;
2940         char* value = NULL;
2941         int extra_parens = 0;   // boolean
2942
2943         const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2944         if( ! value_obj ) {
2945                 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2946                 if( !value ) {
2947                         osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2948                                 modulename );
2949                         free( field_transform );
2950                         return NULL;
2951                 }
2952                 extra_parens = 1;
2953         } else if( value_obj->type == JSON_ARRAY ) {
2954                 value = searchValueTransform( value_obj );
2955                 if( !value ) {
2956                         osrfLogError( OSRF_LOG_MARK,
2957                                 "%s: Error building value transform for field transform", modulename );
2958                         free( field_transform );
2959                         return NULL;
2960                 }
2961         } else if( value_obj->type == JSON_HASH ) {
2962                 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2963                 if( !value ) {
2964                         osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2965                                 modulename );
2966                         free( field_transform );
2967                         return NULL;
2968                 }
2969                 extra_parens = 1;
2970         } else if( value_obj->type == JSON_NUMBER ) {
2971                 value = jsonNumberToDBString( field, value_obj );
2972         } else if( value_obj->type == JSON_NULL ) {
2973                 osrfLogError( OSRF_LOG_MARK,
2974                         "%s: Error building predicate for field transform: null value", modulename );
2975                 free( field_transform );
2976                 return NULL;
2977         } else if( value_obj->type == JSON_BOOL ) {
2978                 osrfLogError( OSRF_LOG_MARK,
2979                         "%s: Error building predicate for field transform: boolean value", modulename );
2980                 free( field_transform );
2981                 return NULL;
2982         } else {
2983                 if( !strcmp( get_primitive( field ), "number") ) {
2984                         value = jsonNumberToDBString( field, value_obj );
2985                 } else {
2986                         value = jsonObjectToSimpleString( value_obj );
2987                         if( !dbi_conn_quote_string( dbhandle, &value )) {
2988                                 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2989                                         modulename, value );
2990                                 free( value );
2991                                 free( field_transform );
2992                                 return NULL;
2993                         }
2994                 }
2995         }
2996
2997         const char* left_parens  = "";
2998         const char* right_parens = "";
2999
3000         if( extra_parens ) {
3001                 left_parens  = "(";
3002                 right_parens = ")";
3003         }
3004
3005         const char* right_percent = "";
3006         const char* real_op       = op;
3007
3008         if( !strcasecmp( op, "startwith") ) {
3009                 real_op = "like";
3010                 right_percent = "|| '%'";
3011         }
3012
3013         growing_buffer* sql_buf = buffer_init( 32 );
3014
3015         buffer_fadd(
3016                 sql_buf,
3017                 "%s%s %s %s %s%s %s%s",
3018                 left_parens,
3019                 field_transform,
3020                 real_op,
3021                 left_parens,
3022                 value,
3023                 right_percent,
3024                 right_parens,
3025                 right_parens
3026         );
3027
3028         free( value );
3029         free( field_transform );
3030
3031         return buffer_release( sql_buf );
3032 }
3033
3034 static char* searchSimplePredicate( const char* op, const char* class_alias,
3035                 osrfHash* field, const jsonObject* node ) {
3036
3037         if( ! is_good_operator( op ) ) {
3038                 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
3039                 return NULL;
3040         }
3041
3042         char* val = NULL;
3043
3044         // Get the value to which we are comparing the specified column
3045         if( node->type != JSON_NULL ) {
3046                 if( node->type == JSON_NUMBER ) {
3047                         val = jsonNumberToDBString( field, node );
3048                 } else if( !strcmp( get_primitive( field ), "number" ) ) {
3049                         val = jsonNumberToDBString( field, node );
3050                 } else {
3051                         val = jsonObjectToSimpleString( node );
3052                 }
3053         }
3054
3055         if( val ) {
3056                 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
3057                         // Value is not numeric; enclose it in quotes
3058                         if( !dbi_conn_quote_string( dbhandle, &val ) ) {
3059                                 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
3060                                         modulename, val );
3061                                 free( val );
3062                                 return NULL;
3063                         }
3064                 }
3065         } else {
3066                 // Compare to a null value
3067                 val = strdup( "NULL" );
3068                 if( strcmp( op, "=" ))
3069                         op = "IS NOT";
3070                 else
3071                         op = "IS";
3072         }
3073
3074         const char* right_percent = "";
3075         const char* real_op       = op;
3076
3077         if( !strcasecmp( op, "startwith") ) {
3078                 real_op = "like";
3079                 right_percent = "|| '%'";
3080         }
3081
3082         growing_buffer* sql_buf = buffer_init( 32 );
3083         buffer_fadd( sql_buf, "\"%s\".%s %s %s%s", class_alias, osrfHashGet(field, "name"), real_op, val, right_percent );
3084         char* pred = buffer_release( sql_buf );
3085
3086         free( val );
3087
3088         return pred;
3089 }
3090
3091 static char* searchBETWEENPredicate( const char* class_alias,
3092                 osrfHash* field, const jsonObject* node ) {
3093
3094         const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
3095         const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
3096
3097         if( NULL == y_node ) {
3098                 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
3099                 return NULL;
3100         }
3101         else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
3102                 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
3103                 return NULL;
3104         }
3105
3106         char* x_string;
3107         char* y_string;
3108
3109         if( !strcmp( get_primitive( field ), "number") ) {
3110                 x_string = jsonNumberToDBString( field, x_node );
3111                 y_string = jsonNumberToDBString( field, y_node );
3112
3113         } else {
3114                 x_string = jsonObjectToSimpleString( x_node );
3115                 y_string = jsonObjectToSimpleString( y_node );
3116                 if( !(dbi_conn_quote_string( dbhandle, &x_string )
3117                         && dbi_conn_quote_string( dbhandle, &y_string )) ) {
3118                         osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
3119                                         modulename, x_string, y_string );
3120                         free( x_string );
3121                         free( y_string );
3122                         return NULL;
3123                 }
3124         }
3125
3126         growing_buffer* sql_buf = buffer_init( 32 );
3127         buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
3128                         class_alias, osrfHashGet( field, "name" ), x_string, y_string );
3129         free( x_string );
3130         free( y_string );
3131
3132         return buffer_release( sql_buf );
3133 }
3134
3135 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
3136                                                           jsonObject* node, osrfMethodContext* ctx ) {
3137
3138         char* pred = NULL;
3139         if( node->type == JSON_ARRAY ) { // equality IN search
3140                 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
3141         } else if( node->type == JSON_HASH ) { // other search
3142                 jsonIterator* pred_itr = jsonNewIterator( node );
3143                 if( !jsonIteratorHasNext( pred_itr ) ) {
3144                         osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
3145                                         modulename, osrfHashGet(field, "name" ));
3146                 } else {
3147                         jsonObject* pred_node = jsonIteratorNext( pred_itr );
3148
3149                         // Verify that there are no additional predicates
3150                         if( jsonIteratorHasNext( pred_itr ) ) {
3151                                 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
3152                                                 modulename, osrfHashGet(field, "name" ));
3153                         } else if( !(strcasecmp( pred_itr->key,"between" )) )
3154                                 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
3155                         else if( !(strcasecmp( pred_itr->key,"in" ))
3156                                         || !(strcasecmp( pred_itr->key,"not in" )) )
3157                                 pred = searchINPredicate(
3158                                         class_info->alias, field, pred_node, pred_itr->key, ctx );
3159                         else if( pred_node->type == JSON_ARRAY )
3160                                 pred = searchFunctionPredicate(
3161                                         class_info->alias, field, pred_node, pred_itr->key );
3162                         else if( pred_node->type == JSON_HASH )
3163                                 pred = searchFieldTransformPredicate(
3164                                         class_info, field, pred_node, pred_itr->key );
3165                         else
3166                                 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
3167                 }
3168                 jsonIteratorFree( pred_itr );
3169
3170         } else if( node->type == JSON_NULL ) { // IS NULL search
3171                 growing_buffer* _p = buffer_init( 64 );
3172                 buffer_fadd(
3173                         _p,
3174                         "\"%s\".%s IS NULL",
3175                         class_info->alias,
3176                         osrfHashGet( field, "name" )
3177                 );
3178                 pred = buffer_release( _p );
3179         } else { // equality search
3180                 pred = searchSimplePredicate( "=", class_info->alias, field, node );
3181         }
3182
3183         return pred;
3184
3185 }
3186
3187
3188 /*
3189
3190 join : {
3191         acn : {
3192                 field : record,
3193                 fkey : id
3194                 type : left
3195                 filter_op : or
3196                 filter : { ... },
3197                 join : {
3198                         acp : {
3199                                 field : call_number,
3200                                 fkey : id,
3201                                 filter : { ... },
3202                         },
3203                 },
3204         },
3205         mrd : {
3206                 field : record,
3207                 type : inner
3208                 fkey : id,
3209                 filter : { ... },
3210         }
3211 }
3212
3213 */
3214
3215 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3216
3217         const jsonObject* working_hash;
3218         jsonObject* freeable_hash = NULL;
3219
3220         if( join_hash->type == JSON_HASH ) {
3221                 working_hash = join_hash;
3222         } else if( join_hash->type == JSON_STRING ) {
3223                 // turn it into a JSON_HASH by creating a wrapper
3224                 // around a copy of the original
3225                 const char* _tmp = jsonObjectGetString( join_hash );
3226                 freeable_hash = jsonNewObjectType( JSON_HASH );
3227                 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3228                 working_hash = freeable_hash;
3229         } else {
3230                 osrfLogError(
3231                         OSRF_LOG_MARK,
3232                         "%s: JOIN failed; expected JSON object type not found",
3233                         modulename
3234                 );
3235                 return NULL;
3236         }
3237
3238         growing_buffer* join_buf = buffer_init( 128 );
3239         const char* leftclass = left_info->class_name;
3240
3241         jsonObject* snode = NULL;
3242         jsonIterator* search_itr = jsonNewIterator( working_hash );
3243
3244         while ( (snode = jsonIteratorNext( search_itr )) ) {
3245                 const char* right_alias = search_itr->key;
3246                 const char* class =
3247                                 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3248                 if( ! class )
3249                         class = right_alias;
3250
3251                 const ClassInfo* right_info = add_joined_class( right_alias, class );
3252                 if( !right_info ) {
3253                         osrfLogError(
3254                                 OSRF_LOG_MARK,
3255                                 "%s: JOIN failed.  Class \"%s\" not resolved in IDL",
3256                                 modulename,
3257                                 search_itr->key
3258                         );
3259                         jsonIteratorFree( search_itr );
3260                         buffer_free( join_buf );
3261                         if( freeable_hash )
3262                                 jsonObjectFree( freeable_hash );
3263                         return NULL;
3264                 }
3265                 osrfHash* links    = right_info->links;
3266                 const char* table  = right_info->source_def;
3267
3268                 const char* fkey  = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3269                 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3270
3271                 if( field && !fkey ) {
3272                         // Look up the corresponding join column in the IDL.
3273                         // The link must be defined in the child table,
3274                         // and point to the right parent table.
3275                         osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3276                         const char* reltype = NULL;
3277                         const char* other_class = NULL;
3278                         reltype = osrfHashGet( idl_link, "reltype" );
3279                         if( reltype && strcmp( reltype, "has_many" ) )
3280                                 other_class = osrfHashGet( idl_link, "class" );
3281                         if( other_class && !strcmp( other_class, leftclass ) )
3282                                 fkey = osrfHashGet( idl_link, "key" );
3283                         if( !fkey ) {
3284                                 osrfLogError(
3285                                         OSRF_LOG_MARK,
3286                                         "%s: JOIN failed.  No link defined from %s.%s to %s",
3287                                         modulename,
3288                                         class,
3289                                         field,
3290                                         leftclass
3291                                 );
3292                                 buffer_free( join_buf );
3293                                 if( freeable_hash )
3294                                         jsonObjectFree( freeable_hash );
3295                                 jsonIteratorFree( search_itr );
3296                                 return NULL;
3297                         }
3298
3299                 } else if( !field && fkey ) {
3300                         // Look up the corresponding join column in the IDL.
3301                         // The link must be defined in the child table,
3302                         // and point to the right parent table.
3303                         osrfHash* left_links = left_info->links;
3304                         osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3305                         const char* reltype = NULL;
3306                         const char* other_class = NULL;
3307                         reltype = osrfHashGet( idl_link, "reltype" );
3308                         if( reltype && strcmp( reltype, "has_many" ) )
3309                                 other_class = osrfHashGet( idl_link, "class" );
3310                         if( other_class && !strcmp( other_class, class ) )
3311                                 field = osrfHashGet( idl_link, "key" );
3312                         if( !field ) {
3313                                 osrfLogError(
3314                                         OSRF_LOG_MARK,
3315                                         "%s: JOIN failed.  No link defined from %s.%s to %s",
3316                                         modulename,
3317                                         leftclass,
3318                                         fkey,
3319                                         class
3320                                 );
3321                                 buffer_free( join_buf );
3322                                 if( freeable_hash )
3323                                         jsonObjectFree( freeable_hash );
3324                                 jsonIteratorFree( search_itr );
3325                                 return NULL;
3326                         }
3327
3328                 } else if( !field && !fkey ) {
3329                         osrfHash* left_links = left_info->links;
3330
3331                         // For each link defined for the left class:
3332                         // see if the link references the joined class
3333                         osrfHashIterator* itr = osrfNewHashIterator( left_links );
3334                         osrfHash* curr_link = NULL;
3335                         while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3336                                 const char* other_class = osrfHashGet( curr_link, "class" );
3337                                 if( other_class && !strcmp( other_class, class ) ) {
3338
3339                                         // In the IDL, the parent class doesn't always know then names of the child
3340                                         // columns that are pointing to it, so don't use that end of the link
3341                                         const char* reltype = osrfHashGet( curr_link, "reltype" );
3342                                         if( reltype && strcmp( reltype, "has_many" ) ) {
3343                                                 // Found a link between the classes
3344                                                 fkey = osrfHashIteratorKey( itr );
3345                                                 field = osrfHashGet( curr_link, "key" );
3346                                                 break;
3347                                         }
3348                                 }
3349                         }
3350                         osrfHashIteratorFree( itr );
3351
3352                         if( !field || !fkey ) {
3353                                 // Do another such search, with the classes reversed
3354
3355                                 // For each link defined for the joined class:
3356                                 // see if the link references the left class
3357                                 osrfHashIterator* itr = osrfNewHashIterator( links );
3358                                 osrfHash* curr_link = NULL;
3359                                 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3360                                         const char* other_class = osrfHashGet( curr_link, "class" );
3361                                         if( other_class && !strcmp( other_class, leftclass ) ) {
3362
3363                                                 // In the IDL, the parent class doesn't know then names of the child
3364                                                 // columns that are pointing to it, so don't use that end of the link
3365                                                 const char* reltype = osrfHashGet( curr_link, "reltype" );
3366                                                 if( reltype && strcmp( reltype, "has_many" ) ) {
3367                                                         // Found a link between the classes
3368                                                         field = osrfHashIteratorKey( itr );
3369                                                         fkey = osrfHashGet( curr_link, "key" );
3370                                                         break;
3371                                                 }
3372                                         }
3373                                 }
3374                                 osrfHashIteratorFree( itr );
3375                         }
3376
3377                         if( !field || !fkey ) {
3378                                 osrfLogError(
3379                                         OSRF_LOG_MARK,
3380                                         "%s: JOIN failed.  No link defined between %s and %s",
3381                                         modulename,
3382                                         leftclass,
3383                                         class
3384                                 );
3385                                 buffer_free( join_buf );
3386                                 if( freeable_hash )
3387                                         jsonObjectFree( freeable_hash );
3388                                 jsonIteratorFree( search_itr );
3389                                 return NULL;
3390                         }
3391                 }
3392
3393                 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3394                 if( type ) {
3395                         if( !strcasecmp( type,"left" )) {
3396                                 buffer_add( join_buf, " LEFT JOIN" );
3397                         } else if( !strcasecmp( type,"right" )) {
3398                                 buffer_add( join_buf, " RIGHT JOIN" );
3399                         } else if( !strcasecmp( type,"full" )) {
3400                                 buffer_add( join_buf, " FULL JOIN" );
3401                         } else {
3402                                 buffer_add( join_buf, " INNER JOIN" );
3403                         }
3404                 } else {
3405                         buffer_add( join_buf, " INNER JOIN" );
3406                 }
3407
3408                 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3409                                         table, right_alias, right_alias, field, left_info->alias, fkey );
3410
3411                 // Add any other join conditions as specified by "filter"
3412                 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3413                 if( filter ) {
3414                         const char* filter_op = jsonObjectGetString(
3415                                 jsonObjectGetKeyConst( snode, "filter_op" ) );
3416                         if( filter_op && !strcasecmp( "or",filter_op )) {
3417                                 buffer_add( join_buf, " OR " );
3418                         } else {
3419                                 buffer_add( join_buf, " AND " );
3420                         }
3421
3422                         char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3423                         if( jpred ) {
3424                                 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3425                                 OSRF_BUFFER_ADD( join_buf, jpred );
3426                                 free( jpred );
3427                         } else {
3428                                 osrfLogError(
3429                                         OSRF_LOG_MARK,
3430                                         "%s: JOIN failed.  Invalid conditional expression.",
3431                                         modulename
3432                                 );
3433                                 jsonIteratorFree( search_itr );
3434                                 buffer_free( join_buf );
3435                                 if( freeable_hash )
3436                                         jsonObjectFree( freeable_hash );
3437                                 return NULL;
3438                         }
3439                 }
3440
3441                 buffer_add( join_buf, " ) " );
3442
3443                 // Recursively add a nested join, if one is present
3444                 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3445                 if( join_filter ) {
3446                         char* jpred = searchJOIN( join_filter, right_info );
3447                         if( jpred ) {
3448                                 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3449                                 OSRF_BUFFER_ADD( join_buf, jpred );
3450                                 free( jpred );
3451                         } else {
3452                                 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3453                                 jsonIteratorFree( search_itr );
3454                                 buffer_free( join_buf );
3455                                 if( freeable_hash )
3456                                         jsonObjectFree( freeable_hash );
3457                                 return NULL;
3458                         }
3459                 }
3460         }
3461
3462         if( freeable_hash )
3463                 jsonObjectFree( freeable_hash );
3464         jsonIteratorFree( search_itr );
3465
3466         return buffer_release( join_buf );
3467 }
3468
3469 /*
3470
3471 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3472 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3473 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3474
3475 Generate code to express a set of conditions, as for a WHERE clause.  Parameters:
3476
3477 search_hash is the JSON expression of the conditions.
3478 meta is the class definition from the IDL, for the relevant table.
3479 opjoin_type indicates whether multiple conditions, if present, should be
3480         connected by AND or OR.
3481 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3482         to pass it to other functions -- and all they do with it is to use the session
3483         and request members to send error messages back to the client.
3484
3485 */
3486
3487 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3488                 int opjoin_type, osrfMethodContext* ctx ) {
3489
3490         osrfLogDebug(
3491                 OSRF_LOG_MARK,
3492                 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3493                 "opjoin_type = %d, ctx addr = %p",
3494                 modulename,
3495                 search_hash,
3496                 class_info->class_def,
3497                 opjoin_type,
3498                 ctx
3499         );
3500
3501         growing_buffer* sql_buf = buffer_init( 128 );
3502
3503         jsonObject* node = NULL;
3504
3505         int first = 1;
3506         if( search_hash->type == JSON_ARRAY ) {
3507                 if( 0 == search_hash->size ) {
3508                         osrfLogError(
3509                                 OSRF_LOG_MARK,
3510                                 "%s: Invalid predicate structure: empty JSON array",
3511                                 modulename
3512                         );
3513                         buffer_free( sql_buf );
3514                         return NULL;
3515                 }
3516
3517                 unsigned long i = 0;
3518                 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3519                         if( first ) {
3520                                 first = 0;
3521                         } else {
3522                                 if( opjoin_type == OR_OP_JOIN )
3523                                         buffer_add( sql_buf, " OR " );
3524                                 else
3525                                         buffer_add( sql_buf, " AND " );
3526                         }
3527
3528                         char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3529                         if( ! subpred ) {
3530                                 buffer_free( sql_buf );
3531                                 return NULL;
3532                         }
3533
3534                         buffer_fadd( sql_buf, "( %s )", subpred );
3535                         free( subpred );
3536                 }
3537
3538         } else if( search_hash->type == JSON_HASH ) {
3539                 osrfLogDebug( OSRF_LOG_MARK,
3540                         "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3541                 jsonIterator* search_itr = jsonNewIterator( search_hash );
3542                 if( !jsonIteratorHasNext( search_itr ) ) {
3543                         osrfLogError(
3544                                 OSRF_LOG_MARK,
3545                                 "%s: Invalid predicate structure: empty JSON object",
3546                                 modulename
3547                         );
3548                         jsonIteratorFree( search_itr );
3549                         buffer_free( sql_buf );
3550                         return NULL;
3551                 }
3552
3553                 while( (node = jsonIteratorNext( search_itr )) ) {
3554
3555                         if( first ) {
3556                                 first = 0;
3557                         } else {
3558                                 if( opjoin_type == OR_OP_JOIN )
3559                                         buffer_add( sql_buf, " OR " );
3560                                 else
3561                                         buffer_add( sql_buf, " AND " );
3562                         }
3563
3564                         if( '+' == search_itr->key[ 0 ] ) {
3565
3566                                 // This plus sign prefixes a class name or other table alias;
3567                                 // make sure the table alias is in scope
3568                                 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3569                                 if( ! alias_info ) {
3570                                         osrfLogError(
3571                                                          OSRF_LOG_MARK,
3572                                                         "%s: Invalid table alias \"%s\" in WHERE clause",
3573                                                         modulename,
3574                                                         search_itr->key + 1
3575                                         );
3576                                         jsonIteratorFree( search_itr );
3577                                         buffer_free( sql_buf );
3578                                         return NULL;
3579                                 }
3580
3581                                 if( node->type == JSON_STRING ) {
3582                                         // It's the name of a column; make sure it belongs to the class
3583                                         const char* fieldname = jsonObjectGetString( node );
3584                                         if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3585                                                 osrfLogError(
3586                                                         OSRF_LOG_MARK,
3587                                                         "%s: Invalid column name \"%s\" in WHERE clause "
3588                                                         "for table alias \"%s\"",
3589                                                         modulename,
3590                                                         fieldname,
3591                                                         alias_info->alias
3592                                                 );
3593                                                 jsonIteratorFree( search_itr );
3594                                                 buffer_free( sql_buf );
3595                                                 return NULL;
3596                                         }
3597
3598                                         buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3599                                 } else {
3600                                         // It's something more complicated
3601                                         char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3602                                         if( ! subpred ) {
3603                                                 jsonIteratorFree( search_itr );
3604                                                 buffer_free( sql_buf );
3605                                                 return NULL;
3606                                         }
3607
3608                                         buffer_fadd( sql_buf, "( %s )", subpred );
3609                                         free( subpred );
3610                                 }
3611                         } else if( '-' == search_itr->key[ 0 ] ) {
3612                                 if( !strcasecmp( "-or", search_itr->key )) {
3613                                         char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3614                                         if( ! subpred ) {
3615                                                 jsonIteratorFree( search_itr );
3616                                                 buffer_free( sql_buf );
3617                                                 return NULL;
3618                                         }
3619
3620                                         buffer_fadd( sql_buf, "( %s )", subpred );
3621                                         free( subpred );
3622                                 } else if( !strcasecmp( "-and", search_itr->key )) {
3623                                         char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3624                                         if( ! subpred ) {
3625                                                 jsonIteratorFree( search_itr );
3626                                                 buffer_free( sql_buf );
3627                                                 return NULL;
3628                                         }
3629
3630                                         buffer_fadd( sql_buf, "( %s )", subpred );
3631                                         free( subpred );
3632                                 } else if( !strcasecmp("-not",search_itr->key) ) {
3633                                         char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3634                                         if( ! subpred ) {
3635                                                 jsonIteratorFree( search_itr );
3636                                                 buffer_free( sql_buf );
3637                                                 return NULL;
3638                                         }
3639
3640                                         buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3641                                         free( subpred );
3642                                 } else if( !strcasecmp( "-exists", search_itr->key )) {
3643                                         char* subpred = buildQuery( ctx, node, SUBSELECT );
3644                                         if( ! subpred ) {
3645                                                 jsonIteratorFree( search_itr );
3646                                                 buffer_free( sql_buf );
3647                                                 return NULL;
3648                                         }
3649
3650                                         buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3651                                         free( subpred );
3652                                 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3653                                         char* subpred = buildQuery( ctx, node, SUBSELECT );
3654                                         if( ! subpred ) {
3655                                                 jsonIteratorFree( search_itr );
3656                                                 buffer_free( sql_buf );
3657                                                 return NULL;
3658                                         }
3659
3660                                         buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3661                                         free( subpred );
3662                                 } else {     // Invalid "minus" operator
3663                                         osrfLogError(
3664                                                          OSRF_LOG_MARK,
3665                                                         "%s: Invalid operator \"%s\" in WHERE clause",
3666                                                         modulename,
3667                                                         search_itr->key
3668                                         );
3669                                         jsonIteratorFree( search_itr );
3670                                         buffer_free( sql_buf );
3671                                         return NULL;
3672                                 }
3673
3674                         } else {
3675
3676                                 const char* class = class_info->class_name;
3677                                 osrfHash* fields = class_info->fields;
3678                                 osrfHash* field = osrfHashGet( fields, search_itr->key );
3679
3680                                 if( !field ) {
3681                                         const char* table = class_info->source_def;
3682                                         osrfLogError(
3683                                                 OSRF_LOG_MARK,
3684                                                 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3685                                                 modulename,
3686                                                 search_itr->key,
3687                                                 table ? table : "?",
3688                                                 class ? class : "?"
3689                                         );
3690                                         jsonIteratorFree( search_itr );
3691                                         buffer_free( sql_buf );
3692                                         return NULL;
3693                                 }
3694
3695                                 char* subpred = searchPredicate( class_info, field, node, ctx );
3696                                 if( ! subpred ) {
3697                                         buffer_free( sql_buf );
3698                                         jsonIteratorFree( search_itr );
3699                                         return NULL;
3700                                 }
3701
3702                                 buffer_add( sql_buf, subpred );
3703                                 free( subpred );
3704                         }
3705                 }
3706                 jsonIteratorFree( search_itr );
3707
3708         } else {
3709                 // ERROR ... only hash and array allowed at this level
3710                 char* predicate_string = jsonObjectToJSON( search_hash );
3711                 osrfLogError(
3712                         OSRF_LOG_MARK,
3713                         "%s: Invalid predicate structure: %s",
3714                         modulename,
3715                         predicate_string
3716                 );
3717                 buffer_free( sql_buf );
3718                 free( predicate_string );
3719                 return NULL;
3720         }
3721
3722         return buffer_release( sql_buf );
3723 }
3724
3725 /* Build a JSON_ARRAY of field names for a given table alias
3726 */
3727 static jsonObject* defaultSelectList( const char* table_alias ) {
3728
3729         if( ! table_alias )
3730                 table_alias = "";
3731
3732         ClassInfo* class_info = search_all_alias( table_alias );
3733         if( ! class_info ) {
3734                 osrfLogError(
3735                         OSRF_LOG_MARK,
3736                         "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3737                         modulename,
3738                         table_alias
3739                 );
3740                 return NULL;
3741         }
3742
3743         jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3744         osrfHash* field_def = NULL;
3745         osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3746         while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3747                 const char* field_name = osrfHashIteratorKey( field_itr );
3748                 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3749                         jsonObjectPush( array, jsonNewObject( field_name ) );
3750                 }
3751         }
3752         osrfHashIteratorFree( field_itr );
3753
3754         return array;
3755 }
3756
3757 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3758 // The jsonObject must be a JSON_HASH with an single entry for "union",
3759 // "intersect", or "except".  The data associated with this key must be an
3760 // array of hashes, each hash being a query.
3761 // Also allowed but currently ignored: entries for "order_by" and "alias".
3762 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3763         // Sanity check
3764         if( ! combo || combo->type != JSON_HASH )
3765                 return NULL;      // should be impossible; validated by caller
3766
3767         const jsonObject* query_array = NULL;   // array of subordinate queries
3768         const char* op = NULL;     // name of operator, e.g. UNION
3769         const char* alias = NULL;  // alias for the query (needed for ORDER BY)
3770         int op_count = 0;          // for detecting conflicting operators
3771         int excepting = 0;         // boolean
3772         int all = 0;               // boolean
3773         jsonObject* order_obj = NULL;
3774
3775         // Identify the elements in the hash
3776         jsonIterator* query_itr = jsonNewIterator( combo );
3777         jsonObject* curr_obj = NULL;
3778         while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3779                 if( ! strcmp( "union", query_itr->key ) ) {
3780                         ++op_count;
3781                         op = " UNION ";
3782                         query_array = curr_obj;
3783                 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3784                         ++op_count;
3785                         op = " INTERSECT ";
3786                         query_array = curr_obj;
3787                 } else if( ! strcmp( "except", query_itr->key ) ) {
3788                         ++op_count;
3789                         op = " EXCEPT ";
3790                         excepting = 1;
3791                         query_array = curr_obj;
3792                 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3793                         osrfLogWarning(
3794                                 OSRF_LOG_MARK,
3795                                 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3796                                 modulename
3797                         );
3798                         order_obj = curr_obj;
3799                 } else if( ! strcmp( "alias", query_itr->key ) ) {
3800                         if( curr_obj->type != JSON_STRING ) {
3801                                 jsonIteratorFree( query_itr );
3802                                 return NULL;
3803                         }
3804                         alias = jsonObjectGetString( curr_obj );
3805                 } else if( ! strcmp( "all", query_itr->key ) ) {
3806                         if( obj_is_true( curr_obj ) )
3807                                 all = 1;
3808                 } else {
3809                         if( ctx )
3810                                 osrfAppSessionStatus(
3811                                         ctx->session,
3812                                         OSRF_STATUS_INTERNALSERVERERROR,
3813                                         "osrfMethodException",
3814                                         ctx->request,
3815                                         "Malformed query; unexpected entry in query object"
3816                                 );
3817                         osrfLogError(
3818                                 OSRF_LOG_MARK,
3819                                 "%s: Unexpected entry for \"%s\" in%squery",
3820                                 modulename,
3821                                 query_itr->key,
3822                                 op
3823                         );
3824                         jsonIteratorFree( query_itr );
3825                         return NULL;
3826                 }
3827         }
3828         jsonIteratorFree( query_itr );
3829
3830         // More sanity checks
3831         if( ! query_array ) {
3832                 if( ctx )
3833                         osrfAppSessionStatus(
3834                                 ctx->session,
3835                                 OSRF_STATUS_INTERNALSERVERERROR,
3836                                 "osrfMethodException",
3837                                 ctx->request,
3838                                 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3839                         );
3840                 osrfLogError(
3841                         OSRF_LOG_MARK,
3842                         "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3843                         modulename
3844                 );
3845                 return NULL;        // should be impossible...
3846         } else if( op_count > 1 ) {
3847                 if( ctx )
3848                                 osrfAppSessionStatus(
3849                                 ctx->session,
3850                                 OSRF_STATUS_INTERNALSERVERERROR,
3851                                 "osrfMethodException",
3852                                 ctx->request,
3853                                 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3854                         );
3855                 osrfLogError(
3856                         OSRF_LOG_MARK,
3857                         "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3858                         modulename
3859                 );
3860                 return NULL;
3861         } if( query_array->type != JSON_ARRAY ) {
3862                 if( ctx )
3863                                 osrfAppSessionStatus(
3864                                 ctx->session,
3865                                 OSRF_STATUS_INTERNALSERVERERROR,
3866                                 "osrfMethodException",
3867                                 ctx->request,
3868                                 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3869                         );
3870                 osrfLogError(
3871                         OSRF_LOG_MARK,
3872                         "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3873                         modulename,
3874                         op,
3875                         json_type( query_array->type )
3876                 );
3877                 return NULL;
3878         } if( query_array->size < 2 ) {
3879                 if( ctx )
3880                         osrfAppSessionStatus(
3881                                 ctx->session,
3882                                 OSRF_STATUS_INTERNALSERVERERROR,
3883                                 "osrfMethodException",
3884                                 ctx->request,
3885                                 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3886                         );
3887                 osrfLogError(
3888                         OSRF_LOG_MARK,
3889                         "%s:%srequires multiple queries as operands",
3890                         modulename,
3891                         op
3892                 );
3893                 return NULL;
3894         } else if( excepting && query_array->size > 2 ) {
3895                 if( ctx )
3896                         osrfAppSessionStatus(
3897                                 ctx->session,
3898                                 OSRF_STATUS_INTERNALSERVERERROR,
3899                                 "osrfMethodException",
3900                                 ctx->request,
3901                                 "EXCEPT operator has too many queries as operands"
3902                         );
3903                 osrfLogError(
3904                         OSRF_LOG_MARK,
3905                         "%s:EXCEPT operator has too many queries as operands",
3906                         modulename
3907                 );
3908                 return NULL;
3909         } else if( order_obj && ! alias ) {
3910                 if( ctx )
3911                         osrfAppSessionStatus(
3912                                 ctx->session,
3913                                 OSRF_STATUS_INTERNALSERVERERROR,
3914                                 "osrfMethodException",
3915                                 ctx->request,
3916                                 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3917                         );
3918                 osrfLogError(
3919                         OSRF_LOG_MARK,
3920                         "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3921                         modulename
3922                 );
3923                 return NULL;
3924         }
3925
3926         // So far so good.  Now build the SQL.
3927         growing_buffer* sql = buffer_init( 256 );
3928
3929         // If we nested inside another UNION, INTERSECT, or EXCEPT,
3930         // Add a layer of parentheses
3931         if( flags & SUBCOMBO )
3932                 OSRF_BUFFER_ADD( sql, "( " );
3933
3934         // Traverse the query array.  Each entry should be a hash.
3935         int first = 1;   // boolean
3936         int i = 0;
3937         jsonObject* query = NULL;
3938         while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3939                 if( query->type != JSON_HASH ) {
3940                         if( ctx )
3941                                 osrfAppSessionStatus(
3942                                         ctx->session,
3943                                         OSRF_STATUS_INTERNALSERVERERROR,
3944                                         "osrfMethodException",
3945                                         ctx->request,
3946                                         "Malformed query under UNION, INTERSECT or EXCEPT"
3947                                 );
3948                         osrfLogError(
3949                                 OSRF_LOG_MARK,
3950                                 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3951                                 modulename,
3952                                 op,
3953                                 json_type( query->type )
3954                         );
3955                         buffer_free( sql );
3956                         return NULL;
3957                 }
3958
3959                 if( first )
3960                         first = 0;
3961                 else {
3962                         OSRF_BUFFER_ADD( sql, op );
3963                         if( all )
3964                                 OSRF_BUFFER_ADD( sql, "ALL " );
3965                 }
3966
3967                 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3968                 if( ! query_str ) {
3969                         osrfLogError(
3970                                 OSRF_LOG_MARK,
3971                                 "%s: Error building query under%s",
3972                                 modulename,
3973                                 op
3974                         );
3975                         buffer_free( sql );
3976                         return NULL;
3977                 }
3978
3979                 OSRF_BUFFER_ADD( sql, query_str );
3980         }
3981
3982         if( flags & SUBCOMBO )
3983                 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3984
3985         if( !(flags & SUBSELECT) )
3986                 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3987
3988         return buffer_release( sql );
3989 }
3990
3991 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3992 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3993 // or "except" to indicate the type of query.
3994 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3995         // Sanity checks
3996         if( ! query ) {
3997                 if( ctx )
3998                         osrfAppSessionStatus(
3999                                 ctx->session,
4000                                 OSRF_STATUS_INTERNALSERVERERROR,
4001                                 "osrfMethodException",
4002                                 ctx->request,
4003                                 "Malformed query; no query object"
4004                         );
4005                 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
4006                 return NULL;
4007         } else if( query->type != JSON_HASH ) {
4008                 if( ctx )
4009                         osrfAppSessionStatus(
4010                                 ctx->session,
4011                                 OSRF_STATUS_INTERNALSERVERERROR,
4012                                 "osrfMethodException",
4013                                 ctx->request,
4014                                 "Malformed query object"
4015                         );
4016                 osrfLogError(
4017                         OSRF_LOG_MARK,
4018                         "%s: Query object is %s instead of JSON_HASH",
4019                         modulename,
4020                         json_type( query->type )
4021                 );
4022                 return NULL;
4023         }
4024
4025         // Determine what kind of query it purports to be, and dispatch accordingly.
4026         if( jsonObjectGetKeyConst( query, "union" ) ||
4027                 jsonObjectGetKeyConst( query, "intersect" ) ||
4028                 jsonObjectGetKeyConst( query, "except" )) {
4029                 return doCombo( ctx, query, flags );
4030         } else {
4031                 // It is presumably a SELECT query
4032
4033                 // Push a node onto the stack for the current query.  Every level of
4034                 // subquery gets its own QueryFrame on the Stack.
4035                 push_query_frame();
4036
4037                 // Build an SQL SELECT statement
4038                 char* sql = SELECT(
4039                         ctx,
4040                         jsonObjectGetKey( query, "select" ),
4041                         jsonObjectGetKeyConst( query, "from" ),
4042                         jsonObjectGetKeyConst( query, "where" ),
4043                         jsonObjectGetKeyConst( query, "having" ),
4044                         jsonObjectGetKeyConst( query, "order_by" ),
4045                         jsonObjectGetKeyConst( query, "limit" ),
4046                         jsonObjectGetKeyConst( query, "offset" ),
4047                         flags
4048                 );
4049                 pop_query_frame();
4050                 return sql;
4051         }
4052 }
4053
4054 char* SELECT (
4055                 /* method context */ osrfMethodContext* ctx,
4056
4057                 /* SELECT   */ jsonObject* selhash,
4058                 /* FROM     */ const jsonObject* join_hash,
4059                 /* WHERE    */ const jsonObject* search_hash,
4060                 /* HAVING   */ const jsonObject* having_hash,
4061                 /* ORDER BY */ const jsonObject* order_hash,
4062                 /* LIMIT    */ const jsonObject* limit,
4063                 /* OFFSET   */ const jsonObject* offset,
4064                 /* flags    */ int flags
4065 ) {
4066         const char* locale = osrf_message_get_last_locale();
4067
4068         // general tmp objects
4069         const jsonObject* tmp_const;
4070         jsonObject* selclass = NULL;
4071         jsonObject* snode = NULL;
4072         jsonObject* onode = NULL;
4073
4074         char* string = NULL;
4075         int from_function = 0;
4076         int first = 1;
4077         int gfirst = 1;
4078         //int hfirst = 1;
4079
4080         osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
4081
4082         // punt if there's no FROM clause
4083         if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
4084                 osrfLogError(
4085                         OSRF_LOG_MARK,
4086                         "%s: FROM clause is missing or empty",
4087                         modulename
4088                 );
4089                 if( ctx )
4090                         osrfAppSessionStatus(
4091                                 ctx->session,
4092                                 OSRF_STATUS_INTERNALSERVERERROR,
4093                                 "osrfMethodException",
4094                                 ctx->request,
4095                                 "FROM clause is missing or empty in JSON query"
4096                         );
4097                 return NULL;
4098         }
4099
4100         // the core search class
4101         const char* core_class = NULL;
4102
4103         // get the core class -- the only key of the top level FROM clause, or a string
4104         if( join_hash->type == JSON_HASH ) {
4105                 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
4106                 snode = jsonIteratorNext( tmp_itr );
4107
4108                 // Populate the current QueryFrame with information
4109                 // about the core class
4110                 if( add_query_core( NULL, tmp_itr->key ) ) {
4111                         if( ctx )
4112                                 osrfAppSessionStatus(
4113                                         ctx->session,
4114                                         OSRF_STATUS_INTERNALSERVERERROR,
4115                                         "osrfMethodException",
4116                                         ctx->request,
4117                                         "Unable to look up core class"
4118                                 );
4119                         return NULL;
4120                 }
4121                 core_class = curr_query->core.class_name;
4122                 join_hash = snode;
4123
4124                 jsonObject* extra = jsonIteratorNext( tmp_itr );
4125
4126                 jsonIteratorFree( tmp_itr );
4127                 snode = NULL;
4128
4129                 // There shouldn't be more than one entry in join_hash
4130                 if( extra ) {
4131                         osrfLogError(
4132                                 OSRF_LOG_MARK,
4133                                 "%s: Malformed FROM clause: extra entry in JSON_HASH",
4134                                 modulename
4135                         );
4136                         if( ctx )
4137                                 osrfAppSessionStatus(
4138                                         ctx->session,
4139                                         OSRF_STATUS_INTERNALSERVERERROR,
4140                                         "osrfMethodException",
4141                                         ctx->request,
4142                                         "Malformed FROM clause in JSON query"
4143                                 );
4144                         return NULL;    // Malformed join_hash; extra entry
4145                 }
4146         } else if( join_hash->type == JSON_ARRAY ) {
4147                 // We're selecting from a function, not from a table
4148                 from_function = 1;
4149                 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
4150                 selhash = NULL;
4151
4152         } else if( join_hash->type == JSON_STRING ) {
4153                 // Populate the current QueryFrame with information
4154                 // about the core class
4155                 core_class = jsonObjectGetString( join_hash );
4156                 join_hash = NULL;
4157                 if( add_query_core( NULL, core_class ) ) {
4158                         if( ctx )
4159                                 osrfAppSessionStatus(
4160                                         ctx->session,
4161                                         OSRF_STATUS_INTERNALSERVERERROR,
4162                                         "osrfMethodException",
4163                                         ctx->request,
4164                                         "Unable to look up core class"
4165                                 );
4166                         return NULL;
4167                 }
4168         }
4169         else {
4170                 osrfLogError(
4171                         OSRF_LOG_MARK,
4172                         "%s: FROM clause is unexpected JSON type: %s",
4173                         modulename,
4174                         json_type( join_hash->type )
4175                 );
4176                 if( ctx )
4177                         osrfAppSessionStatus(
4178                                 ctx->session,
4179                                 OSRF_STATUS_INTERNALSERVERERROR,
4180                                 "osrfMethodException",
4181                                 ctx->request,
4182                                 "Ill-formed FROM clause in JSON query"
4183                         );
4184                 return NULL;
4185         }
4186
4187         // Build the join clause, if any, while filling out the list
4188         // of joined classes in the current QueryFrame.
4189         char* join_clause = NULL;
4190         if( join_hash && ! from_function ) {
4191
4192                 join_clause = searchJOIN( join_hash, &curr_query->core );
4193                 if( ! join_clause ) {
4194                         if( ctx )
4195                                 osrfAppSessionStatus(
4196                                         ctx->session,
4197                                         OSRF_STATUS_INTERNALSERVERERROR,
4198                                         "osrfMethodException",
4199                                         ctx->request,
4200                                         "Unable to construct JOIN clause(s)"
4201                                 );
4202                         return NULL;
4203                 }
4204         }
4205
4206         // For in case we don't get a select list
4207         jsonObject* defaultselhash = NULL;
4208
4209         // if there is no select list, build a default select list ...
4210         if( !selhash && !from_function ) {
4211                 jsonObject* default_list = defaultSelectList( core_class );
4212                 if( ! default_list ) {
4213                         if( ctx ) {
4214                                 osrfAppSessionStatus(
4215                                         ctx->session,
4216                                         OSRF_STATUS_INTERNALSERVERERROR,
4217                                         "osrfMethodException",
4218                                         ctx->request,
4219                                         "Unable to build default SELECT clause in JSON query"
4220                                 );
4221                                 free( join_clause );
4222                                 return NULL;
4223                         }
4224                 }
4225
4226                 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
4227                 jsonObjectSetKey( selhash, core_class, default_list );
4228         }
4229
4230         // The SELECT clause can be encoded only by a hash
4231         if( !from_function && selhash->type != JSON_HASH ) {
4232                 osrfLogError(
4233                         OSRF_LOG_MARK,
4234                         "%s: Expected JSON_HASH for SELECT clause; found %s",
4235                         modulename,
4236                         json_type( selhash->type )
4237                 );
4238
4239                 if( ctx )
4240                         osrfAppSessionStatus(
4241                                 ctx->session,
4242                                 OSRF_STATUS_INTERNALSERVERERROR,
4243                                 "osrfMethodException",
4244                                 ctx->request,
4245                                 "Malformed SELECT clause in JSON query"
4246                         );
4247                 free( join_clause );
4248                 return NULL;
4249         }
4250
4251         // If you see a null or wild card specifier for the core class, or an
4252         // empty array, replace it with a default SELECT list
4253         tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4254         if( tmp_const ) {
4255                 int default_needed = 0;   // boolean
4256                 if( JSON_STRING == tmp_const->type
4257                         && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4258                                 default_needed = 1;
4259                 else if( JSON_NULL == tmp_const->type )
4260                         default_needed = 1;
4261
4262                 if( default_needed ) {
4263                         // Build a default SELECT list
4264                         jsonObject* default_list = defaultSelectList( core_class );
4265                         if( ! default_list ) {
4266                                 if( ctx ) {
4267                                         osrfAppSessionStatus(
4268                                                 ctx->session,
4269                                                 OSRF_STATUS_INTERNALSERVERERROR,
4270                                                 "osrfMethodException",
4271                                                 ctx->request,
4272                                                 "Can't build default SELECT clause in JSON query"
4273                                         );
4274                                         free( join_clause );
4275                                         return NULL;
4276                                 }
4277                         }
4278
4279                         jsonObjectSetKey( selhash, core_class, default_list );
4280                 }
4281         }
4282
4283         // temp buffers for the SELECT list and GROUP BY clause
4284         growing_buffer* select_buf = buffer_init( 128 );
4285         growing_buffer* group_buf  = buffer_init( 128 );
4286
4287         int aggregate_found = 0;     // boolean
4288
4289         // Build a select list
4290         if( from_function )   // From a function we select everything
4291                 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4292         else {
4293
4294                 // Build the SELECT list as SQL
4295             int sel_pos = 1;
4296             first = 1;
4297             gfirst = 1;
4298             jsonIterator* selclass_itr = jsonNewIterator( selhash );
4299             while ( (selclass = jsonIteratorNext( selclass_itr )) ) {    // For each class
4300
4301                         const char* cname = selclass_itr->key;
4302
4303                         // Make sure the target relation is in the FROM clause.
4304
4305                         // At this point join_hash is a step down from the join_hash we
4306                         // received as a parameter.  If the original was a JSON_STRING,
4307                         // then json_hash is now NULL.  If the original was a JSON_HASH,
4308                         // then json_hash is now the first (and only) entry in it,
4309                         // denoting the core class.  We've already excluded the
4310                         // possibility that the original was a JSON_ARRAY, because in
4311                         // that case from_function would be non-NULL, and we wouldn't
4312                         // be here.
4313
4314                         // If the current table alias isn't in scope, bail out
4315                         ClassInfo* class_info = search_alias( cname );
4316                         if( ! class_info ) {
4317                                 osrfLogError(
4318                                         OSRF_LOG_MARK,
4319                                         "%s: SELECT clause references class not in FROM clause: \"%s\"",
4320                                         modulename,
4321                                         cname
4322                                 );
4323                                 if( ctx )
4324                                         osrfAppSessionStatus(
4325                                                 ctx->session,
4326                                                 OSRF_STATUS_INTERNALSERVERERROR,
4327                                                 "osrfMethodException",
4328                                                 ctx->request,
4329                                                 "Selected class not in FROM clause in JSON query"
4330                                         );
4331                                 jsonIteratorFree( selclass_itr );
4332                                 buffer_free( select_buf );
4333                                 buffer_free( group_buf );
4334                                 if( defaultselhash )
4335                                         jsonObjectFree( defaultselhash );
4336                                 free( join_clause );
4337                                 return NULL;
4338                         }
4339
4340                         if( selclass->type != JSON_ARRAY ) {
4341                                 osrfLogError(
4342                                         OSRF_LOG_MARK,
4343                                         "%s: Malformed SELECT list for class \"%s\"; not an array",
4344                                         modulename,
4345                                         cname
4346                                 );
4347                                 if( ctx )
4348                                         osrfAppSessionStatus(
4349                                                 ctx->session,
4350                                                 OSRF_STATUS_INTERNALSERVERERROR,
4351                                                 "osrfMethodException",
4352                                                 ctx->request,
4353                                                 "Selected class not in FROM clause in JSON query"
4354                                         );
4355
4356                                 jsonIteratorFree( selclass_itr );
4357                                 buffer_free( select_buf );
4358                                 buffer_free( group_buf );
4359                                 if( defaultselhash )
4360                                         jsonObjectFree( defaultselhash );
4361                                 free( join_clause );
4362                                 return NULL;
4363                         }
4364
4365                         // Look up some attributes of the current class
4366                         osrfHash* idlClass        = class_info->class_def;
4367                         osrfHash* class_field_set = class_info->fields;
4368                         const char* class_pkey    = osrfHashGet( idlClass, "primarykey" );
4369                         const char* class_tname   = osrfHashGet( idlClass, "tablename" );
4370
4371                         if( 0 == selclass->size ) {
4372                                 osrfLogWarning(
4373                                         OSRF_LOG_MARK,
4374                                         "%s: No columns selected from \"%s\"",
4375                                         modulename,
4376                                         cname
4377                                 );
4378                         }
4379
4380                         // stitch together the column list for the current table alias...
4381                         unsigned long field_idx = 0;
4382                         jsonObject* selfield = NULL;
4383                         while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4384
4385                                 // If we need a separator comma, add one
4386                                 if( first ) {
4387                                         first = 0;
4388                                 } else {
4389                                         OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4390                                 }
4391
4392                                 // if the field specification is a string, add it to the list
4393                                 if( selfield->type == JSON_STRING ) {
4394
4395                                         // Look up the field in the IDL
4396                                         const char* col_name = jsonObjectGetString( selfield );
4397                                         osrfHash* field_def = NULL;
4398
4399                                         if (!osrfStringArrayContains(
4400                                                         osrfHashGet(
4401                                                                 osrfHashGet( class_field_set, col_name ),
4402                                                                 "suppress_controller"),
4403                                                         modulename
4404                                         ))
4405                                                 field_def = osrfHashGet( class_field_set, col_name );
4406
4407                                         if( !field_def ) {
4408                                                 // No such field in current class
4409                                                 osrfLogError(
4410                                                         OSRF_LOG_MARK,
4411                                                         "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4412                                                         modulename,
4413                                                         col_name,
4414                                                         cname
4415                                                 );
4416                                                 if( ctx )
4417                                                         osrfAppSessionStatus(
4418                                                                 ctx->session,
4419                                                                 OSRF_STATUS_INTERNALSERVERERROR,
4420                                                                 "osrfMethodException",
4421                                                                 ctx->request,
4422                                                                 "Selected column not defined in JSON query"
4423                                                         );
4424                                                 jsonIteratorFree( selclass_itr );
4425                                                 buffer_free( select_buf );
4426                                                 buffer_free( group_buf );
4427                                                 if( defaultselhash )
4428                                                         jsonObjectFree( defaultselhash );
4429                                                 free( join_clause );
4430                                                 return NULL;
4431                                         } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4432                                                 // Virtual field not allowed
4433                                                 osrfLogError(
4434                                                         OSRF_LOG_MARK,
4435                                                         "%s: Selected column \"%s\" for class \"%s\" is virtual",
4436                                                         modulename,
4437                                                         col_name,
4438                                                         cname
4439                                                 );
4440                                                 if( ctx )
4441                                                         osrfAppSessionStatus(
4442                                                                 ctx->session,
4443                                                                 OSRF_STATUS_INTERNALSERVERERROR,
4444                                                                 "osrfMethodException",
4445                                                                 ctx->request,
4446                                                                 "Selected column may not be virtual in JSON query"
4447                                                         );
4448                                                 jsonIteratorFree( selclass_itr );
4449                                                 buffer_free( select_buf );
4450                                                 buffer_free( group_buf );
4451                                                 if( defaultselhash )
4452                                                         jsonObjectFree( defaultselhash );
4453                                                 free( join_clause );
4454                                                 return NULL;
4455                                         }
4456
4457                                         if( locale ) {
4458                                                 const char* i18n;
4459                                                 if( flags & DISABLE_I18N )
4460                                                         i18n = NULL;
4461                                                 else
4462                                                         i18n = osrfHashGet( field_def, "i18n" );
4463
4464                                                 if( str_is_true( i18n ) ) {
4465                                                         buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4466                                                                 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4467                                                                 class_tname, cname, col_name, class_pkey,
4468                                                                 cname, class_pkey, locale, col_name );
4469                                                 } else {
4470                                                         buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4471                                                                 cname, col_name, col_name );
4472                                                 }
4473                                         } else {
4474                                                 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4475                                                                 cname, col_name, col_name );
4476                                         }
4477
4478                                 // ... but it could be an object, in which case we check for a Field Transform
4479                                 } else if( selfield->type == JSON_HASH ) {
4480
4481                                         const char* col_name = jsonObjectGetString(
4482                                                         jsonObjectGetKeyConst( selfield, "column" ) );
4483
4484                                         // Get the field definition from the IDL
4485                                         osrfHash* field_def = NULL;
4486                                         if (!osrfStringArrayContains(
4487                                                         osrfHashGet(
4488                                                                 osrfHashGet( class_field_set, col_name ),
4489                                                                 "suppress_controller"),
4490                                                         modulename
4491                                         ))
4492                                                 field_def = osrfHashGet( class_field_set, col_name );
4493
4494
4495                                         if( !field_def ) {
4496                                                 // No such field in current class
4497                                                 osrfLogError(
4498                                                         OSRF_LOG_MARK,
4499                                                         "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4500                                                         modulename,
4501                                                         col_name,
4502                                                         cname
4503                                                 );
4504                                                 if( ctx )
4505                                                         osrfAppSessionStatus(
4506                                                                 ctx->session,
4507                                                                 OSRF_STATUS_INTERNALSERVERERROR,
4508                                                                 "osrfMethodException",
4509                                                                 ctx->request,
4510                                                                 "Selected column is not defined in JSON query"
4511                                                         );
4512                                                 jsonIteratorFree( selclass_itr );
4513                                                 buffer_free( select_buf );
4514                                                 buffer_free( group_buf );
4515                                                 if( defaultselhash )
4516                                                         jsonObjectFree( defaultselhash );
4517                                                 free( join_clause );
4518                                                 return NULL;
4519                                         } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4520                                                 // No such field in current class
4521                                                 osrfLogError(
4522                                                         OSRF_LOG_MARK,
4523                                                         "%s: Selected column \"%s\" is virtual for class \"%s\"",
4524                                                         modulename,
4525                                                         col_name,
4526                                                         cname
4527                                                 );
4528                                                 if( ctx )
4529                                                         osrfAppSessionStatus(
4530                                                                 ctx->session,
4531                                                                 OSRF_STATUS_INTERNALSERVERERROR,
4532                                                                 "osrfMethodException",
4533                                                                 ctx->request,
4534                                                                 "Selected column is virtual in JSON query"
4535                                                         );
4536                                                 jsonIteratorFree( selclass_itr );
4537                                                 buffer_free( select_buf );
4538                                                 buffer_free( group_buf );
4539                                                 if( defaultselhash )
4540                                                         jsonObjectFree( defaultselhash );
4541                                                 free( join_clause );
4542                                                 return NULL;
4543                                         }
4544
4545                                         // Decide what to use as a column alias
4546                                         const char* _alias;
4547                                         if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4548                                                 _alias = jsonObjectGetString( tmp_const );
4549                                         } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4550                                                 _alias = jsonObjectGetString( tmp_const );
4551                                         } else {         // Use field name as the alias
4552                                                 _alias = col_name;
4553                                         }
4554
4555                                         if( jsonObjectGetKeyConst( selfield, "transform" )) {
4556                                                 char* transform_str = searchFieldTransform(
4557                                                         class_info->alias, field_def, selfield );
4558                                                 if( transform_str ) {
4559                                                         buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4560                                                         free( transform_str );
4561                                                 } else {
4562                                                         if( ctx )
4563                                                                 osrfAppSessionStatus(
4564                                                                         ctx->session,
4565                                                                         OSRF_STATUS_INTERNALSERVERERROR,
4566                                                                         "osrfMethodException",
4567                                                                         ctx->request,
4568                                                                         "Unable to generate transform function in JSON query"
4569                                                                 );
4570                                                         jsonIteratorFree( selclass_itr );
4571                                                         buffer_free( select_buf );
4572                                                         buffer_free( group_buf );
4573                                                         if( defaultselhash )
4574                                                                 jsonObjectFree( defaultselhash );
4575                                                         free( join_clause );
4576                                                         return NULL;
4577                                                 }
4578                                         } else {
4579
4580                                                 if( locale ) {
4581                                                         const char* i18n;
4582                                                         if( flags & DISABLE_I18N )
4583                                                                 i18n = NULL;
4584                                                         else
4585                                                                 i18n = osrfHashGet( field_def, "i18n" );
4586
4587                                                         if( str_is_true( i18n ) ) {
4588                                                                 buffer_fadd( select_buf,
4589                                                                         " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4590                                                                         "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4591                                                                         class_tname, cname, col_name, class_pkey, cname,
4592                                                                         class_pkey, locale, _alias );
4593                                                         } else {
4594                                                                 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4595                                                                         cname, col_name, _alias );
4596                                                         }
4597                                                 } else {
4598                                                         buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4599                                                                 cname, col_name, _alias );
4600                                                 }
4601                                         }
4602                                 }
4603                                 else {
4604                                         osrfLogError(
4605                                                 OSRF_LOG_MARK,
4606                                                 "%s: Selected item is unexpected JSON type: %s",
4607                                                 modulename,
4608                                                 json_type( selfield->type )
4609                                         );
4610                                         if( ctx )
4611                                                 osrfAppSessionStatus(
4612                                                         ctx->session,
4613                                                         OSRF_STATUS_INTERNALSERVERERROR,
4614                                                         "osrfMethodException",
4615                                                         ctx->request,
4616                                                         "Ill-formed SELECT item in JSON query"
4617                                                 );
4618                                         jsonIteratorFree( selclass_itr );
4619                                         buffer_free( select_buf );
4620                                         buffer_free( group_buf );
4621                                         if( defaultselhash )
4622                                                 jsonObjectFree( defaultselhash );
4623                                         free( join_clause );
4624                                         return NULL;
4625                                 }
4626
4627                                 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4628                                 if( obj_is_true( agg_obj ) )
4629                                         aggregate_found = 1;
4630                                 else {
4631                                         // Append a comma (except for the first one)
4632                                         // and add the column to a GROUP BY clause
4633                                         if( gfirst )
4634                                                 gfirst = 0;
4635                                         else
4636                                                 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4637
4638                                         buffer_fadd( group_buf, " %d", sel_pos );
4639                                 }
4640
4641 #if 0
4642                             if (is_agg->size || (flags & SELECT_DISTINCT)) {
4643
4644                                         const jsonObject* aggregate_obj = jsonObjectGetKeyConst( selfield, "aggregate");
4645                                     if ( ! obj_is_true( aggregate_obj ) ) {
4646                                             if (gfirst) {
4647                                                     gfirst = 0;
4648                                             } else {
4649                                                         OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4650                                             }
4651
4652                                             buffer_fadd(group_buf, " %d", sel_pos);
4653
4654                                         /*
4655                                     } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4656                                             if (gfirst) {
4657                                                     gfirst = 0;
4658                                             } else {
4659                                                         OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4660                                             }
4661
4662                                             _column = searchFieldTransform(class_info->alias, field, selfield);
4663                                                 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4664                                                 OSRF_BUFFER_ADD(group_buf, _column);
4665                                             _column = searchFieldTransform(class_info->alias, field, selfield);
4666                                         */
4667                                     }
4668                             }
4669 #endif
4670
4671                                 sel_pos++;
4672                         } // end while -- iterating across SELECT columns
4673
4674                 } // end while -- iterating across classes
4675
4676                 jsonIteratorFree( selclass_itr );
4677         }
4678
4679         char* col_list = buffer_release( select_buf );
4680
4681         // Make sure the SELECT list isn't empty.  This can happen, for example,
4682         // if we try to build a default SELECT clause from a non-core table.
4683
4684         if( ! *col_list ) {
4685                 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4686                 if( ctx )
4687                         osrfAppSessionStatus(
4688                                 ctx->session,
4689                                 OSRF_STATUS_INTERNALSERVERERROR,
4690                                 "osrfMethodException",
4691                                 ctx->request,
4692                                 "SELECT list is empty"
4693                 );
4694                 free( col_list );
4695                 buffer_free( group_buf );
4696                 if( defaultselhash )
4697                         jsonObjectFree( defaultselhash );
4698                 free( join_clause );
4699                 return NULL;
4700         }
4701
4702         char* table = NULL;
4703         if( from_function )
4704                 table = searchValueTransform( join_hash );
4705         else
4706                 table = strdup( curr_query->core.source_def );
4707
4708         if( !table ) {
4709                 if( ctx )
4710                         osrfAppSessionStatus(
4711                                 ctx->session,
4712                                 OSRF_STATUS_INTERNALSERVERERROR,
4713                                 "osrfMethodException",
4714                                 ctx->request,
4715                                 "Unable to identify table for core class"
4716                         );
4717                 free( col_list );
4718                 buffer_free( group_buf );
4719                 if( defaultselhash )
4720                         jsonObjectFree( defaultselhash );
4721                 free( join_clause );
4722                 return NULL;
4723         }
4724
4725         // Put it all together
4726         growing_buffer* sql_buf = buffer_init( 128 );
4727         buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4728         free( col_list );
4729         free( table );
4730
4731         // Append the join clause, if any
4732         if( join_clause ) {
4733                 buffer_add(sql_buf, join_clause );
4734                 free( join_clause );
4735         }
4736
4737         char* order_by_list = NULL;
4738         char* having_buf = NULL;
4739
4740         if( !from_function ) {
4741
4742                 // Build a WHERE clause, if there is one
4743                 if( search_hash ) {
4744                         buffer_add( sql_buf, " WHERE " );
4745
4746                         // and it's on the WHERE clause
4747                         char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4748                         if( ! pred ) {
4749                                 if( ctx ) {
4750                                         osrfAppSessionStatus(
4751                                                 ctx->session,
4752                                                 OSRF_STATUS_INTERNALSERVERERROR,
4753                                                 "osrfMethodException",
4754                                                 ctx->request,
4755                                                 "Severe query error in WHERE predicate -- see error log for more details"
4756                                         );
4757                                 }
4758                                 buffer_free( group_buf );
4759                                 buffer_free( sql_buf );
4760                                 if( defaultselhash )
4761                                         jsonObjectFree( defaultselhash );
4762                                 return NULL;
4763                         }
4764
4765                         buffer_add( sql_buf, pred );
4766                         free( pred );
4767                 }
4768
4769                 // Build a HAVING clause, if there is one
4770                 if( having_hash ) {
4771
4772                         // and it's on the the WHERE clause
4773                         having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4774
4775                         if( ! having_buf ) {
4776                                 if( ctx ) {
4777                                                 osrfAppSessionStatus(
4778                                                 ctx->session,
4779                                                 OSRF_STATUS_INTERNALSERVERERROR,
4780                                                 "osrfMethodException",
4781                                                 ctx->request,
4782                                                 "Severe query error in HAVING predicate -- see error log for more details"
4783                                         );
4784                                 }
4785                                 buffer_free( group_buf );
4786                                 buffer_free( sql_buf );
4787                                 if( defaultselhash )
4788                                         jsonObjectFree( defaultselhash );
4789                                 return NULL;
4790                         }
4791                 }
4792
4793                 // Build an ORDER BY clause, if there is one
4794                 if( NULL == order_hash )
4795                         ;  // No ORDER BY? do nothing
4796                 else if( JSON_ARRAY == order_hash->type ) {
4797                         order_by_list = buildOrderByFromArray( ctx, order_hash );
4798                         if( !order_by_list ) {
4799                                 free( having_buf );
4800                                 buffer_free( group_buf );
4801                                 buffer_free( sql_buf );
4802                                 if( defaultselhash )
4803                                         jsonObjectFree( defaultselhash );
4804                                 return NULL;
4805                         }
4806                 } else if( JSON_HASH == order_hash->type ) {
4807                         // This hash is keyed on class alias.  Each class has either
4808                         // an array of field names or a hash keyed on field name.
4809                         growing_buffer* order_buf = NULL;  // to collect ORDER BY list
4810                         jsonIterator* class_itr = jsonNewIterator( order_hash );
4811                         while( (snode = jsonIteratorNext( class_itr )) ) {
4812
4813                                 ClassInfo* order_class_info = search_alias( class_itr->key );
4814                                 if( ! order_class_info ) {
4815                                         osrfLogError( OSRF_LOG_MARK,
4816                                                 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4817                                                 modulename, class_itr->key );
4818                                         if( ctx )
4819                                                 osrfAppSessionStatus(
4820                                                         ctx->session,
4821                                                         OSRF_STATUS_INTERNALSERVERERROR,
4822                                                         "osrfMethodException",
4823                                                         ctx->request,
4824                                                         "Invalid class referenced in ORDER BY clause -- "
4825                                                                 "see error log for more details"
4826                                                 );
4827                                         jsonIteratorFree( class_itr );
4828                                         buffer_free( order_buf );
4829                                         free( having_buf );
4830                                         buffer_free( group_buf );
4831                                         buffer_free( sql_buf );
4832                                         if( defaultselhash )
4833                                                 jsonObjectFree( defaultselhash );
4834                                         return NULL;
4835                                 }
4836
4837                                 osrfHash* field_list_def = order_class_info->fields;
4838
4839                                 if( snode->type == JSON_HASH ) {
4840
4841                                         // Hash is keyed on field names from the current class.  For each field
4842                                         // there is another layer of hash to define the sorting details, if any,
4843                                         // or a string to indicate direction of sorting.
4844                                         jsonIterator* order_itr = jsonNewIterator( snode );
4845                                         while( (onode = jsonIteratorNext( order_itr )) ) {
4846
4847                                                 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4848                                                 if( !field_def ) {
4849                                                         osrfLogError( OSRF_LOG_MARK,
4850                                                                 "%s: Invalid field \"%s\" in ORDER BY clause",
4851                                                                 modulename, order_itr->key );
4852                                                         if( ctx )
4853                                                                 osrfAppSessionStatus(
4854                                                                         ctx->session,
4855                                                                         OSRF_STATUS_INTERNALSERVERERROR,
4856                                                                         "osrfMethodException",
4857                                                                         ctx->request,
4858                                                                         "Invalid field in ORDER BY clause -- "
4859                                                                         "see error log for more details"
4860                                                                 );
4861                                                         jsonIteratorFree( order_itr );
4862                                                         jsonIteratorFree( class_itr );
4863                                                         buffer_free( order_buf );
4864                                                         free( having_buf );
4865                                                         buffer_free( group_buf );
4866                                                         buffer_free( sql_buf );
4867                                                         if( defaultselhash )
4868                                                                 jsonObjectFree( defaultselhash );
4869                                                         return NULL;
4870                                                 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4871                                                         osrfLogError( OSRF_LOG_MARK,
4872                                                                 "%s: Virtual field \"%s\" in ORDER BY clause",
4873                                                                 modulename, order_itr->key );
4874                                                         if( ctx )
4875                                                                 osrfAppSessionStatus(
4876                                                                         ctx->session,
4877                                                                         OSRF_STATUS_INTERNALSERVERERROR,
4878                                                                         "osrfMethodException",
4879                                                                         ctx->request,
4880                                                                         "Virtual field in ORDER BY clause -- "
4881                                                                         "see error log for more details"
4882                                                         );
4883                                                         jsonIteratorFree( order_itr );
4884                                                         jsonIteratorFree( class_itr );
4885                                                         buffer_free( order_buf );
4886                                                         free( having_buf );
4887                                                         buffer_free( group_buf );
4888                                                         buffer_free( sql_buf );
4889                                                         if( defaultselhash )
4890                                                                 jsonObjectFree( defaultselhash );
4891                                                         return NULL;
4892                                                 }
4893
4894                                                 const char* direction = NULL;
4895                                                 if( onode->type == JSON_HASH ) {
4896                                                         if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4897                                                                 string = searchFieldTransform(
4898                                                                         class_itr->key,
4899                                                                         osrfHashGet( field_list_def, order_itr->key ),
4900                                                                         onode
4901                                                                 );
4902                                                                 if( ! string ) {
4903                                                                         if( ctx ) osrfAppSessionStatus(
4904                                                                                 ctx->session,
4905                                                                                 OSRF_STATUS_INTERNALSERVERERROR,
4906                                                                                 "osrfMethodException",
4907                                                                                 ctx->request,
4908                                                                                 "Severe query error in ORDER BY clause -- "
4909                                                                                 "see error log for more details"
4910                                                                         );
4911                                                                         jsonIteratorFree( order_itr );
4912                                                                         jsonIteratorFree( class_itr );
4913                                                                         free( having_buf );
4914                                                                         buffer_free( group_buf );
4915                                                                         buffer_free( order_buf);
4916                                                                         buffer_free( sql_buf );
4917                                                                         if( defaultselhash )
4918                                                                                 jsonObjectFree( defaultselhash );
4919                                                                         return NULL;
4920                                                                 }
4921                                                         } else {
4922                                                                 growing_buffer* field_buf = buffer_init( 16 );
4923                                                                 buffer_fadd( field_buf, "\"%s\".%s",
4924                                                                         class_itr->key, order_itr->key );
4925                                                                 string = buffer_release( field_buf );
4926                                                         }
4927
4928                                                         if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4929                                                                 const char* dir = jsonObjectGetString( tmp_const );
4930                                                                 if(!strncasecmp( dir, "d", 1 )) {
4931                                                                         direction = " DESC";
4932                                                                 } else {
4933                                                                         direction = " ASC";
4934                                                                 }
4935                                                         }
4936
4937                                                 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4938                                                         osrfLogError( OSRF_LOG_MARK,
4939                                                                 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4940                                                                 modulename, json_type( onode->type ) );
4941                                                         if( ctx )
4942                                                                 osrfAppSessionStatus(
4943                                                                         ctx->session,
4944                                                                         OSRF_STATUS_INTERNALSERVERERROR,
4945                                                                         "osrfMethodException",
4946                                                                         ctx->request,
4947                                                                         "Malformed ORDER BY clause -- see error log for more details"
4948                                                                 );
4949                                                         jsonIteratorFree( order_itr );
4950                                                         jsonIteratorFree( class_itr );
4951                                                         free( having_buf );
4952                                                         buffer_free( group_buf );
4953                                                         buffer_free( order_buf );
4954                                                         buffer_free( sql_buf );
4955                                                         if( defaultselhash )
4956                                                                 jsonObjectFree( defaultselhash );
4957                                                         return NULL;
4958
4959                                                 } else {
4960                                                         string = strdup( order_itr->key );
4961                                                         const char* dir = jsonObjectGetString( onode );
4962                                                         if( !strncasecmp( dir, "d", 1 )) {
4963                                                                 direction = " DESC";
4964                                                         } else {
4965                                                                 direction = " ASC";
4966                                                         }
4967                                                 }
4968
4969                                                 if( order_buf )
4970                                                         OSRF_BUFFER_ADD( order_buf, ", " );
4971                                                 else
4972                                                         order_buf = buffer_init( 128 );
4973
4974                                                 OSRF_BUFFER_ADD( order_buf, string );
4975                                                 free( string );
4976
4977                                                 if( direction ) {
4978                                                          OSRF_BUFFER_ADD( order_buf, direction );
4979                                                 }
4980
4981                                         } // end while
4982                                         jsonIteratorFree( order_itr );
4983
4984                                 } else if( snode->type == JSON_ARRAY ) {
4985
4986                                         // Array is a list of fields from the current class
4987                                         unsigned long order_idx = 0;
4988                                         while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4989
4990                                                 const char* _f = jsonObjectGetString( onode );
4991
4992                                                 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4993                                                 if( !field_def ) {
4994                                                         osrfLogError( OSRF_LOG_MARK,
4995                                                                         "%s: Invalid field \"%s\" in ORDER BY clause",
4996                                                                         modulename, _f );
4997                                                         if( ctx )
4998                                                                 osrfAppSessionStatus(
4999                                                                         ctx->session,
5000                                                                         OSRF_STATUS_INTERNALSERVERERROR,
5001                                                                         "osrfMethodException",
5002                                                                         ctx->request,
5003                                                                         "Invalid field in ORDER BY clause -- "
5004                                                                         "see error log for more details"
5005                                                                 );
5006                                                         jsonIteratorFree( class_itr );
5007                                                         buffer_free( order_buf );
5008                                                         free( having_buf );
5009                                                         buffer_free( group_buf );
5010                                                         buffer_free( sql_buf );
5011                                                         if( defaultselhash )
5012                                                                 jsonObjectFree( defaultselhash );
5013                                                         return NULL;
5014                                                 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5015                                                         osrfLogError( OSRF_LOG_MARK,
5016                                                                 "%s: Virtual field \"%s\" in ORDER BY clause",
5017                                                                 modulename, _f );
5018                                                         if( ctx )
5019                                                                 osrfAppSessionStatus(
5020                                                                         ctx->session,
5021                                                                         OSRF_STATUS_INTERNALSERVERERROR,
5022                                                                         "osrfMethodException",
5023                                                                         ctx->request,
5024                                                                         "Virtual field in ORDER BY clause -- "
5025                                                                         "see error log for more details"
5026                                                                 );
5027                                                         jsonIteratorFree( class_itr );
5028                                                         buffer_free( order_buf );
5029                                                         free( having_buf );
5030                                                         buffer_free( group_buf );
5031                                                         buffer_free( sql_buf );
5032                                                         if( defaultselhash )
5033                                                                 jsonObjectFree( defaultselhash );
5034                                                         return NULL;
5035                                                 }
5036
5037                                                 if( order_buf )
5038                                                         OSRF_BUFFER_ADD( order_buf, ", " );
5039                                                 else
5040                                                         order_buf = buffer_init( 128 );
5041
5042                                                 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
5043
5044                                         } // end while
5045
5046                                 // IT'S THE OOOOOOOOOOOLD STYLE!
5047                                 } else {
5048                                         osrfLogError( OSRF_LOG_MARK,
5049                                                 "%s: Possible SQL injection attempt; direct order by is not allowed",
5050                                                 modulename );
5051                                         if(ctx) {
5052                                                 osrfAppSessionStatus(
5053                                                         ctx->session,
5054                                                         OSRF_STATUS_INTERNALSERVERERROR,
5055                                                         "osrfMethodException",
5056                                                         ctx->request,
5057                                                         "Severe query error -- see error log for more details"
5058                                                 );
5059                                         }
5060
5061                                         free( having_buf );
5062                                         buffer_free( group_buf );
5063                                         buffer_free( order_buf );
5064                                         buffer_free( sql_buf );
5065                                         if( defaultselhash )
5066                                                 jsonObjectFree( defaultselhash );
5067                                         jsonIteratorFree( class_itr );
5068                                         return NULL;
5069                                 }
5070                         } // end while
5071                         jsonIteratorFree( class_itr );
5072                         if( order_buf )
5073                                 order_by_list = buffer_release( order_buf );
5074                 } else {
5075                         osrfLogError( OSRF_LOG_MARK,
5076                                 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
5077                                 modulename, json_type( order_hash->type ) );
5078                         if( ctx )
5079                                 osrfAppSessionStatus(
5080                                         ctx->session,
5081                                         OSRF_STATUS_INTERNALSERVERERROR,
5082                                         "osrfMethodException",
5083                                         ctx->request,
5084                                         "Malformed ORDER BY clause -- see error log for more details"
5085                                 );
5086                         free( having_buf );
5087                         buffer_free( group_buf );
5088                         buffer_free( sql_buf );
5089                         if( defaultselhash )
5090                                 jsonObjectFree( defaultselhash );
5091                         return NULL;
5092                 }
5093         }
5094
5095         string = buffer_release( group_buf );
5096
5097         if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
5098                 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
5099                 OSRF_BUFFER_ADD( sql_buf, string );
5100         }
5101
5102         free( string );
5103
5104         if( having_buf && *having_buf ) {
5105                 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
5106                 OSRF_BUFFER_ADD( sql_buf, having_buf );
5107                 free( having_buf );
5108         }
5109
5110         if( order_by_list ) {
5111
5112                 if( *order_by_list ) {
5113                         OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5114                         OSRF_BUFFER_ADD( sql_buf, order_by_list );
5115                 }
5116
5117                 free( order_by_list );
5118         }
5119
5120         if( limit ){
5121                 const char* str = jsonObjectGetString( limit );
5122                 if (str) { // limit could be JSON_NULL, etc.
5123                         buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
5124                 }
5125         }
5126
5127         if( offset ) {
5128                 const char* str = jsonObjectGetString( offset );
5129                 if (str) {
5130                         buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
5131                 }
5132         }
5133
5134         if( !(flags & SUBSELECT) )
5135                 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5136
5137         if( defaultselhash )
5138                  jsonObjectFree( defaultselhash );
5139
5140         return buffer_release( sql_buf );
5141
5142 } // end of SELECT()
5143
5144 /**
5145         @brief Build a list of ORDER BY expressions.
5146         @param ctx Pointer to the method context.
5147         @param order_array Pointer to a JSON_ARRAY of field specifications.
5148         @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
5149         Each expression may be either a column reference or a function call whose first parameter
5150         is a column reference.
5151
5152         Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
5153         It may optionally include entries for "direction" and/or "transform".
5154
5155         The calling code is responsible for freeing the returned string.
5156 */
5157 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
5158         if( ! order_array ) {
5159                 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
5160                         modulename );
5161                 if( ctx )
5162                         osrfAppSessionStatus(
5163                                 ctx->session,
5164                                 OSRF_STATUS_INTERNALSERVERERROR,
5165                                 "osrfMethodException",
5166                                 ctx->request,
5167                                 "Logic error: ORDER BY clause expected, not found; "
5168                                         "see error log for more details"
5169                         );
5170                 return NULL;
5171         } else if( order_array->type != JSON_ARRAY ) {
5172                 osrfLogError( OSRF_LOG_MARK,
5173                         "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
5174                 if( ctx )
5175                         osrfAppSessionStatus(
5176                         ctx->session,
5177                         OSRF_STATUS_INTERNALSERVERERROR,
5178                         "osrfMethodException",
5179                         ctx->request,
5180                         "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
5181                 return NULL;
5182         }
5183
5184         growing_buffer* order_buf = buffer_init( 128 );
5185         int first = 1;        // boolean
5186         int order_idx = 0;
5187         jsonObject* order_spec;
5188         while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
5189
5190                 if( JSON_HASH != order_spec->type ) {
5191                         osrfLogError( OSRF_LOG_MARK,
5192                                 "%s: Malformed field specification in ORDER BY clause; "
5193                                 "expected JSON_HASH, found %s",
5194                                 modulename, json_type( order_spec->type ) );
5195                         if( ctx )
5196                                 osrfAppSessionStatus(
5197                                          ctx->session,
5198                                         OSRF_STATUS_INTERNALSERVERERROR,
5199                                         "osrfMethodException",
5200                                         ctx->request,
5201                                         "Malformed ORDER BY clause -- see error log for more details"
5202                                 );
5203                         buffer_free( order_buf );
5204                         return NULL;
5205                 }
5206
5207                 const char* class_alias =
5208                         jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
5209                 const char* field =
5210                         jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
5211
5212                 jsonObject* compare_to = jsonObjectGetKey( order_spec, "compare" );
5213
5214                 if( !field || !class_alias ) {
5215                         osrfLogError( OSRF_LOG_MARK,
5216                                 "%s: Missing class or field name in field specification of ORDER BY clause",
5217                                 modulename );
5218                         if( ctx )
5219                                 osrfAppSessionStatus(
5220                                         ctx->session,
5221                                         OSRF_STATUS_INTERNALSERVERERROR,
5222                                         "osrfMethodException",
5223                                         ctx->request,
5224                                         "Malformed ORDER BY clause -- see error log for more details"
5225                                 );
5226                         buffer_free( order_buf );
5227                         return NULL;
5228                 }
5229
5230                 const ClassInfo* order_class_info = search_alias( class_alias );
5231                 if( ! order_class_info ) {
5232                         osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
5233                                 "not in FROM clause, skipping it", modulename, class_alias );
5234                         continue;
5235                 }
5236
5237                 // Add a separating comma, except at the beginning
5238                 if( first )
5239                         first = 0;
5240                 else
5241                         OSRF_BUFFER_ADD( order_buf, ", " );
5242
5243                 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
5244                 if( !field_def ) {
5245                         osrfLogError( OSRF_LOG_MARK,
5246                                 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
5247                                 modulename, class_alias, field );
5248                         if( ctx )
5249                                 osrfAppSessionStatus(
5250                                         ctx->session,
5251                                         OSRF_STATUS_INTERNALSERVERERROR,
5252                                         "osrfMethodException",
5253                                         ctx->request,
5254                                         "Invalid field referenced in ORDER BY clause -- "
5255                                         "see error log for more details"
5256                                 );
5257                         free( order_buf );
5258                         return NULL;
5259                 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5260                         osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5261                                 modulename, field );
5262                         if( ctx )
5263                                 osrfAppSessionStatus(
5264                                         ctx->session,
5265                                         OSRF_STATUS_INTERNALSERVERERROR,
5266                                         "osrfMethodException",
5267                                         ctx->request,
5268                                         "Virtual field in ORDER BY clause -- see error log for more details"
5269                                 );
5270                         buffer_free( order_buf );
5271                         return NULL;
5272                 }
5273
5274                 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5275                         char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5276                         if( ! transform_str ) {
5277                                 if( ctx )
5278                                         osrfAppSessionStatus(
5279                                                 ctx->session,
5280                                                 OSRF_STATUS_INTERNALSERVERERROR,
5281                                                 "osrfMethodException",
5282                                                 ctx->request,
5283                                                 "Severe query error in ORDER BY clause -- "
5284                                                 "see error log for more details"
5285                                         );
5286                                 buffer_free( order_buf );
5287                                 return NULL;
5288                         }
5289
5290                         OSRF_BUFFER_ADD( order_buf, transform_str );
5291                         free( transform_str );
5292                 } else if( compare_to ) {
5293                         char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
5294                         if( ! compare_str ) {
5295                                 if( ctx )
5296                                         osrfAppSessionStatus(
5297                                                 ctx->session,
5298                                                 OSRF_STATUS_INTERNALSERVERERROR,
5299                                                 "osrfMethodException",
5300                                                 ctx->request,
5301                                                 "Severe query error in ORDER BY clause -- "
5302                                                 "see error log for more details"
5303                                         );
5304                                 buffer_free( order_buf );
5305                                 return NULL;
5306                         }
5307
5308                         buffer_fadd( order_buf, "(%s)", compare_str );
5309                         free( compare_str );
5310                 }
5311                 else
5312                         buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5313
5314                 const char* direction =
5315                         jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5316                 if( direction ) {
5317                         if( direction[ 0 ] && ( 'd' == direction[ 0 ] || 'D' == direction[ 0 ] ) )
5318                                 OSRF_BUFFER_ADD( order_buf, " DESC" );
5319                         else
5320                                 OSRF_BUFFER_ADD( order_buf, " ASC" );
5321                 }
5322         }
5323
5324         return buffer_release( order_buf );
5325 }
5326
5327 /**
5328         @brief Build a SELECT statement.
5329         @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5330         @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5331         @param meta Pointer to the class metadata for the core class.
5332         @param ctx Pointer to the method context.
5333         @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5334
5335         Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5336         "order_by", "limit", and "offset".
5337
5338         The SELECT statements built here are distinct from those built for the json_query method.
5339 */
5340 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5341         osrfHash* meta, osrfMethodContext* ctx ) {
5342
5343         const char* locale = osrf_message_get_last_locale();
5344
5345         osrfHash* fields = osrfHashGet( meta, "fields" );
5346         const char* core_class = osrfHashGet( meta, "classname" );
5347
5348         const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5349
5350         jsonObject* selhash = NULL;
5351         jsonObject* defaultselhash = NULL;
5352
5353         growing_buffer* sql_buf = buffer_init( 128 );
5354         growing_buffer* select_buf = buffer_init( 128 );
5355
5356         if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5357                 defaultselhash = jsonNewObjectType( JSON_HASH );
5358                 selhash = defaultselhash;
5359         }
5360
5361         // If there's no SELECT list for the core class, build one
5362         if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5363                 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5364
5365                 // Add every non-virtual field to the field list
5366                 osrfHash* field_def = NULL;
5367                 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5368                 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5369                         if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5370                                 const char* field = osrfHashIteratorKey( field_itr );
5371                                 jsonObjectPush( field_list, jsonNewObject( field ) );
5372                         }
5373                 }
5374                 osrfHashIteratorFree( field_itr );
5375                 jsonObjectSetKey( selhash, core_class, field_list );
5376         }
5377
5378         // Build a list of columns for the SELECT clause
5379         int first = 1;
5380         const jsonObject* snode = NULL;
5381         jsonIterator* class_itr = jsonNewIterator( selhash );
5382         while( (snode = jsonIteratorNext( class_itr )) ) {        // For each class
5383
5384                 // If the class isn't in the IDL, ignore it
5385                 const char* cname = class_itr->key;
5386                 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5387                 if( !idlClass )
5388                         continue;
5389
5390                 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5391                 if( strcmp( core_class, class_itr->key )) {
5392                         if( !join_hash )
5393                                 continue;
5394
5395                         jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5396                         if( !found->size ) {
5397                                 jsonObjectFree( found );
5398                                 continue;
5399                         }
5400
5401                         jsonObjectFree( found );
5402                 }
5403
5404                 const jsonObject* node = NULL;
5405                 jsonIterator* select_itr = jsonNewIterator( snode );
5406                 while( (node = jsonIteratorNext( select_itr )) ) {
5407                         const char* item_str = jsonObjectGetString( node );
5408                         osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5409                         char* fname = osrfHashGet( field, "name" );
5410
5411                         if( !field )
5412                                 continue;
5413
5414                         if (osrfStringArrayContains( osrfHashGet(field, "suppress_controller"), modulename ))
5415                                 continue;
5416
5417                         if( first ) {
5418                                 first = 0;
5419                         } else {
5420                                 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5421                         }
5422
5423                         if( locale ) {
5424                                 const char* i18n;
5425                                 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5426                                 if( obj_is_true( no_i18n_obj ) )    // Suppress internationalization?
5427                                         i18n = NULL;
5428                                 else
5429                                         i18n = osrfHashGet( field, "i18n" );
5430
5431                                 if( str_is_true( i18n ) ) {
5432                                         char* pkey = osrfHashGet( idlClass, "primarykey" );
5433                                         char* tname = osrfHashGet( idlClass, "tablename" );
5434
5435                                         buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5436                                                         "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5437                                                         tname, cname, fname, pkey, cname, pkey, locale, fname );
5438                                 } else {
5439                                         buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5440                                 }
5441                         } else {
5442                                 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5443                         }
5444                 }
5445
5446                 jsonIteratorFree( select_itr );
5447         }
5448
5449         jsonIteratorFree( class_itr );
5450
5451         char* col_list = buffer_release( select_buf );
5452         char* table = oilsGetRelation( meta );
5453         if( !table )
5454                 table = strdup( "(null)" );
5455
5456         buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5457         free( col_list );
5458         free( table );
5459
5460         // Clear the query stack (as a fail-safe precaution against possible
5461         // leftover garbage); then push the first query frame onto the stack.
5462         clear_query_stack();
5463         push_query_frame();
5464         if( add_query_core( NULL, core_class ) ) {
5465                 if( ctx )
5466                         osrfAppSessionStatus(
5467                                 ctx->session,
5468                                 OSRF_STATUS_INTERNALSERVERERROR,
5469                                 "osrfMethodException",
5470                                 ctx->request,
5471                                 "Unable to build query frame for core class"
5472                         );
5473                 buffer_free( sql_buf );
5474                 if( defaultselhash )
5475                         jsonObjectFree( defaultselhash );
5476                 return NULL;
5477         }
5478
5479         // Add the JOIN clauses, if any
5480         if( join_hash ) {
5481                 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5482                 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5483                 OSRF_BUFFER_ADD( sql_buf, join_clause );
5484                 free( join_clause );
5485         }
5486
5487         osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL =  %s",
5488                 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5489
5490         OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5491
5492         // Add the conditions in the WHERE clause
5493         char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5494         if( !pred ) {
5495                 osrfAppSessionStatus(
5496                         ctx->session,
5497                         OSRF_STATUS_INTERNALSERVERERROR,
5498                                 "osrfMethodException",
5499                                 ctx->request,
5500                                 "Severe query error -- see error log for more details"
5501                         );
5502                 buffer_free( sql_buf );
5503                 if( defaultselhash )
5504                         jsonObjectFree( defaultselhash );
5505                 clear_query_stack();
5506                 return NULL;
5507         } else {
5508                 buffer_add( sql_buf, pred );
5509                 free( pred );
5510         }
5511
5512         // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5513         if( rest_of_query ) {
5514                 const jsonObject* order_by = NULL;
5515                 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5516
5517                         char* order_by_list = NULL;
5518
5519                         if( JSON_ARRAY == order_by->type ) {
5520                                 order_by_list = buildOrderByFromArray( ctx, order_by );
5521                                 if( !order_by_list ) {
5522                                         buffer_free( sql_buf );
5523                                         if( defaultselhash )
5524                                                 jsonObjectFree( defaultselhash );
5525                                         clear_query_stack();
5526                                         return NULL;
5527                                 }
5528                         } else if( JSON_HASH == order_by->type ) {
5529                                 // We expect order_by to be a JSON_HASH keyed on class names.  Traverse it
5530                                 // and build a list of ORDER BY expressions.
5531                                 growing_buffer* order_buf = buffer_init( 128 );
5532                                 first = 1;
5533                                 jsonIterator* class_itr = jsonNewIterator( order_by );
5534                                 while( (snode = jsonIteratorNext( class_itr )) ) {  // For each class:
5535
5536                                         ClassInfo* order_class_info = search_alias( class_itr->key );
5537                                         if( ! order_class_info )
5538                                                 continue;    // class not referenced by FROM clause?  Ignore it.
5539
5540                                         if( JSON_HASH == snode->type ) {
5541
5542                                                 // If the data for the current class is a JSON_HASH, then it is
5543                                                 // keyed on field name.
5544
5545                                                 const jsonObject* onode = NULL;
5546                                                 jsonIterator* order_itr = jsonNewIterator( snode );
5547                                                 while( (onode = jsonIteratorNext( order_itr )) ) {  // For each field
5548
5549                                                         osrfHash* field_def = osrfHashGet(
5550                                                                 order_class_info->fields, order_itr->key );
5551                                                         if( !field_def )
5552                                                                 continue;    // Field not defined in IDL?  Ignore it.
5553                                                         if( str_is_true( osrfHashGet( field_def, "virtual")))
5554                                                                 continue;    // Field is virtual?  Ignore it.
5555
5556                                                         char* field_str = NULL;
5557                                                         char* direction = NULL;
5558                                                         if( onode->type == JSON_HASH ) {
5559                                                                 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5560                                                                         field_str = searchFieldTransform(
5561                                                                                 class_itr->key, field_def, onode );
5562                                                                         if( ! field_str ) {
5563                                                                                 osrfAppSessionStatus(
5564                                                                                         ctx->session,
5565                                                                                         OSRF_STATUS_INTERNALSERVERERROR,
5566                                                                                         "osrfMethodException",
5567                                                                                         ctx->request,
5568                                                                                         "Severe query error in ORDER BY clause -- "
5569                                                                                         "see error log for more details"
5570                                                                                 );
5571                                                                                 jsonIteratorFree( order_itr );
5572                                                                                 jsonIteratorFree( class_itr );
5573                                                                                 buffer_free( order_buf );
5574                                                                                 buffer_free( sql_buf );
5575                                                                                 if( defaultselhash )
5576                                                                                         jsonObjectFree( defaultselhash );
5577                                                                                 clear_query_stack();
5578                                                                                 return NULL;
5579                                                                         }
5580                                                                 } else {
5581                                                                         growing_buffer* field_buf = buffer_init( 16 );
5582                                                                         buffer_fadd( field_buf, "\"%s\".%s",
5583                                                                                 class_itr->key, order_itr->key );
5584                                                                         field_str = buffer_release( field_buf );
5585                                                                 }
5586
5587                                                                 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5588                                                                         const char* dir = jsonObjectGetString( order_by );
5589                                                                         if(!strncasecmp( dir, "d", 1 )) {
5590                                                                                 direction = " DESC";
5591                                                                         }
5592                                                                 }
5593                                                         } else {
5594                                                                 field_str = strdup( order_itr->key );
5595                                                                 const char* dir = jsonObjectGetString( onode );
5596                                                                 if( !strncasecmp( dir, "d", 1 )) {
5597                                                                         direction = " DESC";
5598                                                                 } else {
5599                                                                         direction = " ASC";
5600                                                                 }
5601                                                         }
5602
5603                                                         if( first ) {
5604                                                                 first = 0;
5605                                                         } else {
5606                                                                 buffer_add( order_buf, ", " );
5607                                                         }
5608
5609                                                         buffer_add( order_buf, field_str );
5610                                                         free( field_str );
5611
5612                                                         if( direction ) {
5613                                                                 buffer_add( order_buf, direction );
5614                                                         }
5615                                                 } // end while; looping over ORDER BY expressions
5616
5617                                                 jsonIteratorFree( order_itr );
5618
5619                                         } else if( JSON_STRING == snode->type ) {
5620                                                 // We expect a comma-separated list of sort fields.
5621                                                 const char* str = jsonObjectGetString( snode );
5622                                                 if( strchr( str, ';' )) {
5623                                                         // No semicolons allowed.  It is theoretically possible for a
5624                                                         // legitimate semicolon to occur within quotes, but it's not likely
5625                                                         // to occur in practice in the context of an ORDER BY list.
5626                                                         osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5627                                                                 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5628                                                         if( ctx ) {
5629                                                                 osrfAppSessionStatus(
5630                                                                         ctx->session,
5631                                                                         OSRF_STATUS_INTERNALSERVERERROR,
5632                                                                         "osrfMethodException",
5633                                                                         ctx->request,
5634                                                                         "Possible attempt at SOL injection -- "
5635                                                                                 "semicolon found in ORDER BY list"
5636                                                                 );
5637                                                         }
5638                                                         jsonIteratorFree( class_itr );
5639                                                         buffer_free( order_buf );
5640                                                         buffer_free( sql_buf );
5641                                                         if( defaultselhash )
5642                                                                 jsonObjectFree( defaultselhash );
5643                                                         clear_query_stack();
5644                                                         return NULL;
5645                                                 }
5646                                                 buffer_add( order_buf, str );
5647                                                 break;
5648                                         }
5649
5650                                 } // end while; looping over order_by classes
5651
5652                                 jsonIteratorFree( class_itr );
5653                                 order_by_list = buffer_release( order_buf );
5654
5655                         } else {
5656                                 osrfLogWarning( OSRF_LOG_MARK,
5657                                         "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5658                                         "no ORDER BY generated" );
5659                         }
5660
5661                         if( order_by_list && *order_by_list ) {
5662                                 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5663                                 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5664                         }
5665
5666                         free( order_by_list );
5667                 }
5668
5669                 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5670                 if( limit ) {
5671                         const char* str = jsonObjectGetString( limit );
5672                         if (str) {
5673                                 buffer_fadd(
5674                                         sql_buf,
5675                                         " LIMIT %d",
5676                                         atoi(str)
5677                                 );
5678                         }
5679                 }
5680
5681                 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5682                 if( offset ) {
5683                         const char* str = jsonObjectGetString( offset );
5684                         if (str) {
5685                                 buffer_fadd(
5686                                         sql_buf,
5687                                         " OFFSET %d",
5688                                         atoi( str )
5689                                 );
5690                         }
5691                 }
5692         }
5693
5694         if( defaultselhash )
5695                 jsonObjectFree( defaultselhash );
5696         clear_query_stack();
5697
5698         OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5699         return buffer_release( sql_buf );
5700 }
5701
5702 int doJSONSearch ( osrfMethodContext* ctx ) {
5703         if(osrfMethodVerifyContext( ctx )) {
5704                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
5705                 return -1;
5706         }
5707
5708         osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5709
5710         int err = 0;
5711
5712         jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5713
5714         int flags = 0;
5715
5716         if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5717                 flags |= SELECT_DISTINCT;
5718
5719         if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5720                 flags |= DISABLE_I18N;
5721
5722         osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5723         clear_query_stack();       // a possibly needless precaution
5724         char* sql = buildQuery( ctx, hash, flags );
5725         clear_query_stack();
5726
5727         if( !sql ) {
5728                 err = -1;
5729                 return err;
5730         }
5731
5732         osrfLogDebug( OSRF_LOG_MARK, "%s SQL =  %s", modulename, sql );
5733
5734         // XXX for now...
5735         dbhandle = writehandle;
5736
5737         dbi_result result = dbi_conn_query( dbhandle, sql );
5738
5739         if( result ) {
5740                 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5741
5742                 if( dbi_result_first_row( result )) {
5743                         /* JSONify the result */
5744                         osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5745
5746                         do {
5747                                 jsonObject* return_val = oilsMakeJSONFromResult( result );
5748                                 osrfAppRespond( ctx, return_val );
5749                                 jsonObjectFree( return_val );
5750                         } while( dbi_result_next_row( result ));
5751
5752                 } else {
5753                         osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5754                 }
5755
5756                 osrfAppRespondComplete( ctx, NULL );
5757
5758                 /* clean up the query */
5759                 dbi_result_free( result );
5760
5761         } else {
5762                 err = -1;
5763                 const char* msg;
5764                 int errnum = dbi_conn_error( dbhandle, &msg );
5765                 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5766                         modulename, sql, errnum, msg ? msg : "(No description available)" );
5767                 osrfAppSessionStatus(
5768                         ctx->session,
5769                         OSRF_STATUS_INTERNALSERVERERROR,
5770                         "osrfMethodException",
5771                         ctx->request,
5772                         "Severe query error -- see error log for more details"
5773                 );
5774                 if( !oilsIsDBConnected( dbhandle ))
5775                         osrfAppSessionPanic( ctx->session );
5776         }
5777
5778         free( sql );
5779         return err;
5780 }
5781
5782 // The last parameter, err, is used to report an error condition by updating an int owned by
5783 // the calling code.
5784
5785 // In case of an error, we set *err to -1.  If there is no error, *err is left unchanged.
5786 // It is the responsibility of the calling code to initialize *err before the
5787 // call, so that it will be able to make sense of the result.
5788
5789 // Note also that we return NULL if and only if we set *err to -1.  So the err parameter is
5790 // redundant anyway.
5791 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5792                 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5793
5794         // XXX for now...
5795         dbhandle = writehandle;
5796
5797         char* core_class = osrfHashGet( class_meta, "classname" );
5798         osrfLogDebug( OSRF_LOG_MARK, "entering doFieldmapperSearch() with core_class %s", core_class );
5799
5800         char* pkey = osrfHashGet( class_meta, "primarykey" );
5801
5802         if (!ctx->session->userData)
5803                 (void) initSessionCache( ctx );
5804
5805         char *methodtype = osrfHashGet( (osrfHash *) ctx->method->userData, "methodtype" );
5806         char *inside_verify = osrfHashGet( (osrfHash*) ctx->session->userData, "inside_verify" );
5807         int need_to_verify = (inside_verify ? !atoi(inside_verify) : 1);
5808
5809         int i_respond_directly = 0;
5810         int flesh_depth = 0;
5811
5812         char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5813         if( !sql ) {
5814                 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5815                 *err = -1;
5816                 return NULL;
5817         }
5818
5819         osrfLogDebug( OSRF_LOG_MARK, "%s SQL =  %s", modulename, sql );
5820
5821         dbi_result result = dbi_conn_query( dbhandle, sql );
5822         if( NULL == result ) {
5823                 const char* msg;
5824                 int errnum = dbi_conn_error( dbhandle, &msg );
5825                 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5826                         modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5827                         msg ? msg : "(No description available)" );
5828                 if( !oilsIsDBConnected( dbhandle ))
5829                         osrfAppSessionPanic( ctx->session );
5830                 osrfAppSessionStatus(
5831                         ctx->session,
5832                         OSRF_STATUS_INTERNALSERVERERROR,
5833                         "osrfMethodException",
5834                         ctx->request,
5835                         "Severe query error -- see error log for more details"
5836                 );
5837                 *err = -1;
5838                 free( sql );
5839                 return NULL;
5840
5841         } else {
5842                 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5843         }
5844
5845         jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5846         jsonObject* row_obj = NULL;
5847
5848         // The following two steps are for verifyObjectPCRUD()'s benefit.
5849         // 1. get the flesh depth
5850         const jsonObject* _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5851         if( _tmp ) {
5852                 flesh_depth = (int) jsonObjectGetNumber( _tmp );
5853                 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5854                         flesh_depth = max_flesh_depth;
5855         }
5856
5857         // 2. figure out one consistent rs_size for verifyObjectPCRUD to use
5858         // over the whole life of this request.  This means if we've already set
5859         // up a rs_size_req_%d, do nothing.
5860         //      a. Incidentally, we can also use this opportunity to set i_respond_directly
5861         int *rs_size = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
5862         if( !rs_size ) {        // pointer null, so value not set in hash
5863                 // i_respond_directly can only be true at the /top/ of a recursive search, if even that.
5864                 i_respond_directly = ( *methodtype == 'r' || *methodtype == 'i' || *methodtype == 's' );
5865
5866                 rs_size = (int *) safe_malloc( sizeof(int) );   // will be freed by sessionDataFree()
5867                 unsigned long long result_count = dbi_result_get_numrows( result );
5868                 *rs_size = (int) result_count * (flesh_depth + 1);      // yes, we could lose some bits, but come on
5869                 osrfHashSet( (osrfHash *) ctx->session->userData, rs_size, "rs_size_req_%d", ctx->request );
5870         }
5871
5872         if( dbi_result_first_row( result )) {
5873
5874                 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5875                 // in a JSON_ARRAY of rows.  If two or more rows have the same key value, then
5876                 // eliminate the duplicates.
5877                 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5878                 osrfHash* dedup = osrfNewHash();
5879                 do {
5880                         row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5881                         char* pkey_val = oilsFMGetString( row_obj, pkey );
5882                         if( osrfHashGet( dedup, pkey_val ) ) {
5883                                 jsonObjectFree( row_obj );
5884                                 free( pkey_val );
5885                         } else {
5886                                 if( !enforce_pcrud || !need_to_verify ||
5887                                                 verifyObjectPCRUD( ctx, class_meta, row_obj, 0 /* means check user data for rs_size */ )) {
5888                                         osrfHashSet( dedup, pkey_val, pkey_val );
5889                                         jsonObjectPush( res_list, row_obj );
5890                                 }
5891                         }
5892                 } while( dbi_result_next_row( result ));
5893                 osrfHashFree( dedup );
5894
5895         } else {
5896                 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5897                         modulename, sql );
5898         }
5899
5900         /* clean up the query */
5901         dbi_result_free( result );
5902         free( sql );
5903
5904         // If we're asked to flesh, and there's anything to flesh, then flesh it
5905         // (formerly we would skip fleshing if in pcrud mode, but now we support
5906         // fleshing even in PCRUD).
5907         if( res_list->size ) {
5908                 jsonObject* temp_blob;  // We need a non-zero flesh depth, and a list of fields to flesh
5909                 jsonObject* flesh_fields; 
5910                 jsonObject* flesh_blob = NULL;
5911                 osrfStringArray* link_fields = NULL;
5912                 osrfHash* links = NULL;
5913                 int want_flesh = 0;
5914
5915                 if( query_hash ) {
5916                         temp_blob = jsonObjectGetKey( query_hash, "flesh_fields" );
5917                         if( temp_blob && flesh_depth > 0 ) {
5918
5919                                 flesh_blob = jsonObjectClone( temp_blob );
5920                                 flesh_fields = jsonObjectGetKey( flesh_blob, core_class );
5921
5922                                 links = osrfHashGet( class_meta, "links" );
5923
5924                                 // Make an osrfStringArray of the names of fields to be fleshed
5925                                 if( flesh_fields ) {
5926                                         if( flesh_fields->size == 1 ) {
5927                                                 const char* _t = jsonObjectGetString(
5928                                                         jsonObjectGetIndex( flesh_fields, 0 ) );
5929                                                 if( !strcmp( _t, "*" ))
5930                                                         link_fields = osrfHashKeys( links );
5931                                         }
5932
5933                                         if( !link_fields ) {
5934                                                 jsonObject* _f;
5935                                                 link_fields = osrfNewStringArray( 1 );
5936                                                 jsonIterator* _i = jsonNewIterator( flesh_fields );
5937                                                 while ((_f = jsonIteratorNext( _i ))) {
5938                                                         osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5939                                                 }
5940                                                 jsonIteratorFree( _i );
5941                                         }
5942                                 }
5943                                 want_flesh = link_fields ? 1 : 0;
5944                         }
5945                 }
5946
5947                 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5948
5949                 // Iterate over the JSON_ARRAY of rows
5950                 jsonObject* cur;
5951                 unsigned long res_idx = 0;
5952                 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5953
5954                         int i = 0;
5955                         const char* link_field;
5956
5957                         // Iterate over the list of fleshable fields
5958                         if ( want_flesh ) {
5959                                 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5960
5961                                         osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5962
5963                                         osrfHash* kid_link = osrfHashGet( links, link_field );
5964                                         if( !kid_link )
5965                                                 continue;     // Not a link field; skip it
5966
5967                                         osrfHash* field = osrfHashGet( fields, link_field );
5968                                         if( !field )
5969                                                 continue;     // Not a field at all; skip it (IDL is ill-formed)
5970
5971                                         osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5972                                                 osrfHashGet( kid_link, "class" ));
5973                                         if( !kid_idl )
5974                                                 continue;   // The class it links to doesn't exist; skip it
5975
5976                                         const char* reltype = osrfHashGet( kid_link, "reltype" );
5977                                         if( !reltype )
5978                                                 continue;   // No reltype; skip it (IDL is ill-formed)
5979
5980                                         osrfHash* value_field = field;
5981
5982                                         if(    !strcmp( reltype, "has_many" )
5983                                                 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5984                                                 value_field = osrfHashGet(
5985                                                         fields, osrfHashGet( class_meta, "primarykey" ) );
5986                                         }
5987
5988                                         int kid_has_controller = osrfStringArrayContains( osrfHashGet(kid_idl, "controller"), modulename );
5989                                         // fleshing pcrud case: we require the controller in need_to_verify mode
5990                                         if ( !kid_has_controller && enforce_pcrud && need_to_verify ) {
5991                                                 osrfLogInfo( OSRF_LOG_MARK, "%s is not listed as a controller for %s; moving on", modulename, core_class );
5992
5993                                                 jsonObjectSetIndex(
5994                                                         cur,
5995                                                         (unsigned long) atoi( osrfHashGet(field, "array_position") ),
5996                                                         jsonNewObjectType(
5997                                                                 !strcmp( reltype, "has_many" ) ? JSON_ARRAY : JSON_NULL
5998                                                         )
5999                                                 );
6000                                                 continue;
6001                                         }
6002
6003                                         osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
6004
6005                                         if( link_map->size > 0 ) {
6006                                                 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
6007                                                 jsonObjectPush(
6008                                                         _kid_key,
6009                                                         jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
6010                                                 );
6011
6012                                                 jsonObjectSetKey(
6013                                                         flesh_blob,
6014                                                         osrfHashGet( kid_link, "class" ),
6015                                                         _kid_key
6016                                                 );
6017                                         };
6018
6019                                         osrfLogDebug(
6020                                                 OSRF_LOG_MARK,
6021                                                 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
6022                                                 osrfHashGet( kid_link, "field" ),
6023                                                 osrfHashGet( kid_link, "class" ),
6024                                                 osrfHashGet( kid_link, "key" ),
6025                                                 osrfHashGet( kid_link, "reltype" )
6026                                         );
6027
6028                                         const char* search_key = jsonObjectGetString(
6029                                                 jsonObjectGetIndex( cur,
6030                                                         atoi( osrfHashGet( value_field, "array_position" ) )
6031                                                 )
6032                                         );
6033
6034                                         if( !search_key ) {
6035                                                 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
6036                                                 continue;
6037                                         }
6038
6039                                         osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
6040
6041                                         // construct WHERE clause
6042                                         jsonObject* where_clause  = jsonNewObjectType( JSON_HASH );
6043                                         jsonObjectSetKey(
6044                                                 where_clause,
6045                                                 osrfHashGet( kid_link, "key" ),
6046                                                 jsonNewObject( search_key )
6047                                         );
6048
6049                                         // construct the rest of the query, mostly
6050                                         // by copying pieces of the previous level of query
6051                                         jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
6052                                         jsonObjectSetKey( rest_of_query, "flesh",
6053                                                 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
6054                                         );
6055
6056                                         if( flesh_blob )
6057                                                 jsonObjectSetKey( rest_of_query, "flesh_fields",
6058                                                         jsonObjectClone( flesh_blob ));
6059
6060                                         if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
6061                                                 jsonObjectSetKey( rest_of_query, "order_by",
6062                                                         jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
6063                                                 );
6064                                         }
6065
6066                                         if( jsonObjectGetKeyConst( query_hash, "select" )) {
6067                                                 jsonObjectSetKey( rest_of_query, "select",
6068                                                         jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
6069                                                 );
6070                                         }
6071
6072                                         // do the query, recursively, to expand the fleshable field
6073                                         jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
6074                                                 where_clause, rest_of_query, err );
6075
6076                                         jsonObjectFree( where_clause );
6077                                         jsonObjectFree( rest_of_query );
6078
6079                                         if( *err ) {
6080                                                 osrfStringArrayFree( link_fields );
6081                                                 jsonObjectFree( res_list );
6082                                                 jsonObjectFree( flesh_blob );
6083                                                 return NULL;
6084                                         }
6085
6086                                         osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
6087                                                 osrfHashGet( kid_link, "class" ), kids->size );
6088
6089                                         // Traverse the result set
6090                                         jsonObject* X = NULL;
6091                                         if( link_map->size > 0 && kids->size > 0 ) {
6092                                                 X = kids;
6093                                                 kids = jsonNewObjectType( JSON_ARRAY );
6094
6095                                                 jsonObject* _k_node;
6096                                                 unsigned long res_idx = 0;
6097                                                 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
6098                                                         jsonObjectPush(
6099                                                                 kids,
6100                                                                 jsonObjectClone(
6101                                                                         jsonObjectGetIndex(
6102                                                                                 _k_node,
6103                                                                                 (unsigned long) atoi(
6104                                                                                         osrfHashGet(
6105                                                                                                 osrfHashGet(
6106                                                                                                         osrfHashGet(
6107                                                                                                                 osrfHashGet(
6108                                                                                                                         oilsIDL(),
6109                                                                                                                         osrfHashGet( kid_link, "class" )
6110                                                                                                                 ),
6111                                                                                                                 "fields"
6112                                                                                                         ),
6113                                                                                                         osrfStringArrayGetString( link_map, 0 )
6114                                                                                                 ),
6115                                                                                                 "array_position"
6116                                                                                         )
6117                                                                                 )
6118                                                                         )
6119                                                                 )
6120                                                         );
6121                                                 } // end while loop traversing X
6122                                         }
6123
6124                                         if (kids->size > 0) {
6125
6126                                                 if((   !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
6127                                                         || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" ))
6128                                                 ) {
6129                                                         osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
6130                                                                 osrfHashGet( kid_link, "field" ));
6131                                                         jsonObjectSetIndex(
6132                                                                 cur,
6133                                                                 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6134                                                                 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
6135                                                         );
6136                                                 }
6137                                         }
6138
6139                                         if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
6140                                                 // has_many
6141                                                 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
6142                                                         osrfHashGet( kid_link, "field" ) );
6143                                                 jsonObjectSetIndex(
6144                                                         cur,
6145                                                         (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6146                                                         jsonObjectClone( kids )
6147                                                 );
6148                                         }
6149
6150                                         if( X ) {
6151                                                 jsonObjectFree( kids );
6152                                                 kids = X;
6153                                         }
6154
6155                                         jsonObjectFree( kids );
6156
6157                                         osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
6158                                                 osrfHashGet( kid_link, "field" ) );
6159                                         osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
6160
6161                                 } // end while loop traversing list of fleshable fields
6162                         }
6163
6164                         if( i_respond_directly ) {
6165                                 if ( *methodtype == 'i' ) {
6166                                         osrfAppRespond( ctx,
6167                                                 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
6168                                 } else {
6169                                         osrfAppRespond( ctx, cur );
6170                                 }
6171                         }
6172                 } // end while loop traversing res_list
6173                 jsonObjectFree( flesh_blob );
6174                 osrfStringArrayFree( link_fields );
6175         }
6176
6177         if( i_respond_directly ) {
6178                 jsonObjectFree( res_list );
6179                 return jsonNewObjectType( JSON_ARRAY );
6180         } else {
6181                 return res_list;
6182         }
6183 }
6184
6185
6186 int doUpdate( osrfMethodContext* ctx ) {
6187         if( osrfMethodVerifyContext( ctx )) {
6188                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6189                 return -1;
6190         }
6191
6192         if( enforce_pcrud )
6193                 timeout_needs_resetting = 1;
6194
6195         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6196
6197         jsonObject* target = NULL;
6198         if( enforce_pcrud )
6199                 target = jsonObjectGetIndex( ctx->params, 1 );
6200         else
6201                 target = jsonObjectGetIndex( ctx->params, 0 );
6202
6203         if(!verifyObjectClass( ctx, target )) {
6204                 osrfAppRespondComplete( ctx, NULL );
6205                 return -1;
6206         }
6207
6208         if( getXactId( ctx ) == NULL ) {
6209                 osrfAppSessionStatus(
6210                         ctx->session,
6211                         OSRF_STATUS_BADREQUEST,
6212                         "osrfMethodException",
6213                         ctx->request,
6214                         "No active transaction -- required for UPDATE"
6215                 );
6216                 osrfAppRespondComplete( ctx, NULL );
6217                 return -1;
6218         }
6219
6220         // The following test is harmless but redundant.  If a class is
6221         // readonly, we don't register an update method for it.
6222         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6223                 osrfAppSessionStatus(
6224                         ctx->session,
6225                         OSRF_STATUS_BADREQUEST,
6226                         "osrfMethodException",
6227                         ctx->request,
6228                         "Cannot UPDATE readonly class"
6229                 );
6230                 osrfAppRespondComplete( ctx, NULL );
6231                 return -1;
6232         }
6233
6234         const char* trans_id = getXactId( ctx );
6235
6236         // Set the last_xact_id
6237         int index = oilsIDL_ntop( target->classname, "last_xact_id" );
6238         if( index > -1 ) {
6239                 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
6240                                 trans_id, target->classname, index );
6241                 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
6242         }
6243
6244         char* pkey = osrfHashGet( meta, "primarykey" );
6245         osrfHash* fields = osrfHashGet( meta, "fields" );
6246
6247         char* id = oilsFMGetString( target, pkey );
6248
6249         osrfLogDebug(
6250                 OSRF_LOG_MARK,
6251                 "%s updating %s object with %s = %s",
6252                 modulename,
6253                 osrfHashGet( meta, "fieldmapper" ),
6254                 pkey,
6255                 id
6256         );
6257
6258         dbhandle = writehandle;
6259         growing_buffer* sql = buffer_init( 128 );
6260         buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
6261
6262         int first = 1;
6263         osrfHash* field_def = NULL;
6264         osrfHashIterator* field_itr = osrfNewHashIterator( fields );
6265         while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
6266
6267                 // Skip virtual fields, and the primary key
6268                 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
6269                         continue;
6270
6271                 if (osrfStringArrayContains( osrfHashGet(field_def, "suppress_controller"), modulename ))
6272                         continue;
6273
6274
6275                 const char* field_name = osrfHashIteratorKey( field_itr );
6276                 if( ! strcmp( field_name, pkey ) )
6277                         continue;
6278
6279                 const jsonObject* field_object = oilsFMGetObject( target, field_name );
6280
6281                 int value_is_numeric = 0;    // boolean
6282                 char* value;
6283                 if( field_object && field_object->classname ) {
6284                         value = oilsFMGetString(
6285                                 field_object,
6286                                 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
6287                         );
6288                 } else if( field_object && JSON_BOOL == field_object->type ) {
6289                         if( jsonBoolIsTrue( field_object ) )
6290                                 value = strdup( "t" );
6291                         else
6292                                 value = strdup( "f" );
6293                 } else {
6294                         value = jsonObjectToSimpleString( field_object );
6295                         if( field_object && JSON_NUMBER == field_object->type )
6296                                 value_is_numeric = 1;
6297                 }
6298
6299                 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
6300                                 osrfHashGet( meta, "fieldmapper" ), field_name, value);
6301
6302                 if( !field_object || field_object->type == JSON_NULL ) {
6303                         if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
6304                                         && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
6305                                 if( first )
6306                                         first = 0;
6307                                 else
6308                                         OSRF_BUFFER_ADD_CHAR( sql, ',' );
6309                                 buffer_fadd( sql, " %s = NULL", field_name );
6310                         }
6311
6312                 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
6313                         if( first )
6314                                 first = 0;
6315                         else
6316                                 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6317
6318                         const char* numtype = get_datatype( field_def );
6319                         if( !strncmp( numtype, "INT", 3 ) ) {
6320                                 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
6321                         } else if( !strcmp( numtype, "NUMERIC" ) ) {
6322                                 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
6323                         } else {
6324                                 // Must really be intended as a string, so quote it
6325                                 if( dbi_conn_quote_string( dbhandle, &value )) {
6326                                         buffer_fadd( sql, " %s = %s", field_name, value );
6327                                 } else {
6328                                         osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
6329                                                 modulename, value );
6330                                         osrfAppSessionStatus(
6331                                                 ctx->session,
6332                                                 OSRF_STATUS_INTERNALSERVERERROR,
6333                                                 "osrfMethodException",
6334                                                 ctx->request,
6335                                                 "Error quoting string -- please see the error log for more details"
6336                                         );
6337                                         free( value );
6338                                         free( id );
6339                                         osrfHashIteratorFree( field_itr );
6340                                         buffer_free( sql );
6341                                         osrfAppRespondComplete( ctx, NULL );
6342                                         return -1;
6343                                 }
6344                         }
6345
6346                         osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
6347
6348                 } else {
6349                         if( dbi_conn_quote_string( dbhandle, &value ) ) {
6350                                 if( first )
6351                                         first = 0;
6352                                 else
6353                                         OSRF_BUFFER_ADD_CHAR( sql, ',' );
6354                                 buffer_fadd( sql, " %s = %s", field_name, value );
6355                         } else {
6356                                 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
6357                                 osrfAppSessionStatus(
6358                                         ctx->session,
6359                                         OSRF_STATUS_INTERNALSERVERERROR,
6360                                         "osrfMethodException",
6361                                         ctx->request,
6362                                         "Error quoting string -- please see the error log for more details"
6363                                 );
6364                                 free( value );
6365                                 free( id );
6366                                 osrfHashIteratorFree( field_itr );
6367                                 buffer_free( sql );
6368                                 osrfAppRespondComplete( ctx, NULL );
6369                                 return -1;
6370                         }
6371                 }
6372
6373                 free( value );
6374
6375         } // end while
6376
6377         osrfHashIteratorFree( field_itr );
6378
6379         jsonObject* obj = jsonNewObject( id );
6380
6381         if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6382                 dbi_conn_quote_string( dbhandle, &id );
6383
6384         buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6385
6386         char* query = buffer_release( sql );
6387         osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6388
6389         dbi_result result = dbi_conn_query( dbhandle, query );
6390         free( query );
6391
6392         int rc = 0;
6393         if( !result ) {
6394                 jsonObjectFree( obj );
6395                 obj = jsonNewObject( NULL );
6396                 const char* msg;
6397                 int errnum = dbi_conn_error( dbhandle, &msg );
6398                 osrfLogError(
6399                         OSRF_LOG_MARK,
6400                         "%s ERROR updating %s object with %s = %s: %d %s",
6401                         modulename,
6402                         osrfHashGet( meta, "fieldmapper" ),
6403                         pkey,
6404                         id,
6405                         errnum,
6406                         msg ? msg : "(No description available)"
6407                 );
6408                 osrfAppSessionStatus(
6409                         ctx->session,
6410                         OSRF_STATUS_INTERNALSERVERERROR,
6411                         "osrfMethodException",
6412                         ctx->request,
6413                         "Error in updating a row -- please see the error log for more details"
6414                 );
6415                 if( !oilsIsDBConnected( dbhandle ))
6416                         osrfAppSessionPanic( ctx->session );
6417                 rc = -1;
6418         } else
6419                 dbi_result_free( result );
6420
6421         free( id );
6422         osrfAppRespondComplete( ctx, obj );
6423         jsonObjectFree( obj );
6424         return rc;
6425 }
6426
6427 int doDelete( osrfMethodContext* ctx ) {
6428         if( osrfMethodVerifyContext( ctx )) {
6429                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6430                 return -1;
6431         }
6432
6433         if( enforce_pcrud )
6434                 timeout_needs_resetting = 1;
6435
6436         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6437
6438         if( getXactId( ctx ) == NULL ) {
6439                 osrfAppSessionStatus(
6440                         ctx->session,
6441                         OSRF_STATUS_BADREQUEST,
6442                         "osrfMethodException",
6443                         ctx->request,
6444                         "No active transaction -- required for DELETE"
6445                 );
6446                 osrfAppRespondComplete( ctx, NULL );
6447                 return -1;
6448         }
6449
6450         // The following test is harmless but redundant.  If a class is
6451         // readonly, we don't register a delete method for it.
6452         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6453                 osrfAppSessionStatus(
6454                         ctx->session,
6455                         OSRF_STATUS_BADREQUEST,
6456                         "osrfMethodException",
6457                         ctx->request,
6458                         "Cannot DELETE readonly class"
6459                 );
6460                 osrfAppRespondComplete( ctx, NULL );
6461                 return -1;
6462         }
6463
6464         dbhandle = writehandle;
6465
6466         char* pkey = osrfHashGet( meta, "primarykey" );
6467
6468         int _obj_pos = 0;
6469         if( enforce_pcrud )
6470                 _obj_pos = 1;
6471
6472         char* id;
6473         if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6474                 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6475                         osrfAppRespondComplete( ctx, NULL );
6476                         return -1;
6477                 }
6478
6479                 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6480         } else {
6481                 if( enforce_pcrud && !verifyObjectPCRUD( ctx, meta, NULL, 1 )) {
6482                         osrfAppRespondComplete( ctx, NULL );
6483                         return -1;
6484                 }
6485                 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6486         }
6487
6488         osrfLogDebug(
6489                 OSRF_LOG_MARK,
6490                 "%s deleting %s object with %s = %s",
6491                 modulename,
6492                 osrfHashGet( meta, "fieldmapper" ),
6493                 pkey,
6494                 id
6495         );
6496
6497         jsonObject* obj = jsonNewObject( id );
6498
6499         if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6500                 dbi_conn_quote_string( writehandle, &id );
6501
6502         dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6503                 osrfHashGet( meta, "tablename" ), pkey, id );
6504
6505         int rc = 0;
6506         if( !result ) {
6507                 rc = -1;
6508                 jsonObjectFree( obj );
6509                 obj = jsonNewObject( NULL );
6510                 const char* msg;
6511                 int errnum = dbi_conn_error( writehandle, &msg );
6512                 osrfLogError(
6513                         OSRF_LOG_MARK,
6514                         "%s ERROR deleting %s object with %s = %s: %d %s",
6515                         modulename,
6516                         osrfHashGet( meta, "fieldmapper" ),
6517                         pkey,
6518                         id,
6519                         errnum,
6520                         msg ? msg : "(No description available)"
6521                 );
6522                 osrfAppSessionStatus(
6523                         ctx->session,
6524                         OSRF_STATUS_INTERNALSERVERERROR,
6525                         "osrfMethodException",
6526                         ctx->request,
6527                         "Error in deleting a row -- please see the error log for more details"
6528                 );
6529                 if( !oilsIsDBConnected( writehandle ))
6530                         osrfAppSessionPanic( ctx->session );
6531         } else
6532                 dbi_result_free( result );
6533
6534         free( id );
6535
6536         osrfAppRespondComplete( ctx, obj );
6537         jsonObjectFree( obj );
6538         return rc;
6539 }
6540
6541 /**
6542         @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6543         @param result An iterator for a result set; we only look at the current row.
6544         @param @meta Pointer to the class metadata for the core class.
6545         @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6546
6547         If a column is not defined in the IDL, or if it has no array_position defined for it in
6548         the IDL, or if it is defined as virtual, ignore it.
6549
6550         Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6551         or JSON_STRING.  Then insert this jsonObject into the JSON_ARRAY according to its
6552         array_position in the IDL.
6553
6554         A field defined in the IDL but not represented in the returned row will leave a hole
6555         in the JSON_ARRAY.  In effect it will be treated as a null value.
6556
6557         In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6558         regardless of their sequence in the SELECT statement.  The JSON_ARRAY is assigned the
6559         classname corresponding to the @a meta argument.
6560
6561         The calling code is responsible for freeing the the resulting jsonObject by calling
6562         jsonObjectFree().
6563 */
6564 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6565         if( !( result && meta )) return NULL;
6566
6567         jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6568         jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6569         osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6570
6571         osrfHash* fields = osrfHashGet( meta, "fields" );
6572
6573         int columnIndex = 1;
6574         const char* columnName;
6575
6576         /* cycle through the columns in the row returned from the database */
6577         while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6578
6579                 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6580
6581                 int fmIndex = -1;  // Will be set to the IDL's sequence number for this field
6582
6583                 /* determine the field type and storage attributes */
6584                 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6585                 int attr            = dbi_result_get_field_attribs_idx( result, columnIndex );
6586
6587                 // Fetch the IDL's sequence number for the field.  If the field isn't in the IDL,
6588                 // or if it has no sequence number there, or if it's virtual, skip it.
6589                 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6590                 if( _f ) {
6591
6592                         if( str_is_true( osrfHashGet( _f, "virtual" )))
6593                                 continue;   // skip this column: IDL says it's virtual
6594
6595                         const char* pos = (char*) osrfHashGet( _f, "array_position" );
6596                         if( !pos )      // IDL has no sequence number for it.  This shouldn't happen,
6597                                 continue;    // since we assign sequence numbers dynamically as we load the IDL.
6598
6599                         fmIndex = atoi( pos );
6600                         osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6601                 } else {
6602                         continue;     // This field is not defined in the IDL
6603                 }
6604
6605                 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6606                 // sequence number from the IDL (which is likely to be different from the sequence
6607                 // of columns in the SELECT clause).
6608                 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6609                         jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6610                 } else {
6611
6612                         switch( type ) {
6613
6614                                 case DBI_TYPE_INTEGER :
6615
6616                                         if( attr & DBI_INTEGER_SIZE8 )
6617                                                 jsonObjectSetIndex( object, fmIndex,
6618                                                         jsonNewNumberObject(
6619                                                                 dbi_result_get_longlong_idx( result, columnIndex )));
6620                                         else
6621                                                 jsonObjectSetIndex( object, fmIndex,
6622                                                         jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6623
6624                                         break;
6625
6626                                 case DBI_TYPE_DECIMAL :
6627                                         jsonObjectSetIndex( object, fmIndex,
6628                                                         jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6629                                         break;
6630
6631                                 case DBI_TYPE_STRING :
6632
6633                                         jsonObjectSetIndex(
6634                                                 object,
6635                                                 fmIndex,
6636                                                 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6637                                         );
6638
6639                                         break;
6640
6641                                 case DBI_TYPE_DATETIME : {
6642
6643                                         char dt_string[ 256 ] = "";
6644                                         struct tm gmdt;
6645
6646                                         // Fetch the date column as a time_t
6647                                         time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6648
6649                                         // Translate the time_t to a human-readable string
6650                                         if( !( attr & DBI_DATETIME_DATE )) {
6651                                                 gmtime_r( &_tmp_dt, &gmdt );
6652                                                 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6653                                         } else if( !( attr & DBI_DATETIME_TIME )) {
6654                                                 localtime_r( &_tmp_dt, &gmdt );
6655                                                 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6656                                         } else {
6657                                                 localtime_r( &_tmp_dt, &gmdt );
6658                                                 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6659                                         }
6660
6661                                         jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6662
6663                                         break;
6664                                 }
6665                                 case DBI_TYPE_BINARY :
6666                                         osrfLogError( OSRF_LOG_MARK,
6667                                                 "Can't do binary at column %s : index %d", columnName, columnIndex );
6668                         } // End switch
6669                 }
6670                 ++columnIndex;
6671         } // End while
6672
6673         return object;
6674 }
6675
6676 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6677         if( !result ) return NULL;
6678
6679         jsonObject* object = jsonNewObject( NULL );
6680
6681         time_t _tmp_dt;
6682         char dt_string[ 256 ];
6683         struct tm gmdt;
6684
6685         int fmIndex;
6686         int columnIndex = 1;
6687         int attr;
6688         unsigned short type;
6689         const char* columnName;
6690
6691         /* cycle through the column list */
6692         while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6693
6694                 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6695
6696                 fmIndex = -1; // reset the position
6697
6698                 /* determine the field type and storage attributes */
6699                 type = dbi_result_get_field_type_idx( result, columnIndex );
6700                 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6701
6702                 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6703                         jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6704                 } else {
6705
6706                         switch( type ) {
6707
6708                                 case DBI_TYPE_INTEGER :
6709
6710                                         if( attr & DBI_INTEGER_SIZE8 )
6711                                                 jsonObjectSetKey( object, columnName,
6712                                                                 jsonNewNumberObject( dbi_result_get_longlong_idx(
6713                                                                                 result, columnIndex )) );
6714                                         else
6715                                                 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6716                                                                 dbi_result_get_int_idx( result, columnIndex )) );
6717                                         break;
6718
6719                                 case DBI_TYPE_DECIMAL :
6720                                         jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6721                                                 dbi_result_get_double_idx( result, columnIndex )) );
6722                                         break;
6723
6724                                 case DBI_TYPE_STRING :
6725                                         jsonObjectSetKey( object, columnName,
6726                                                 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6727                                         break;
6728
6729                                 case DBI_TYPE_DATETIME :
6730
6731                                         memset( dt_string, '\0', sizeof( dt_string ));
6732                                         memset( &gmdt, '\0', sizeof( gmdt ));
6733
6734                                         _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6735
6736                                         if( !( attr & DBI_DATETIME_DATE )) {
6737                                                 gmtime_r( &_tmp_dt, &gmdt );
6738                                                 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6739                                         } else if( !( attr & DBI_DATETIME_TIME )) {
6740                                                 localtime_r( &_tmp_dt, &gmdt );
6741                                                 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6742                                         } else {
6743                                                 localtime_r( &_tmp_dt, &gmdt );
6744                                                 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6745                                         }
6746
6747                                         jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6748                                         break;
6749
6750                                 case DBI_TYPE_BINARY :
6751                                         osrfLogError( OSRF_LOG_MARK,
6752                                                 "Can't do binary at column %s : index %d", columnName, columnIndex );
6753                         }
6754                 }
6755                 ++columnIndex;
6756         } // end while loop traversing result
6757
6758         return object;
6759 }
6760
6761 // Interpret a string as true or false
6762 int str_is_true( const char* str ) {
6763         if( NULL == str || strcasecmp( str, "true" ) )
6764                 return 0;
6765         else
6766                 return 1;
6767 }
6768
6769 // Interpret a jsonObject as true or false
6770 static int obj_is_true( const jsonObject* obj ) {
6771         if( !obj )
6772                 return 0;
6773         else switch( obj->type )
6774         {
6775                 case JSON_BOOL :
6776                         if( obj->value.b )
6777                                 return 1;
6778                         else
6779                                 return 0;
6780                 case JSON_STRING :
6781                         if( strcasecmp( obj->value.s, "true" ) )
6782                                 return 0;
6783                         else
6784                                 return 1;
6785                 case JSON_NUMBER :          // Support 1/0 for perl's sake
6786                         if( jsonObjectGetNumber( obj ) == 1.0 )
6787                                 return 1;
6788                         else
6789                                 return 0;
6790                 default :
6791                         return 0;
6792         }
6793 }
6794
6795 // Translate a numeric code into a text string identifying a type of
6796 // jsonObject.  To be used for building error messages.
6797 static const char* json_type( int code ) {
6798         switch ( code )
6799         {
6800                 case 0 :
6801                         return "JSON_HASH";
6802                 case 1 :
6803                         return "JSON_ARRAY";
6804                 case 2 :
6805                         return "JSON_STRING";
6806                 case 3 :
6807                         return "JSON_NUMBER";
6808                 case 4 :
6809                         return "JSON_NULL";
6810                 case 5 :
6811                         return "JSON_BOOL";
6812                 default :
6813                         return "(unrecognized)";
6814         }
6815 }
6816
6817 // Extract the "primitive" attribute from an IDL field definition.
6818 // If we haven't initialized the app, then we must be running in
6819 // some kind of testbed.  In that case, default to "string".
6820 static const char* get_primitive( osrfHash* field ) {
6821         const char* s = osrfHashGet( field, "primitive" );
6822         if( !s ) {
6823                 if( child_initialized )
6824                         osrfLogError(
6825                                 OSRF_LOG_MARK,
6826                                 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6827                                 modulename,
6828                                 osrfHashGet( field, "name" )
6829                         );
6830
6831                 s = "string";
6832         }
6833         return s;
6834 }
6835
6836 // Extract the "datatype" attribute from an IDL field definition.
6837 // If we haven't initialized the app, then we must be running in
6838 // some kind of testbed.  In that case, default to to NUMERIC,
6839 // since we look at the datatype only for numbers.
6840 static const char* get_datatype( osrfHash* field ) {
6841         const char* s = osrfHashGet( field, "datatype" );
6842         if( !s ) {
6843                 if( child_initialized )
6844                         osrfLogError(
6845                                 OSRF_LOG_MARK,
6846                                 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6847                                 modulename,
6848                                 osrfHashGet( field, "name" )
6849                         );
6850                 else
6851                         s = "NUMERIC";
6852         }
6853         return s;
6854 }
6855
6856 /**
6857         @brief Determine whether a string is potentially a valid SQL identifier.
6858         @param s The identifier to be tested.
6859         @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6860
6861         Purpose: to prevent certain kinds of SQL injection.  To that end we don't necessarily
6862         need to follow all the rules exactly, such as requiring that the first character not
6863         be a digit.
6864
6865         We allow leading and trailing white space.  In between, we do not allow punctuation
6866         (except for underscores and dollar signs), control characters, or embedded white space.
6867
6868         More pedantically we should allow quoted identifiers containing arbitrary characters, but
6869         for the foreseeable future such quoted identifiers are not likely to be an issue.
6870 */
6871 int is_identifier( const char* s) {
6872         if( !s )
6873                 return 0;
6874
6875         // Skip leading white space
6876         while( isspace( (unsigned char) *s ) )
6877                 ++s;
6878
6879         if( !s )
6880                 return 0;   // Nothing but white space?  Not okay.
6881
6882         // Check each character until we reach white space or
6883         // end-of-string.  Letters, digits, underscores, and
6884         // dollar signs are okay. With the exception of periods
6885         // (as in schema.identifier), control characters and other
6886         // punctuation characters are not okay.  Anything else
6887         // is okay -- it could for example be part of a multibyte
6888         // UTF8 character such as a letter with diacritical marks,
6889         // and those are allowed.
6890         do {
6891                 if( isalnum( (unsigned char) *s )
6892                         || '.' == *s
6893                         || '_' == *s
6894                         || '$' == *s )
6895                         ;  // Fine; keep going
6896                 else if(   ispunct( (unsigned char) *s )
6897                                 || iscntrl( (unsigned char) *s ) )
6898                         return 0;
6899                         ++s;
6900         } while( *s && ! isspace( (unsigned char) *s ) );
6901
6902         // If we found any white space in the above loop,
6903         // the rest had better be all white space.
6904
6905         while( isspace( (unsigned char) *s ) )
6906                 ++s;
6907
6908         if( *s )
6909                 return 0;   // White space was embedded within non-white space
6910
6911         return 1;
6912 }
6913
6914 /**
6915         @brief Determine whether to accept a character string as a comparison operator.
6916         @param op The candidate comparison operator.
6917         @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6918
6919         We don't validate the operator for real.  We just make sure that it doesn't contain
6920         any semicolons or white space (with special exceptions for a few specific operators).
6921         The idea is to block certain kinds of SQL injection.  If it has no semicolons or white
6922         space but it's still not a valid operator, then the database will complain.
6923
6924         Another approach would be to compare the string against a short list of approved operators.
6925         We don't do that because we want to allow custom operators like ">100*", which at this
6926         writing would be difficult or impossible to express otherwise in a JSON query.
6927 */
6928 int is_good_operator( const char* op ) {
6929         if( !op ) return 0;   // Sanity check
6930
6931         const char* s = op;
6932         while( *s ) {
6933                 if( isspace( (unsigned char) *s ) ) {
6934                         // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6935                         // and IS NOT DISTINCT FROM.
6936                         if( !strcasecmp( op, "similar to" ) )
6937                                 return 1;
6938                         else if( !strcasecmp( op, "is distinct from" ) )
6939                                 return 1;
6940                         else if( !strcasecmp( op, "is not distinct from" ) )
6941                                 return 1;
6942                         else
6943                                 return 0;
6944                 }
6945                 else if( ';' == *s )
6946                         return 0;
6947                 ++s;
6948         }
6949         return 1;
6950 }
6951
6952 /**
6953         @name Query Frame Management
6954
6955         The following machinery supports a stack of query frames for use by SELECT().
6956
6957         A query frame caches information about one level of a SELECT query.  When we enter
6958         a subquery, we push another query frame onto the stack, and pop it off when we leave.
6959
6960         The query frame stores information about the core class, and about any joined classes
6961         in the FROM clause.
6962
6963         The main purpose is to map table aliases to classes and tables, so that a query can
6964         join to the same table more than once.  A secondary goal is to reduce the number of
6965         lookups in the IDL by caching the results.
6966 */
6967 /*@{*/
6968
6969 #define STATIC_CLASS_INFO_COUNT 3
6970
6971 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6972
6973 /**
6974         @brief Allocate a ClassInfo as raw memory.
6975         @return Pointer to the newly allocated ClassInfo.
6976
6977         Except for the in_use flag, which is used only by the allocation and deallocation
6978         logic, we don't initialize the ClassInfo here.
6979 */
6980 static ClassInfo* allocate_class_info( void ) {
6981         // In order to reduce the number of mallocs and frees, we return a static
6982         // instance of ClassInfo, if we can find one that we're not already using.
6983         // We rely on the fact that the compiler will implicitly initialize the
6984         // static instances so that in_use == 0.
6985
6986         int i;
6987         for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6988                 if( ! static_class_info[ i ].in_use ) {
6989                         static_class_info[ i ].in_use = 1;
6990                         return static_class_info + i;
6991                 }
6992         }
6993
6994         // The static ones are all in use.  Malloc one.
6995
6996         return safe_malloc( sizeof( ClassInfo ) );
6997 }
6998
6999 /**
7000         @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
7001         @param info Pointer to the ClassInfo to be cleared.
7002 */
7003 static void clear_class_info( ClassInfo* info ) {
7004         // Sanity check
7005         if( ! info )
7006                 return;
7007
7008         // Free any malloc'd strings
7009
7010         if( info->alias != info->alias_store )
7011                 free( info->alias );
7012
7013         if( info->class_name != info->class_name_store )
7014                 free( info->class_name );
7015
7016         free( info->source_def );
7017
7018         info->alias = info->class_name = info->source_def = NULL;
7019         info->next = NULL;
7020 }
7021
7022 /**
7023         @brief Free a ClassInfo and everything it owns.
7024         @param info Pointer to the ClassInfo to be freed.
7025 */
7026 static void free_class_info( ClassInfo* info ) {
7027         // Sanity check
7028         if( ! info )
7029                 return;
7030
7031         clear_class_info( info );
7032
7033         // If it's one of the static instances, just mark it as not in use
7034
7035         int i;
7036         for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
7037                 if( info == static_class_info + i ) {
7038                         static_class_info[ i ].in_use = 0;
7039                         return;
7040                 }
7041         }
7042
7043         // Otherwise it must have been malloc'd, so free it
7044
7045         free( info );
7046 }
7047
7048 /**
7049         @brief Populate an already-allocated ClassInfo.
7050         @param info Pointer to the ClassInfo to be populated.
7051         @param alias Alias for the class.  If it is NULL, or an empty string, use the class
7052         name for an alias.
7053         @param class Name of the class.
7054         @return Zero if successful, or 1 if not.
7055
7056         Populate the ClassInfo with copies of the alias and class name, and with pointers to
7057         the relevant portions of the IDL for the specified class.
7058 */
7059 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
7060         // Sanity checks
7061         if( ! info ){
7062                 osrfLogError( OSRF_LOG_MARK,
7063                                           "%s ERROR: No ClassInfo available to populate", modulename );
7064                 info->alias = info->class_name = info->source_def = NULL;
7065                 info->class_def = info->fields = info->links = NULL;
7066                 return 1;
7067         }
7068
7069         if( ! class ) {
7070                 osrfLogError( OSRF_LOG_MARK,
7071                                           "%s ERROR: No class name provided for lookup", modulename );
7072                 info->alias = info->class_name = info->source_def = NULL;
7073                 info->class_def = info->fields = info->links = NULL;
7074                 return 1;
7075         }
7076
7077         // Alias defaults to class name if not supplied
7078         if( ! alias || ! alias[ 0 ] )
7079                 alias = class;
7080
7081         // Look up class info in the IDL
7082         osrfHash* class_def = osrfHashGet( oilsIDL(), class );
7083         if( ! class_def ) {
7084                 osrfLogError( OSRF_LOG_MARK,
7085                                           "%s ERROR: Class %s not defined in IDL", modulename, class );
7086                 info->alias = info->class_name = info->source_def = NULL;
7087                 info->class_def = info->fields = info->links = NULL;
7088                 return 1;
7089         } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
7090                 osrfLogError( OSRF_LOG_MARK,
7091                                           "%s ERROR: Class %s is defined as virtual", modulename, class );
7092                 info->alias = info->class_name = info->source_def = NULL;
7093                 info->class_def = info->fields = info->links = NULL;
7094                 return 1;
7095         }
7096
7097         osrfHash* links = osrfHashGet( class_def, "links" );
7098         if( ! links ) {
7099                 osrfLogError( OSRF_LOG_MARK,
7100                                           "%s ERROR: No links defined in IDL for class %s", modulename, class );
7101                 info->alias = info->class_name = info->source_def = NULL;
7102                 info->class_def = info->fields = info->links = NULL;
7103                 return 1;
7104         }
7105
7106         osrfHash* fields = osrfHashGet( class_def, "fields" );
7107         if( ! fields ) {
7108                 osrfLogError( OSRF_LOG_MARK,
7109                                           "%s ERROR: No fields defined in IDL for class %s", modulename, class );
7110                 info->alias = info->class_name = info->source_def = NULL;
7111                 info->class_def = info->fields = info->links = NULL;
7112                 return 1;
7113         }
7114
7115         char* source_def = oilsGetRelation( class_def );
7116         if( ! source_def )
7117                 return 1;
7118
7119         // We got everything we need, so populate the ClassInfo
7120         if( strlen( alias ) > ALIAS_STORE_SIZE )
7121                 info->alias = strdup( alias );
7122         else {
7123                 strcpy( info->alias_store, alias );
7124                 info->alias = info->alias_store;
7125         }
7126
7127         if( strlen( class ) > CLASS_NAME_STORE_SIZE )
7128                 info->class_name = strdup( class );
7129         else {
7130                 strcpy( info->class_name_store, class );
7131                 info->class_name = info->class_name_store;
7132         }
7133
7134         info->source_def = source_def;
7135
7136         info->class_def = class_def;
7137         info->links     = links;
7138         info->fields    = fields;
7139
7140         return 0;
7141 }
7142
7143 #define STATIC_FRAME_COUNT 3
7144
7145 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
7146
7147 /**
7148         @brief Allocate a QueryFrame as raw memory.
7149         @return Pointer to the newly allocated QueryFrame.
7150
7151         Except for the in_use flag, which is used only by the allocation and deallocation
7152         logic, we don't initialize the QueryFrame here.
7153 */
7154 static QueryFrame* allocate_frame( void ) {
7155         // In order to reduce the number of mallocs and frees, we return a static
7156         // instance of QueryFrame, if we can find one that we're not already using.
7157         // We rely on the fact that the compiler will implicitly initialize the
7158         // static instances so that in_use == 0.
7159
7160         int i;
7161         for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7162                 if( ! static_frame[ i ].in_use ) {
7163                         static_frame[ i ].in_use = 1;
7164                         return static_frame + i;
7165                 }
7166         }
7167
7168         // The static ones are all in use.  Malloc one.
7169
7170         return safe_malloc( sizeof( QueryFrame ) );
7171 }
7172
7173 /**
7174         @brief Free a QueryFrame, and all the memory it owns.
7175         @param frame Pointer to the QueryFrame to be freed.
7176 */
7177 static void free_query_frame( QueryFrame* frame ) {
7178         // Sanity check
7179         if( ! frame )
7180                 return;
7181
7182         clear_class_info( &frame->core );
7183
7184         // Free the join list
7185         ClassInfo* temp;
7186         ClassInfo* info = frame->join_list;
7187         while( info ) {
7188                 temp = info->next;
7189                 free_class_info( info );
7190                 info = temp;
7191         }
7192
7193         frame->join_list = NULL;
7194         frame->next = NULL;
7195
7196         // If the frame is a static instance, just mark it as unused
7197         int i;
7198         for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7199                 if( frame == static_frame + i ) {
7200                         static_frame[ i ].in_use = 0;
7201                         return;
7202                 }
7203         }
7204
7205         // Otherwise it must have been malloc'd, so free it
7206
7207         free( frame );
7208 }
7209
7210 /**
7211         @brief Search a given QueryFrame for a specified alias.
7212         @param frame Pointer to the QueryFrame to be searched.
7213         @param target The alias for which to search.
7214         @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
7215 */
7216 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
7217         if( ! frame || ! target ) {
7218                 return NULL;
7219         }
7220
7221         ClassInfo* found_class = NULL;
7222
7223         if( !strcmp( target, frame->core.alias ) )
7224                 return &(frame->core);
7225         else {
7226                 ClassInfo* curr_class = frame->join_list;
7227                 while( curr_class ) {
7228                         if( strcmp( target, curr_class->alias ) )
7229                                 curr_class = curr_class->next;
7230                         else {
7231                                 found_class = curr_class;
7232                                 break;
7233                         }
7234                 }
7235         }
7236
7237         return found_class;
7238 }
7239
7240 /**
7241         @brief Push a new (blank) QueryFrame onto the stack.
7242 */
7243 static void push_query_frame( void ) {
7244         QueryFrame* frame = allocate_frame();
7245         frame->join_list = NULL;
7246         frame->next = curr_query;
7247
7248         // Initialize the ClassInfo for the core class
7249         ClassInfo* core = &frame->core;
7250         core->alias = core->class_name = core->source_def = NULL;
7251         core->class_def = core->fields = core->links = NULL;
7252
7253         curr_query = frame;
7254 }
7255
7256 /**
7257         @brief Pop a QueryFrame off the stack and destroy it.
7258 */
7259 static void pop_query_frame( void ) {
7260         // Sanity check
7261         if( ! curr_query )
7262                 return;
7263
7264         QueryFrame* popped = curr_query;
7265         curr_query = popped->next;
7266
7267         free_query_frame( popped );
7268 }
7269
7270 /**
7271         @brief Populate the ClassInfo for the core class.
7272         @param alias Alias for the core class.  If it is NULL or an empty string, we use the
7273         class name as an alias.
7274         @param class_name Name of the core class.
7275         @return Zero if successful, or 1 if not.
7276
7277         Populate the ClassInfo of the core class with copies of the alias and class name, and
7278         with pointers to the relevant portions of the IDL for the core class.
7279 */
7280 static int add_query_core( const char* alias, const char* class_name ) {
7281
7282         // Sanity checks
7283         if( ! curr_query ) {
7284                 osrfLogError( OSRF_LOG_MARK,
7285                                           "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
7286                 return 1;
7287         } else if( curr_query->core.alias ) {
7288                 osrfLogError( OSRF_LOG_MARK,
7289                                           "%s ERROR: Core class %s already populated as %s",
7290                                           modulename, curr_query->core.class_name, curr_query->core.alias );
7291                 return 1;
7292         }
7293
7294         build_class_info( &curr_query->core, alias, class_name );
7295         if( curr_query->core.alias )
7296                 return 0;
7297         else {
7298                 osrfLogError( OSRF_LOG_MARK,
7299                                           "%s ERROR: Unable to look up core class %s", modulename, class_name );
7300                 return 1;
7301         }
7302 }
7303
7304 /**
7305         @brief Search the current QueryFrame for a specified alias.
7306         @param target The alias for which to search.
7307         @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7308 */
7309 static inline ClassInfo* search_alias( const char* target ) {
7310         return search_alias_in_frame( curr_query, target );
7311 }
7312
7313 /**
7314         @brief Search all levels of query for a specified alias, starting with the current query.
7315         @param target The alias for which to search.
7316         @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7317 */
7318 static ClassInfo* search_all_alias( const char* target ) {
7319         ClassInfo* found_class = NULL;
7320         QueryFrame* curr_frame = curr_query;
7321
7322         while( curr_frame ) {
7323                 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
7324                         break;
7325                 else
7326                         curr_frame = curr_frame->next;
7327         }
7328
7329         return found_class;
7330 }
7331
7332 /**
7333         @brief Add a class to the list of classes joined to the current query.
7334         @param alias Alias of the class to be added.  If it is NULL or an empty string, we use
7335         the class name as an alias.
7336         @param classname The name of the class to be added.
7337         @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
7338 */
7339 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
7340
7341         if( ! classname || ! *classname ) {    // sanity check
7342                 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
7343                 return NULL;
7344         }
7345
7346         if( ! alias )
7347                 alias = classname;
7348
7349         const ClassInfo* conflict = search_alias( alias );
7350         if( conflict ) {
7351                 osrfLogError( OSRF_LOG_MARK,
7352                                           "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
7353                                           modulename, alias, conflict->class_name );
7354                 return NULL;
7355         }
7356
7357         ClassInfo* info = allocate_class_info();
7358
7359         if( build_class_info( info, alias, classname ) ) {
7360                 free_class_info( info );
7361                 return NULL;
7362         }
7363
7364         // Add the new ClassInfo to the join list of the current QueryFrame
7365         info->next = curr_query->join_list;
7366         curr_query->join_list = info;
7367
7368         return info;
7369 }
7370
7371 /**
7372         @brief Destroy all nodes on the query stack.
7373 */
7374 static void clear_query_stack( void ) {
7375         while( curr_query )
7376                 pop_query_frame();
7377 }
7378
7379 /**
7380         @brief Implement the set_audit_info method.
7381         @param ctx Pointer to the method context.
7382         @return Zero if successful, or -1 if not.
7383
7384         Issue a SAVEPOINT to the database server.
7385
7386         Method parameters:
7387         - authkey
7388         - user id (int)
7389         - workstation id (int)
7390
7391         If user id is not provided the authkey will be used.
7392         For PCRUD the authkey is always used, even if a user is provided.
7393 */
7394 int setAuditInfo( osrfMethodContext* ctx ) {
7395         if(osrfMethodVerifyContext( ctx )) {
7396                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
7397                 return -1;
7398         }
7399
7400         // Get the user id from the parameters
7401         const char* user_id = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 1) );
7402
7403         if( enforce_pcrud || !user_id ) {
7404                 timeout_needs_resetting = 1;
7405                 const jsonObject* user = verifyUserPCRUD( ctx );
7406                 if( !user )
7407                         return -1;
7408                 osrfAppRespondComplete( ctx, NULL );
7409                 return 0;
7410         }
7411
7412         // Not PCRUD and have a user_id?
7413         int result = writeAuditInfo( ctx, user_id, jsonObjectGetString( jsonObjectGetIndex(ctx->params, 2) ) );
7414         osrfAppRespondComplete( ctx, NULL );
7415         return result;
7416 }
7417
7418 /**
7419         @brief Save a audit info
7420         @param ctx Pointer to the method context.
7421         @param user_id User ID to write as a string
7422         @param ws_id Workstation ID to write as a string
7423 */
7424 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id) {
7425         if( ctx && ctx->session ) {
7426                 osrfAppSession* session = ctx->session;
7427
7428                 osrfHash* cache = session->userData;
7429
7430                 // If the session doesn't already have a hash, create one.  Make sure
7431                 // that the application session frees the hash when it terminates.
7432                 if( NULL == cache ) {
7433                         session->userData = cache = osrfNewHash();
7434                         osrfHashSetCallback( cache, &sessionDataFree );
7435                         ctx->session->userDataFree = &userDataFree;
7436                 }
7437
7438                 dbi_result result = dbi_conn_queryf( writehandle, "SELECT auditor.set_audit_info( %s, %s );", user_id, ws_id ? ws_id : "NULL" );
7439                 if( !result ) {
7440                         osrfLogWarning( OSRF_LOG_MARK, "BAD RESULT" );
7441                         const char* msg;
7442                         int errnum = dbi_conn_error( writehandle, &msg );
7443                         osrfLogError(
7444                                 OSRF_LOG_MARK,
7445                                 "%s: Error setting auditor information: %d %s",
7446                                 modulename,
7447                                 errnum,
7448                                 msg ? msg : "(No description available)"
7449                         );
7450                         osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
7451                                 "osrfMethodException", ctx->request, "Error setting auditor info" );
7452                         if( !oilsIsDBConnected( writehandle ))
7453                                 osrfAppSessionPanic( ctx->session );
7454                         return -1;
7455                 } else {
7456                         dbi_result_free( result );
7457                 }
7458         }
7459         return 0;
7460 }
7461
7462 /**
7463         @brief Remove all but safe character from savepoint name
7464         @param sp User-supplied savepoint name
7465         @return sanitized savepoint name, or NULL
7466
7467     The caller is expected to free the returned string.  Note that
7468     this function exists only because we can't use PQescapeLiteral
7469     without either forking libdbi or abandoning it.
7470 */
7471 static char* _sanitize_savepoint_name( const char* sp ) {
7472
7473         const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_";
7474
7475         // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7476         // and the default value of NAMEDATALEN is 64; that should be long enough
7477         // for our purposes, and it's unlikely that anyone is going to recompile
7478         // PostgreSQL to have a smaller value, so cap the identifier name
7479         // accordingly to avoid the remote chance that someone manages to pass in a
7480         // 12GB savepoint name
7481         const int MAX_LITERAL_NAMELEN = 63;
7482         int len = 0;
7483         len = strlen( sp );
7484         if (len > MAX_LITERAL_NAMELEN) {
7485                 len = MAX_LITERAL_NAMELEN;
7486         }
7487
7488         char* safeSpName = safe_malloc( len + 1 );
7489         int i = 0;
7490         int j;
7491         char* found;
7492         for (j = 0; j < len; j++) {
7493         found = strchr(safe_chars, sp[j]);
7494                 if (found) {
7495                         safeSpName[ i++ ] = found[0];
7496                 }
7497         }
7498         safeSpName[ i ] = '\0';
7499         return safeSpName;
7500 }
7501
7502 /*@}*/