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