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