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