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