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