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