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