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