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