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