]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/c-apps/oils_sql.c
Merge branch 'master' of git.evergreen-ils.org:Evergreen into template-toolkit-opac
[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                 // no result, skip this entirely
2362                 if(NULL != obj && !verifyObjectPCRUD( ctx, obj, 1 )) {
2363                         jsonObjectFree( obj );
2364
2365                         growing_buffer* msg = buffer_init( 128 );
2366                         OSRF_BUFFER_ADD( msg, modulename );
2367                         OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2368
2369                         char* m = buffer_release( msg );
2370                         osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2371                                         ctx->request, m );
2372                         free( m );
2373
2374                         osrfAppRespondComplete( ctx, NULL );
2375                         return -1;
2376                 }
2377         }
2378
2379         osrfAppRespondComplete( ctx, obj );
2380         jsonObjectFree( obj );
2381         return 0;
2382 }
2383
2384 /**
2385         @brief Translate a numeric value to a string representation for the database.
2386         @param field Pointer to the IDL field definition.
2387         @param value Pointer to a jsonObject holding the value of a field.
2388         @return Pointer to a newly allocated string.
2389
2390         The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2391         its contents are numeric.  A non-numeric string is likely to result in invalid SQL,
2392         or (what is worse) valid SQL that is wrong.
2393
2394         If the datatype of the receiving field is not numeric, wrap the value in quotes.
2395
2396         The calling code is responsible for freeing the resulting string by calling free().
2397 */
2398 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2399         growing_buffer* val_buf = buffer_init( 32 );
2400         const char* numtype = get_datatype( field );
2401
2402         // For historical reasons the following contains cruft that could be cleaned up.
2403         if( !strncmp( numtype, "INT", 3 ) ) {
2404                 if( value->type == JSON_NUMBER )
2405                         //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2406                         buffer_fadd( val_buf, jsonObjectGetString( value ) );
2407                 else {
2408                         buffer_fadd( val_buf, jsonObjectGetString( value ) );
2409                 }
2410
2411         } else if( !strcmp( numtype, "NUMERIC" )) {
2412                 if( value->type == JSON_NUMBER )
2413                         buffer_fadd( val_buf, jsonObjectGetString( value ));
2414                 else {
2415                         buffer_fadd( val_buf, jsonObjectGetString( value ));
2416                 }
2417
2418         } else {
2419                 // Presumably this was really intended to be a string, so quote it
2420                 char* str = jsonObjectToSimpleString( value );
2421                 if( dbi_conn_quote_string( dbhandle, &str )) {
2422                         OSRF_BUFFER_ADD( val_buf, str );
2423                         free( str );
2424                 } else {
2425                         osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2426                         free( str );
2427                         buffer_free( val_buf );
2428                         return NULL;
2429                 }
2430         }
2431
2432         return buffer_release( val_buf );
2433 }
2434
2435 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2436                 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2437         growing_buffer* sql_buf = buffer_init( 32 );
2438
2439         buffer_fadd(
2440                 sql_buf,
2441                 "\"%s\".%s ",
2442                 class_alias,
2443                 osrfHashGet( field, "name" )
2444         );
2445
2446         if( !op ) {
2447                 buffer_add( sql_buf, "IN (" );
2448         } else if( !strcasecmp( op,"not in" )) {
2449                 buffer_add( sql_buf, "NOT IN (" );
2450         } else {
2451                 buffer_add( sql_buf, "IN (" );
2452         }
2453
2454         if( node->type == JSON_HASH ) {
2455                 // subquery predicate
2456                 char* subpred = buildQuery( ctx, node, SUBSELECT );
2457                 if( ! subpred ) {
2458                         buffer_free( sql_buf );
2459                         return NULL;
2460                 }
2461
2462                 buffer_add( sql_buf, subpred );
2463                 free( subpred );
2464
2465         } else if( node->type == JSON_ARRAY ) {
2466                 // literal value list
2467                 int in_item_index = 0;
2468                 int in_item_first = 1;
2469                 const jsonObject* in_item;
2470                 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2471
2472                         if( in_item_first )
2473                                 in_item_first = 0;
2474                         else
2475                                 buffer_add( sql_buf, ", " );
2476
2477                         // Sanity check
2478                         if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2479                                 osrfLogError( OSRF_LOG_MARK,
2480                                                 "%s: Expected string or number within IN list; found %s",
2481                                                 modulename, json_type( in_item->type ) );
2482                                 buffer_free( sql_buf );
2483                                 return NULL;
2484                         }
2485
2486                         // Append the literal value -- quoted if not a number
2487                         if( JSON_NUMBER == in_item->type ) {
2488                                 char* val = jsonNumberToDBString( field, in_item );
2489                                 OSRF_BUFFER_ADD( sql_buf, val );
2490                                 free( val );
2491
2492                         } else if( !strcmp( get_primitive( field ), "number" )) {
2493                                 char* val = jsonNumberToDBString( field, in_item );
2494                                 OSRF_BUFFER_ADD( sql_buf, val );
2495                                 free( val );
2496
2497                         } else {
2498                                 char* key_string = jsonObjectToSimpleString( in_item );
2499                                 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2500                                         OSRF_BUFFER_ADD( sql_buf, key_string );
2501                                         free( key_string );
2502                                 } else {
2503                                         osrfLogError( OSRF_LOG_MARK,
2504                                                         "%s: Error quoting key string [%s]", modulename, key_string );
2505                                         free( key_string );
2506                                         buffer_free( sql_buf );
2507                                         return NULL;
2508                                 }
2509                         }
2510                 }
2511
2512                 if( in_item_first ) {
2513                         osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2514                         buffer_free( sql_buf );
2515                         return NULL;
2516                 }
2517         } else {
2518                 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2519                         modulename, json_type( node->type ));
2520                 buffer_free( sql_buf );
2521                 return NULL;
2522         }
2523
2524         OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2525
2526         return buffer_release( sql_buf );
2527 }
2528
2529 // Receive a JSON_ARRAY representing a function call.  The first
2530 // entry in the array is the function name.  The rest are parameters.
2531 static char* searchValueTransform( const jsonObject* array ) {
2532
2533         if( array->size < 1 ) {
2534                 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2535                 return NULL;
2536         }
2537
2538         // Get the function name
2539         jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2540         if( func_item->type != JSON_STRING ) {
2541                 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2542                         modulename, json_type( func_item->type ));
2543                 return NULL;
2544         }
2545
2546         growing_buffer* sql_buf = buffer_init( 32 );
2547
2548         OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2549         OSRF_BUFFER_ADD( sql_buf, "( " );
2550
2551         // Get the parameters
2552         int func_item_index = 1;   // We already grabbed the zeroth entry
2553         while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2554
2555                 // Add a separator comma, if we need one
2556                 if( func_item_index > 2 )
2557                         buffer_add( sql_buf, ", " );
2558
2559                 // Add the current parameter
2560                 if( func_item->type == JSON_NULL ) {
2561                         buffer_add( sql_buf, "NULL" );
2562                 } else {
2563                         char* val = jsonObjectToSimpleString( func_item );
2564                         if( dbi_conn_quote_string( dbhandle, &val )) {
2565                                 OSRF_BUFFER_ADD( sql_buf, val );
2566                                 free( val );
2567                         } else {
2568                                 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2569                                         modulename, val );
2570                                 buffer_free( sql_buf );
2571                                 free( val );
2572                                 return NULL;
2573                         }
2574                 }
2575         }
2576
2577         buffer_add( sql_buf, " )" );
2578
2579         return buffer_release( sql_buf );
2580 }
2581
2582 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2583                 const jsonObject* node, const char* op ) {
2584
2585         if( ! is_good_operator( op ) ) {
2586                 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2587                 return NULL;
2588         }
2589
2590         char* val = searchValueTransform( node );
2591         if( !val )
2592                 return NULL;
2593
2594         growing_buffer* sql_buf = buffer_init( 32 );
2595         buffer_fadd(
2596                 sql_buf,
2597                 "\"%s\".%s %s %s",
2598                 class_alias,
2599                 osrfHashGet( field, "name" ),
2600                 op,
2601                 val
2602         );
2603
2604         free( val );
2605
2606         return buffer_release( sql_buf );
2607 }
2608
2609 // class_alias is a class name or other table alias
2610 // field is a field definition as stored in the IDL
2611 // node comes from the method parameter, and may represent an entry in the SELECT list
2612 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2613                 const jsonObject* node ) {
2614         growing_buffer* sql_buf = buffer_init( 32 );
2615
2616         const char* field_transform = jsonObjectGetString(
2617                 jsonObjectGetKeyConst( node, "transform" ) );
2618         const char* transform_subcolumn = jsonObjectGetString(
2619                 jsonObjectGetKeyConst( node, "result_field" ) );
2620
2621         if( transform_subcolumn ) {
2622                 if( ! is_identifier( transform_subcolumn ) ) {
2623                         osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2624                                         modulename, transform_subcolumn );
2625                         buffer_free( sql_buf );
2626                         return NULL;
2627                 }
2628                 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' );    // enclose transform in parentheses
2629         }
2630
2631         if( field_transform ) {
2632
2633                 if( ! is_identifier( field_transform ) ) {
2634                         osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2635                                         modulename, field_transform );
2636                         buffer_free( sql_buf );
2637                         return NULL;
2638                 }
2639
2640                 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2641                         buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2642                                 field_transform, class_alias, osrfHashGet( field, "name" ));
2643                 } else {
2644                         buffer_fadd( sql_buf, "%s(\"%s\".%s",
2645                                 field_transform, class_alias, osrfHashGet( field, "name" ));
2646                 }
2647
2648                 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2649
2650                 if( array ) {
2651                         if( array->type != JSON_ARRAY ) {
2652                                 osrfLogError( OSRF_LOG_MARK,
2653                                         "%s: Expected JSON_ARRAY for function params; found %s",
2654                                         modulename, json_type( array->type ) );
2655                                 buffer_free( sql_buf );
2656                                 return NULL;
2657                         }
2658                         int func_item_index = 0;
2659                         jsonObject* func_item;
2660                         while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2661
2662                                 char* val = jsonObjectToSimpleString( func_item );
2663
2664                                 if( !val ) {
2665                                         buffer_add( sql_buf, ",NULL" );
2666                                 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2667                                         OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2668                                         OSRF_BUFFER_ADD( sql_buf, val );
2669                                 } else {
2670                                         osrfLogError( OSRF_LOG_MARK,
2671                                                         "%s: Error quoting key string [%s]", modulename, val );
2672                                         free( val );
2673                                         buffer_free( sql_buf );
2674                                         return NULL;
2675                                 }
2676                                 free( val );
2677                         }
2678                 }
2679
2680                 buffer_add( sql_buf, " )" );
2681
2682         } else {
2683                 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2684         }
2685
2686         if( transform_subcolumn )
2687                 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2688
2689         return buffer_release( sql_buf );
2690 }
2691
2692 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2693                 const jsonObject* node, const char* op ) {
2694
2695         if( ! is_good_operator( op ) ) {
2696                 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2697                 return NULL;
2698         }
2699
2700         char* field_transform = searchFieldTransform( class_info->alias, field, node );
2701         if( ! field_transform )
2702                 return NULL;
2703         char* value = NULL;
2704         int extra_parens = 0;   // boolean
2705
2706         const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2707         if( ! value_obj ) {
2708                 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2709                 if( !value ) {
2710                         osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2711                                 modulename );
2712                         free( field_transform );
2713                         return NULL;
2714                 }
2715                 extra_parens = 1;
2716         } else if( value_obj->type == JSON_ARRAY ) {
2717                 value = searchValueTransform( value_obj );
2718                 if( !value ) {
2719                         osrfLogError( OSRF_LOG_MARK,
2720                                 "%s: Error building value transform for field transform", modulename );
2721                         free( field_transform );
2722                         return NULL;
2723                 }
2724         } else if( value_obj->type == JSON_HASH ) {
2725                 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2726                 if( !value ) {
2727                         osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2728                                 modulename );
2729                         free( field_transform );
2730                         return NULL;
2731                 }
2732                 extra_parens = 1;
2733         } else if( value_obj->type == JSON_NUMBER ) {
2734                 value = jsonNumberToDBString( field, value_obj );
2735         } else if( value_obj->type == JSON_NULL ) {
2736                 osrfLogError( OSRF_LOG_MARK,
2737                         "%s: Error building predicate for field transform: null value", modulename );
2738                 free( field_transform );
2739                 return NULL;
2740         } else if( value_obj->type == JSON_BOOL ) {
2741                 osrfLogError( OSRF_LOG_MARK,
2742                         "%s: Error building predicate for field transform: boolean value", modulename );
2743                 free( field_transform );
2744                 return NULL;
2745         } else {
2746                 if( !strcmp( get_primitive( field ), "number") ) {
2747                         value = jsonNumberToDBString( field, value_obj );
2748                 } else {
2749                         value = jsonObjectToSimpleString( value_obj );
2750                         if( !dbi_conn_quote_string( dbhandle, &value )) {
2751                                 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2752                                         modulename, value );
2753                                 free( value );
2754                                 free( field_transform );
2755                                 return NULL;
2756                         }
2757                 }
2758         }
2759
2760         const char* left_parens  = "";
2761         const char* right_parens = "";
2762
2763         if( extra_parens ) {
2764                 left_parens  = "(";
2765                 right_parens = ")";
2766         }
2767
2768         growing_buffer* sql_buf = buffer_init( 32 );
2769
2770         buffer_fadd(
2771                 sql_buf,
2772                 "%s%s %s %s %s %s%s",
2773                 left_parens,
2774                 field_transform,
2775                 op,
2776                 left_parens,
2777                 value,
2778                 right_parens,
2779                 right_parens
2780         );
2781
2782         free( value );
2783         free( field_transform );
2784
2785         return buffer_release( sql_buf );
2786 }
2787
2788 static char* searchSimplePredicate( const char* op, const char* class_alias,
2789                 osrfHash* field, const jsonObject* node ) {
2790
2791         if( ! is_good_operator( op ) ) {
2792                 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2793                 return NULL;
2794         }
2795
2796         char* val = NULL;
2797
2798         // Get the value to which we are comparing the specified column
2799         if( node->type != JSON_NULL ) {
2800                 if( node->type == JSON_NUMBER ) {
2801                         val = jsonNumberToDBString( field, node );
2802                 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2803                         val = jsonNumberToDBString( field, node );
2804                 } else {
2805                         val = jsonObjectToSimpleString( node );
2806                 }
2807         }
2808
2809         if( val ) {
2810                 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2811                         // Value is not numeric; enclose it in quotes
2812                         if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2813                                 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2814                                         modulename, val );
2815                                 free( val );
2816                                 return NULL;
2817                         }
2818                 }
2819         } else {
2820                 // Compare to a null value
2821                 val = strdup( "NULL" );
2822                 if( strcmp( op, "=" ))
2823                         op = "IS NOT";
2824                 else
2825                         op = "IS";
2826         }
2827
2828         growing_buffer* sql_buf = buffer_init( 32 );
2829         buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2830         char* pred = buffer_release( sql_buf );
2831
2832         free( val );
2833
2834         return pred;
2835 }
2836
2837 static char* searchBETWEENPredicate( const char* class_alias,
2838                 osrfHash* field, const jsonObject* node ) {
2839
2840         const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2841         const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2842
2843         if( NULL == y_node ) {
2844                 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2845                 return NULL;
2846         }
2847         else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2848                 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2849                 return NULL;
2850         }
2851
2852         char* x_string;
2853         char* y_string;
2854
2855         if( !strcmp( get_primitive( field ), "number") ) {
2856                 x_string = jsonNumberToDBString( field, x_node );
2857                 y_string = jsonNumberToDBString( field, y_node );
2858
2859         } else {
2860                 x_string = jsonObjectToSimpleString( x_node );
2861                 y_string = jsonObjectToSimpleString( y_node );
2862                 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2863                         && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2864                         osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2865                                         modulename, x_string, y_string );
2866                         free( x_string );
2867                         free( y_string );
2868                         return NULL;
2869                 }
2870         }
2871
2872         growing_buffer* sql_buf = buffer_init( 32 );
2873         buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2874                         class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2875         free( x_string );
2876         free( y_string );
2877
2878         return buffer_release( sql_buf );
2879 }
2880
2881 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2882                                                           jsonObject* node, osrfMethodContext* ctx ) {
2883
2884         char* pred = NULL;
2885         if( node->type == JSON_ARRAY ) { // equality IN search
2886                 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2887         } else if( node->type == JSON_HASH ) { // other search
2888                 jsonIterator* pred_itr = jsonNewIterator( node );
2889                 if( !jsonIteratorHasNext( pred_itr ) ) {
2890                         osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2891                                         modulename, osrfHashGet(field, "name" ));
2892                 } else {
2893                         jsonObject* pred_node = jsonIteratorNext( pred_itr );
2894
2895                         // Verify that there are no additional predicates
2896                         if( jsonIteratorHasNext( pred_itr ) ) {
2897                                 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2898                                                 modulename, osrfHashGet(field, "name" ));
2899                         } else if( !(strcasecmp( pred_itr->key,"between" )) )
2900                                 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2901                         else if( !(strcasecmp( pred_itr->key,"in" ))
2902                                         || !(strcasecmp( pred_itr->key,"not in" )) )
2903                                 pred = searchINPredicate(
2904                                         class_info->alias, field, pred_node, pred_itr->key, ctx );
2905                         else if( pred_node->type == JSON_ARRAY )
2906                                 pred = searchFunctionPredicate(
2907                                         class_info->alias, field, pred_node, pred_itr->key );
2908                         else if( pred_node->type == JSON_HASH )
2909                                 pred = searchFieldTransformPredicate(
2910                                         class_info, field, pred_node, pred_itr->key );
2911                         else
2912                                 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2913                 }
2914                 jsonIteratorFree( pred_itr );
2915
2916         } else if( node->type == JSON_NULL ) { // IS NULL search
2917                 growing_buffer* _p = buffer_init( 64 );
2918                 buffer_fadd(
2919                         _p,
2920                         "\"%s\".%s IS NULL",
2921                         class_info->class_name,
2922                         osrfHashGet( field, "name" )
2923                 );
2924                 pred = buffer_release( _p );
2925         } else { // equality search
2926                 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2927         }
2928
2929         return pred;
2930
2931 }
2932
2933
2934 /*
2935
2936 join : {
2937         acn : {
2938                 field : record,
2939                 fkey : id
2940                 type : left
2941                 filter_op : or
2942                 filter : { ... },
2943                 join : {
2944                         acp : {
2945                                 field : call_number,
2946                                 fkey : id,
2947                                 filter : { ... },
2948                         },
2949                 },
2950         },
2951         mrd : {
2952                 field : record,
2953                 type : inner
2954                 fkey : id,
2955                 filter : { ... },
2956         }
2957 }
2958
2959 */
2960
2961 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2962
2963         const jsonObject* working_hash;
2964         jsonObject* freeable_hash = NULL;
2965
2966         if( join_hash->type == JSON_HASH ) {
2967                 working_hash = join_hash;
2968         } else if( join_hash->type == JSON_STRING ) {
2969                 // turn it into a JSON_HASH by creating a wrapper
2970                 // around a copy of the original
2971                 const char* _tmp = jsonObjectGetString( join_hash );
2972                 freeable_hash = jsonNewObjectType( JSON_HASH );
2973                 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2974                 working_hash = freeable_hash;
2975         } else {
2976                 osrfLogError(
2977                         OSRF_LOG_MARK,
2978                         "%s: JOIN failed; expected JSON object type not found",
2979                         modulename
2980                 );
2981                 return NULL;
2982         }
2983
2984         growing_buffer* join_buf = buffer_init( 128 );
2985         const char* leftclass = left_info->class_name;
2986
2987         jsonObject* snode = NULL;
2988         jsonIterator* search_itr = jsonNewIterator( working_hash );
2989
2990         while ( (snode = jsonIteratorNext( search_itr )) ) {
2991                 const char* right_alias = search_itr->key;
2992                 const char* class =
2993                                 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2994                 if( ! class )
2995                         class = right_alias;
2996
2997                 const ClassInfo* right_info = add_joined_class( right_alias, class );
2998                 if( !right_info ) {
2999                         osrfLogError(
3000                                 OSRF_LOG_MARK,
3001                                 "%s: JOIN failed.  Class \"%s\" not resolved in IDL",
3002                                 modulename,
3003                                 search_itr->key
3004                         );
3005                         jsonIteratorFree( search_itr );
3006                         buffer_free( join_buf );
3007                         if( freeable_hash )
3008                                 jsonObjectFree( freeable_hash );
3009                         return NULL;
3010                 }
3011                 osrfHash* links    = right_info->links;
3012                 const char* table  = right_info->source_def;
3013
3014                 const char* fkey  = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3015                 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3016
3017                 if( field && !fkey ) {
3018                         // Look up the corresponding join column in the IDL.
3019                         // The link must be defined in the child table,
3020                         // and point to the right parent table.
3021                         osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3022                         const char* reltype = NULL;
3023                         const char* other_class = NULL;
3024                         reltype = osrfHashGet( idl_link, "reltype" );
3025                         if( reltype && strcmp( reltype, "has_many" ) )
3026                                 other_class = osrfHashGet( idl_link, "class" );
3027                         if( other_class && !strcmp( other_class, leftclass ) )
3028                                 fkey = osrfHashGet( idl_link, "key" );
3029                         if( !fkey ) {
3030                                 osrfLogError(
3031                                         OSRF_LOG_MARK,
3032                                         "%s: JOIN failed.  No link defined from %s.%s to %s",
3033                                         modulename,
3034                                         class,
3035                                         field,
3036                                         leftclass
3037                                 );
3038                                 buffer_free( join_buf );
3039                                 if( freeable_hash )
3040                                         jsonObjectFree( freeable_hash );
3041                                 jsonIteratorFree( search_itr );
3042                                 return NULL;
3043                         }
3044
3045                 } else if( !field && fkey ) {
3046                         // Look up the corresponding join column in the IDL.
3047                         // The link must be defined in the child table,
3048                         // and point to the right parent table.
3049                         osrfHash* left_links = left_info->links;
3050                         osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3051                         const char* reltype = NULL;
3052                         const char* other_class = NULL;
3053                         reltype = osrfHashGet( idl_link, "reltype" );
3054                         if( reltype && strcmp( reltype, "has_many" ) )
3055                                 other_class = osrfHashGet( idl_link, "class" );
3056                         if( other_class && !strcmp( other_class, class ) )
3057                                 field = osrfHashGet( idl_link, "key" );
3058                         if( !field ) {
3059                                 osrfLogError(
3060                                         OSRF_LOG_MARK,
3061                                         "%s: JOIN failed.  No link defined from %s.%s to %s",
3062                                         modulename,
3063                                         leftclass,
3064                                         fkey,
3065                                         class
3066                                 );
3067                                 buffer_free( join_buf );
3068                                 if( freeable_hash )
3069                                         jsonObjectFree( freeable_hash );
3070                                 jsonIteratorFree( search_itr );
3071                                 return NULL;
3072                         }
3073
3074                 } else if( !field && !fkey ) {
3075                         osrfHash* left_links = left_info->links;
3076
3077                         // For each link defined for the left class:
3078                         // see if the link references the joined class
3079                         osrfHashIterator* itr = osrfNewHashIterator( left_links );
3080                         osrfHash* curr_link = NULL;
3081                         while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3082                                 const char* other_class = osrfHashGet( curr_link, "class" );
3083                                 if( other_class && !strcmp( other_class, class ) ) {
3084
3085                                         // In the IDL, the parent class doesn't always know then names of the child
3086                                         // columns that are pointing to it, so don't use that end of the link
3087                                         const char* reltype = osrfHashGet( curr_link, "reltype" );
3088                                         if( reltype && strcmp( reltype, "has_many" ) ) {
3089                                                 // Found a link between the classes
3090                                                 fkey = osrfHashIteratorKey( itr );
3091                                                 field = osrfHashGet( curr_link, "key" );
3092                                                 break;
3093                                         }
3094                                 }
3095                         }
3096                         osrfHashIteratorFree( itr );
3097
3098                         if( !field || !fkey ) {
3099                                 // Do another such search, with the classes reversed
3100
3101                                 // For each link defined for the joined class:
3102                                 // see if the link references the left class
3103                                 osrfHashIterator* itr = osrfNewHashIterator( links );
3104                                 osrfHash* curr_link = NULL;
3105                                 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3106                                         const char* other_class = osrfHashGet( curr_link, "class" );
3107                                         if( other_class && !strcmp( other_class, leftclass ) ) {
3108
3109                                                 // In the IDL, the parent class doesn't know then names of the child
3110                                                 // columns that are pointing to it, so don't use that end of the link
3111                                                 const char* reltype = osrfHashGet( curr_link, "reltype" );
3112                                                 if( reltype && strcmp( reltype, "has_many" ) ) {
3113                                                         // Found a link between the classes
3114                                                         field = osrfHashIteratorKey( itr );
3115                                                         fkey = osrfHashGet( curr_link, "key" );
3116                                                         break;
3117                                                 }
3118                                         }
3119                                 }
3120                                 osrfHashIteratorFree( itr );
3121                         }
3122
3123                         if( !field || !fkey ) {
3124                                 osrfLogError(
3125                                         OSRF_LOG_MARK,
3126                                         "%s: JOIN failed.  No link defined between %s and %s",
3127                                         modulename,
3128                                         leftclass,
3129                                         class
3130                                 );
3131                                 buffer_free( join_buf );
3132                                 if( freeable_hash )
3133                                         jsonObjectFree( freeable_hash );
3134                                 jsonIteratorFree( search_itr );
3135                                 return NULL;
3136                         }
3137                 }
3138
3139                 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3140                 if( type ) {
3141                         if( !strcasecmp( type,"left" )) {
3142                                 buffer_add( join_buf, " LEFT JOIN" );
3143                         } else if( !strcasecmp( type,"right" )) {
3144                                 buffer_add( join_buf, " RIGHT JOIN" );
3145                         } else if( !strcasecmp( type,"full" )) {
3146                                 buffer_add( join_buf, " FULL JOIN" );
3147                         } else {
3148                                 buffer_add( join_buf, " INNER JOIN" );
3149                         }
3150                 } else {
3151                         buffer_add( join_buf, " INNER JOIN" );
3152                 }
3153
3154                 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3155                                         table, right_alias, right_alias, field, left_info->alias, fkey );
3156
3157                 // Add any other join conditions as specified by "filter"
3158                 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3159                 if( filter ) {
3160                         const char* filter_op = jsonObjectGetString(
3161                                 jsonObjectGetKeyConst( snode, "filter_op" ) );
3162                         if( filter_op && !strcasecmp( "or",filter_op )) {
3163                                 buffer_add( join_buf, " OR " );
3164                         } else {
3165                                 buffer_add( join_buf, " AND " );
3166                         }
3167
3168                         char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3169                         if( jpred ) {
3170                                 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3171                                 OSRF_BUFFER_ADD( join_buf, jpred );
3172                                 free( jpred );
3173                         } else {
3174                                 osrfLogError(
3175                                         OSRF_LOG_MARK,
3176                                         "%s: JOIN failed.  Invalid conditional expression.",
3177                                         modulename
3178                                 );
3179                                 jsonIteratorFree( search_itr );
3180                                 buffer_free( join_buf );
3181                                 if( freeable_hash )
3182                                         jsonObjectFree( freeable_hash );
3183                                 return NULL;
3184                         }
3185                 }
3186
3187                 buffer_add( join_buf, " ) " );
3188
3189                 // Recursively add a nested join, if one is present
3190                 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3191                 if( join_filter ) {
3192                         char* jpred = searchJOIN( join_filter, right_info );
3193                         if( jpred ) {
3194                                 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3195                                 OSRF_BUFFER_ADD( join_buf, jpred );
3196                                 free( jpred );
3197                         } else {
3198                                 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3199                                 jsonIteratorFree( search_itr );
3200                                 buffer_free( join_buf );
3201                                 if( freeable_hash )
3202                                         jsonObjectFree( freeable_hash );
3203                                 return NULL;
3204                         }
3205                 }
3206         }
3207
3208         if( freeable_hash )
3209                 jsonObjectFree( freeable_hash );
3210         jsonIteratorFree( search_itr );
3211
3212         return buffer_release( join_buf );
3213 }
3214
3215 /*
3216
3217 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3218 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3219 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3220
3221 Generate code to express a set of conditions, as for a WHERE clause.  Parameters:
3222
3223 search_hash is the JSON expression of the conditions.
3224 meta is the class definition from the IDL, for the relevant table.
3225 opjoin_type indicates whether multiple conditions, if present, should be
3226         connected by AND or OR.
3227 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3228         to pass it to other functions -- and all they do with it is to use the session
3229         and request members to send error messages back to the client.
3230
3231 */
3232
3233 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3234                 int opjoin_type, osrfMethodContext* ctx ) {
3235
3236         osrfLogDebug(
3237                 OSRF_LOG_MARK,
3238                 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3239                 "opjoin_type = %d, ctx addr = %p",
3240                 modulename,
3241                 search_hash,
3242                 class_info->class_def,
3243                 opjoin_type,
3244                 ctx
3245         );
3246
3247         growing_buffer* sql_buf = buffer_init( 128 );
3248
3249         jsonObject* node = NULL;
3250
3251         int first = 1;
3252         if( search_hash->type == JSON_ARRAY ) {
3253                 if( 0 == search_hash->size ) {
3254                         osrfLogError(
3255                                 OSRF_LOG_MARK,
3256                                 "%s: Invalid predicate structure: empty JSON array",
3257                                 modulename
3258                         );
3259                         buffer_free( sql_buf );
3260                         return NULL;
3261                 }
3262
3263                 unsigned long i = 0;
3264                 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3265                         if( first ) {
3266                                 first = 0;
3267                         } else {
3268                                 if( opjoin_type == OR_OP_JOIN )
3269                                         buffer_add( sql_buf, " OR " );
3270                                 else
3271                                         buffer_add( sql_buf, " AND " );
3272                         }
3273
3274                         char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3275                         if( ! subpred ) {
3276                                 buffer_free( sql_buf );
3277                                 return NULL;
3278                         }
3279
3280                         buffer_fadd( sql_buf, "( %s )", subpred );
3281                         free( subpred );
3282                 }
3283
3284         } else if( search_hash->type == JSON_HASH ) {
3285                 osrfLogDebug( OSRF_LOG_MARK,
3286                         "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3287                 jsonIterator* search_itr = jsonNewIterator( search_hash );
3288                 if( !jsonIteratorHasNext( search_itr ) ) {
3289                         osrfLogError(
3290                                 OSRF_LOG_MARK,
3291                                 "%s: Invalid predicate structure: empty JSON object",
3292                                 modulename
3293                         );
3294                         jsonIteratorFree( search_itr );
3295                         buffer_free( sql_buf );
3296                         return NULL;
3297                 }
3298
3299                 while( (node = jsonIteratorNext( search_itr )) ) {
3300
3301                         if( first ) {
3302                                 first = 0;
3303                         } else {
3304                                 if( opjoin_type == OR_OP_JOIN )
3305                                         buffer_add( sql_buf, " OR " );
3306                                 else
3307                                         buffer_add( sql_buf, " AND " );
3308                         }
3309
3310                         if( '+' == search_itr->key[ 0 ] ) {
3311
3312                                 // This plus sign prefixes a class name or other table alias;
3313                                 // make sure the table alias is in scope
3314                                 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3315                                 if( ! alias_info ) {
3316                                         osrfLogError(
3317                                                          OSRF_LOG_MARK,
3318                                                         "%s: Invalid table alias \"%s\" in WHERE clause",
3319                                                         modulename,
3320                                                         search_itr->key + 1
3321                                         );
3322                                         jsonIteratorFree( search_itr );
3323                                         buffer_free( sql_buf );
3324                                         return NULL;
3325                                 }
3326
3327                                 if( node->type == JSON_STRING ) {
3328                                         // It's the name of a column; make sure it belongs to the class
3329                                         const char* fieldname = jsonObjectGetString( node );
3330                                         if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3331                                                 osrfLogError(
3332                                                         OSRF_LOG_MARK,
3333                                                         "%s: Invalid column name \"%s\" in WHERE clause "
3334                                                         "for table alias \"%s\"",
3335                                                         modulename,
3336                                                         fieldname,
3337                                                         alias_info->alias
3338                                                 );
3339                                                 jsonIteratorFree( search_itr );
3340                                                 buffer_free( sql_buf );
3341                                                 return NULL;
3342                                         }
3343
3344                                         buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3345                                 } else {
3346                                         // It's something more complicated
3347                                         char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3348                                         if( ! subpred ) {
3349                                                 jsonIteratorFree( search_itr );
3350                                                 buffer_free( sql_buf );
3351                                                 return NULL;
3352                                         }
3353
3354                                         buffer_fadd( sql_buf, "( %s )", subpred );
3355                                         free( subpred );
3356                                 }
3357                         } else if( '-' == search_itr->key[ 0 ] ) {
3358                                 if( !strcasecmp( "-or", search_itr->key )) {
3359                                         char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3360                                         if( ! subpred ) {
3361                                                 jsonIteratorFree( search_itr );
3362                                                 buffer_free( sql_buf );
3363                                                 return NULL;
3364                                         }
3365
3366                                         buffer_fadd( sql_buf, "( %s )", subpred );
3367                                         free( subpred );
3368                                 } else if( !strcasecmp( "-and", search_itr->key )) {
3369                                         char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3370                                         if( ! subpred ) {
3371                                                 jsonIteratorFree( search_itr );
3372                                                 buffer_free( sql_buf );
3373                                                 return NULL;
3374                                         }
3375
3376                                         buffer_fadd( sql_buf, "( %s )", subpred );
3377                                         free( subpred );
3378                                 } else if( !strcasecmp("-not",search_itr->key) ) {
3379                                         char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3380                                         if( ! subpred ) {
3381                                                 jsonIteratorFree( search_itr );
3382                                                 buffer_free( sql_buf );
3383                                                 return NULL;
3384                                         }
3385
3386                                         buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3387                                         free( subpred );
3388                                 } else if( !strcasecmp( "-exists", search_itr->key )) {
3389                                         char* subpred = buildQuery( ctx, node, SUBSELECT );
3390                                         if( ! subpred ) {
3391                                                 jsonIteratorFree( search_itr );
3392                                                 buffer_free( sql_buf );
3393                                                 return NULL;
3394                                         }
3395
3396                                         buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3397                                         free( subpred );
3398                                 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3399                                         char* subpred = buildQuery( ctx, node, SUBSELECT );
3400                                         if( ! subpred ) {
3401                                                 jsonIteratorFree( search_itr );
3402                                                 buffer_free( sql_buf );
3403                                                 return NULL;
3404                                         }
3405
3406                                         buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3407                                         free( subpred );
3408                                 } else {     // Invalid "minus" operator
3409                                         osrfLogError(
3410                                                          OSRF_LOG_MARK,
3411                                                         "%s: Invalid operator \"%s\" in WHERE clause",
3412                                                         modulename,
3413                                                         search_itr->key
3414                                         );
3415                                         jsonIteratorFree( search_itr );
3416                                         buffer_free( sql_buf );
3417                                         return NULL;
3418                                 }
3419
3420                         } else {
3421
3422                                 const char* class = class_info->class_name;
3423                                 osrfHash* fields = class_info->fields;
3424                                 osrfHash* field = osrfHashGet( fields, search_itr->key );
3425
3426                                 if( !field ) {
3427                                         const char* table = class_info->source_def;
3428                                         osrfLogError(
3429                                                 OSRF_LOG_MARK,
3430                                                 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3431                                                 modulename,
3432                                                 search_itr->key,
3433                                                 table ? table : "?",
3434                                                 class ? class : "?"
3435                                         );
3436                                         jsonIteratorFree( search_itr );
3437                                         buffer_free( sql_buf );
3438                                         return NULL;
3439                                 }
3440
3441                                 char* subpred = searchPredicate( class_info, field, node, ctx );
3442                                 if( ! subpred ) {
3443                                         buffer_free( sql_buf );
3444                                         jsonIteratorFree( search_itr );
3445                                         return NULL;
3446                                 }
3447
3448                                 buffer_add( sql_buf, subpred );
3449                                 free( subpred );
3450                         }
3451                 }
3452                 jsonIteratorFree( search_itr );
3453
3454         } else {
3455                 // ERROR ... only hash and array allowed at this level
3456                 char* predicate_string = jsonObjectToJSON( search_hash );
3457                 osrfLogError(
3458                         OSRF_LOG_MARK,
3459                         "%s: Invalid predicate structure: %s",
3460                         modulename,
3461                         predicate_string
3462                 );
3463                 buffer_free( sql_buf );
3464                 free( predicate_string );
3465                 return NULL;
3466         }
3467
3468         return buffer_release( sql_buf );
3469 }
3470
3471 /* Build a JSON_ARRAY of field names for a given table alias
3472 */
3473 static jsonObject* defaultSelectList( const char* table_alias ) {
3474
3475         if( ! table_alias )
3476                 table_alias = "";
3477
3478         ClassInfo* class_info = search_all_alias( table_alias );
3479         if( ! class_info ) {
3480                 osrfLogError(
3481                         OSRF_LOG_MARK,
3482                         "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3483                         modulename,
3484                         table_alias
3485                 );
3486                 return NULL;
3487         }
3488
3489         jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3490         osrfHash* field_def = NULL;
3491         osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3492         while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3493                 const char* field_name = osrfHashIteratorKey( field_itr );
3494                 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3495                         jsonObjectPush( array, jsonNewObject( field_name ) );
3496                 }
3497         }
3498         osrfHashIteratorFree( field_itr );
3499
3500         return array;
3501 }
3502
3503 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3504 // The jsonObject must be a JSON_HASH with an single entry for "union",
3505 // "intersect", or "except".  The data associated with this key must be an
3506 // array of hashes, each hash being a query.
3507 // Also allowed but currently ignored: entries for "order_by" and "alias".
3508 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3509         // Sanity check
3510         if( ! combo || combo->type != JSON_HASH )
3511                 return NULL;      // should be impossible; validated by caller
3512
3513         const jsonObject* query_array = NULL;   // array of subordinate queries
3514         const char* op = NULL;     // name of operator, e.g. UNION
3515         const char* alias = NULL;  // alias for the query (needed for ORDER BY)
3516         int op_count = 0;          // for detecting conflicting operators
3517         int excepting = 0;         // boolean
3518         int all = 0;               // boolean
3519         jsonObject* order_obj = NULL;
3520
3521         // Identify the elements in the hash
3522         jsonIterator* query_itr = jsonNewIterator( combo );
3523         jsonObject* curr_obj = NULL;
3524         while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3525                 if( ! strcmp( "union", query_itr->key ) ) {
3526                         ++op_count;
3527                         op = " UNION ";
3528                         query_array = curr_obj;
3529                 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3530                         ++op_count;
3531                         op = " INTERSECT ";
3532                         query_array = curr_obj;
3533                 } else if( ! strcmp( "except", query_itr->key ) ) {
3534                         ++op_count;
3535                         op = " EXCEPT ";
3536                         excepting = 1;
3537                         query_array = curr_obj;
3538                 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3539                         osrfLogWarning(
3540                                 OSRF_LOG_MARK,
3541                                 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3542                                 modulename
3543                         );
3544                         order_obj = curr_obj;
3545                 } else if( ! strcmp( "alias", query_itr->key ) ) {
3546                         if( curr_obj->type != JSON_STRING ) {
3547                                 jsonIteratorFree( query_itr );
3548                                 return NULL;
3549                         }
3550                         alias = jsonObjectGetString( curr_obj );
3551                 } else if( ! strcmp( "all", query_itr->key ) ) {
3552                         if( obj_is_true( curr_obj ) )
3553                                 all = 1;
3554                 } else {
3555                         if( ctx )
3556                                 osrfAppSessionStatus(
3557                                         ctx->session,
3558                                         OSRF_STATUS_INTERNALSERVERERROR,
3559                                         "osrfMethodException",
3560                                         ctx->request,
3561                                         "Malformed query; unexpected entry in query object"
3562                                 );
3563                         osrfLogError(
3564                                 OSRF_LOG_MARK,
3565                                 "%s: Unexpected entry for \"%s\" in%squery",
3566                                 modulename,
3567                                 query_itr->key,
3568                                 op
3569                         );
3570                         jsonIteratorFree( query_itr );
3571                         return NULL;
3572                 }
3573         }
3574         jsonIteratorFree( query_itr );
3575
3576         // More sanity checks
3577         if( ! query_array ) {
3578                 if( ctx )
3579                         osrfAppSessionStatus(
3580                                 ctx->session,
3581                                 OSRF_STATUS_INTERNALSERVERERROR,
3582                                 "osrfMethodException",
3583                                 ctx->request,
3584                                 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3585                         );
3586                 osrfLogError(
3587                         OSRF_LOG_MARK,
3588                         "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3589                         modulename
3590                 );
3591                 return NULL;        // should be impossible...
3592         } else if( op_count > 1 ) {
3593                 if( ctx )
3594                                 osrfAppSessionStatus(
3595                                 ctx->session,
3596                                 OSRF_STATUS_INTERNALSERVERERROR,
3597                                 "osrfMethodException",
3598                                 ctx->request,
3599                                 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3600                         );
3601                 osrfLogError(
3602                         OSRF_LOG_MARK,
3603                         "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3604                         modulename
3605                 );
3606                 return NULL;
3607         } if( query_array->type != JSON_ARRAY ) {
3608                 if( ctx )
3609                                 osrfAppSessionStatus(
3610                                 ctx->session,
3611                                 OSRF_STATUS_INTERNALSERVERERROR,
3612                                 "osrfMethodException",
3613                                 ctx->request,
3614                                 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3615                         );
3616                 osrfLogError(
3617                         OSRF_LOG_MARK,
3618                         "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3619                         modulename,
3620                         op,
3621                         json_type( query_array->type )
3622                 );
3623                 return NULL;
3624         } if( query_array->size < 2 ) {
3625                 if( ctx )
3626                         osrfAppSessionStatus(
3627                                 ctx->session,
3628                                 OSRF_STATUS_INTERNALSERVERERROR,
3629                                 "osrfMethodException",
3630                                 ctx->request,
3631                                 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3632                         );
3633                 osrfLogError(
3634                         OSRF_LOG_MARK,
3635                         "%s:%srequires multiple queries as operands",
3636                         modulename,
3637                         op
3638                 );
3639                 return NULL;
3640         } else if( excepting && query_array->size > 2 ) {
3641                 if( ctx )
3642                         osrfAppSessionStatus(
3643                                 ctx->session,
3644                                 OSRF_STATUS_INTERNALSERVERERROR,
3645                                 "osrfMethodException",
3646                                 ctx->request,
3647                                 "EXCEPT operator has too many queries as operands"
3648                         );
3649                 osrfLogError(
3650                         OSRF_LOG_MARK,
3651                         "%s:EXCEPT operator has too many queries as operands",
3652                         modulename
3653                 );
3654                 return NULL;
3655         } else if( order_obj && ! alias ) {
3656                 if( ctx )
3657                         osrfAppSessionStatus(
3658                                 ctx->session,
3659                                 OSRF_STATUS_INTERNALSERVERERROR,
3660                                 "osrfMethodException",
3661                                 ctx->request,
3662                                 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3663                         );
3664                 osrfLogError(
3665                         OSRF_LOG_MARK,
3666                         "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3667                         modulename
3668                 );
3669                 return NULL;
3670         }
3671
3672         // So far so good.  Now build the SQL.
3673         growing_buffer* sql = buffer_init( 256 );
3674
3675         // If we nested inside another UNION, INTERSECT, or EXCEPT,
3676         // Add a layer of parentheses
3677         if( flags & SUBCOMBO )
3678                 OSRF_BUFFER_ADD( sql, "( " );
3679
3680         // Traverse the query array.  Each entry should be a hash.
3681         int first = 1;   // boolean
3682         int i = 0;
3683         jsonObject* query = NULL;
3684         while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3685                 if( query->type != JSON_HASH ) {
3686                         if( ctx )
3687                                 osrfAppSessionStatus(
3688                                         ctx->session,
3689                                         OSRF_STATUS_INTERNALSERVERERROR,
3690                                         "osrfMethodException",
3691                                         ctx->request,
3692                                         "Malformed query under UNION, INTERSECT or EXCEPT"
3693                                 );
3694                         osrfLogError(
3695                                 OSRF_LOG_MARK,
3696                                 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3697                                 modulename,
3698                                 op,
3699                                 json_type( query->type )
3700                         );
3701                         buffer_free( sql );
3702                         return NULL;
3703                 }
3704
3705                 if( first )
3706                         first = 0;
3707                 else {
3708                         OSRF_BUFFER_ADD( sql, op );
3709                         if( all )
3710                                 OSRF_BUFFER_ADD( sql, "ALL " );
3711                 }
3712
3713                 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3714                 if( ! query_str ) {
3715                         osrfLogError(
3716                                 OSRF_LOG_MARK,
3717                                 "%s: Error building query under%s",
3718                                 modulename,
3719                                 op
3720                         );
3721                         buffer_free( sql );
3722                         return NULL;
3723                 }
3724
3725                 OSRF_BUFFER_ADD( sql, query_str );
3726         }
3727
3728         if( flags & SUBCOMBO )
3729                 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3730
3731         if( !(flags & SUBSELECT) )
3732                 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3733
3734         return buffer_release( sql );
3735 }
3736
3737 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3738 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3739 // or "except" to indicate the type of query.
3740 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3741         // Sanity checks
3742         if( ! query ) {
3743                 if( ctx )
3744                         osrfAppSessionStatus(
3745                                 ctx->session,
3746                                 OSRF_STATUS_INTERNALSERVERERROR,
3747                                 "osrfMethodException",
3748                                 ctx->request,
3749                                 "Malformed query; no query object"
3750                         );
3751                 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3752                 return NULL;
3753         } else if( query->type != JSON_HASH ) {
3754                 if( ctx )
3755                         osrfAppSessionStatus(
3756                                 ctx->session,
3757                                 OSRF_STATUS_INTERNALSERVERERROR,
3758                                 "osrfMethodException",
3759                                 ctx->request,
3760                                 "Malformed query object"
3761                         );
3762                 osrfLogError(
3763                         OSRF_LOG_MARK,
3764                         "%s: Query object is %s instead of JSON_HASH",
3765                         modulename,
3766                         json_type( query->type )
3767                 );
3768                 return NULL;
3769         }
3770
3771         // Determine what kind of query it purports to be, and dispatch accordingly.
3772         if( jsonObjectGetKeyConst( query, "union" ) ||
3773                 jsonObjectGetKeyConst( query, "intersect" ) ||
3774                 jsonObjectGetKeyConst( query, "except" )) {
3775                 return doCombo( ctx, query, flags );
3776         } else {
3777                 // It is presumably a SELECT query
3778
3779                 // Push a node onto the stack for the current query.  Every level of
3780                 // subquery gets its own QueryFrame on the Stack.
3781                 push_query_frame();
3782
3783                 // Build an SQL SELECT statement
3784                 char* sql = SELECT(
3785                         ctx,
3786                         jsonObjectGetKey( query, "select" ),
3787                         jsonObjectGetKeyConst( query, "from" ),
3788                         jsonObjectGetKeyConst( query, "where" ),
3789                         jsonObjectGetKeyConst( query, "having" ),
3790                         jsonObjectGetKeyConst( query, "order_by" ),
3791                         jsonObjectGetKeyConst( query, "limit" ),
3792                         jsonObjectGetKeyConst( query, "offset" ),
3793                         flags
3794                 );
3795                 pop_query_frame();
3796                 return sql;
3797         }
3798 }
3799
3800 char* SELECT (
3801                 /* method context */ osrfMethodContext* ctx,
3802
3803                 /* SELECT   */ jsonObject* selhash,
3804                 /* FROM     */ const jsonObject* join_hash,
3805                 /* WHERE    */ const jsonObject* search_hash,
3806                 /* HAVING   */ const jsonObject* having_hash,
3807                 /* ORDER BY */ const jsonObject* order_hash,
3808                 /* LIMIT    */ const jsonObject* limit,
3809                 /* OFFSET   */ const jsonObject* offset,
3810                 /* flags    */ int flags
3811 ) {
3812         const char* locale = osrf_message_get_last_locale();
3813
3814         // general tmp objects
3815         const jsonObject* tmp_const;
3816         jsonObject* selclass = NULL;
3817         jsonObject* snode = NULL;
3818         jsonObject* onode = NULL;
3819
3820         char* string = NULL;
3821         int from_function = 0;
3822         int first = 1;
3823         int gfirst = 1;
3824         //int hfirst = 1;
3825
3826         osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3827
3828         // punt if there's no FROM clause
3829         if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3830                 osrfLogError(
3831                         OSRF_LOG_MARK,
3832                         "%s: FROM clause is missing or empty",
3833                         modulename
3834                 );
3835                 if( ctx )
3836                         osrfAppSessionStatus(
3837                                 ctx->session,
3838                                 OSRF_STATUS_INTERNALSERVERERROR,
3839                                 "osrfMethodException",
3840                                 ctx->request,
3841                                 "FROM clause is missing or empty in JSON query"
3842                         );
3843                 return NULL;
3844         }
3845
3846         // the core search class
3847         const char* core_class = NULL;
3848
3849         // get the core class -- the only key of the top level FROM clause, or a string
3850         if( join_hash->type == JSON_HASH ) {
3851                 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3852                 snode = jsonIteratorNext( tmp_itr );
3853
3854                 // Populate the current QueryFrame with information
3855                 // about the core class
3856                 if( add_query_core( NULL, tmp_itr->key ) ) {
3857                         if( ctx )
3858                                 osrfAppSessionStatus(
3859                                         ctx->session,
3860                                         OSRF_STATUS_INTERNALSERVERERROR,
3861                                         "osrfMethodException",
3862                                         ctx->request,
3863                                         "Unable to look up core class"
3864                                 );
3865                         return NULL;
3866                 }
3867                 core_class = curr_query->core.class_name;
3868                 join_hash = snode;
3869
3870                 jsonObject* extra = jsonIteratorNext( tmp_itr );
3871
3872                 jsonIteratorFree( tmp_itr );
3873                 snode = NULL;
3874
3875                 // There shouldn't be more than one entry in join_hash
3876                 if( extra ) {
3877                         osrfLogError(
3878                                 OSRF_LOG_MARK,
3879                                 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3880                                 modulename
3881                         );
3882                         if( ctx )
3883                                 osrfAppSessionStatus(
3884                                         ctx->session,
3885                                         OSRF_STATUS_INTERNALSERVERERROR,
3886                                         "osrfMethodException",
3887                                         ctx->request,
3888                                         "Malformed FROM clause in JSON query"
3889                                 );
3890                         return NULL;    // Malformed join_hash; extra entry
3891                 }
3892         } else if( join_hash->type == JSON_ARRAY ) {
3893                 // We're selecting from a function, not from a table
3894                 from_function = 1;
3895                 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3896                 selhash = NULL;
3897
3898         } else if( join_hash->type == JSON_STRING ) {
3899                 // Populate the current QueryFrame with information
3900                 // about the core class
3901                 core_class = jsonObjectGetString( join_hash );
3902                 join_hash = NULL;
3903                 if( add_query_core( NULL, core_class ) ) {
3904                         if( ctx )
3905                                 osrfAppSessionStatus(
3906                                         ctx->session,
3907                                         OSRF_STATUS_INTERNALSERVERERROR,
3908                                         "osrfMethodException",
3909                                         ctx->request,
3910                                         "Unable to look up core class"
3911                                 );
3912                         return NULL;
3913                 }
3914         }
3915         else {
3916                 osrfLogError(
3917                         OSRF_LOG_MARK,
3918                         "%s: FROM clause is unexpected JSON type: %s",
3919                         modulename,
3920                         json_type( join_hash->type )
3921                 );
3922                 if( ctx )
3923                         osrfAppSessionStatus(
3924                                 ctx->session,
3925                                 OSRF_STATUS_INTERNALSERVERERROR,
3926                                 "osrfMethodException",
3927                                 ctx->request,
3928                                 "Ill-formed FROM clause in JSON query"
3929                         );
3930                 return NULL;
3931         }
3932
3933         // Build the join clause, if any, while filling out the list
3934         // of joined classes in the current QueryFrame.
3935         char* join_clause = NULL;
3936         if( join_hash && ! from_function ) {
3937
3938                 join_clause = searchJOIN( join_hash, &curr_query->core );
3939                 if( ! join_clause ) {
3940                         if( ctx )
3941                                 osrfAppSessionStatus(
3942                                         ctx->session,
3943                                         OSRF_STATUS_INTERNALSERVERERROR,
3944                                         "osrfMethodException",
3945                                         ctx->request,
3946                                         "Unable to construct JOIN clause(s)"
3947                                 );
3948                         return NULL;
3949                 }
3950         }
3951
3952         // For in case we don't get a select list
3953         jsonObject* defaultselhash = NULL;
3954
3955         // if there is no select list, build a default select list ...
3956         if( !selhash && !from_function ) {
3957                 jsonObject* default_list = defaultSelectList( core_class );
3958                 if( ! default_list ) {
3959                         if( ctx ) {
3960                                 osrfAppSessionStatus(
3961                                         ctx->session,
3962                                         OSRF_STATUS_INTERNALSERVERERROR,
3963                                         "osrfMethodException",
3964                                         ctx->request,
3965                                         "Unable to build default SELECT clause in JSON query"
3966                                 );
3967                                 free( join_clause );
3968                                 return NULL;
3969                         }
3970                 }
3971
3972                 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
3973                 jsonObjectSetKey( selhash, core_class, default_list );
3974         }
3975
3976         // The SELECT clause can be encoded only by a hash
3977         if( !from_function && selhash->type != JSON_HASH ) {
3978                 osrfLogError(
3979                         OSRF_LOG_MARK,
3980                         "%s: Expected JSON_HASH for SELECT clause; found %s",
3981                         modulename,
3982                         json_type( selhash->type )
3983                 );
3984
3985                 if( ctx )
3986                         osrfAppSessionStatus(
3987                                 ctx->session,
3988                                 OSRF_STATUS_INTERNALSERVERERROR,
3989                                 "osrfMethodException",
3990                                 ctx->request,
3991                                 "Malformed SELECT clause in JSON query"
3992                         );
3993                 free( join_clause );
3994                 return NULL;
3995         }
3996
3997         // If you see a null or wild card specifier for the core class, or an
3998         // empty array, replace it with a default SELECT list
3999         tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4000         if( tmp_const ) {
4001                 int default_needed = 0;   // boolean
4002                 if( JSON_STRING == tmp_const->type
4003                         && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4004                                 default_needed = 1;
4005                 else if( JSON_NULL == tmp_const->type )
4006                         default_needed = 1;
4007
4008                 if( default_needed ) {
4009                         // Build a default SELECT list
4010                         jsonObject* default_list = defaultSelectList( core_class );
4011                         if( ! default_list ) {
4012                                 if( ctx ) {
4013                                         osrfAppSessionStatus(
4014                                                 ctx->session,
4015                                                 OSRF_STATUS_INTERNALSERVERERROR,
4016                                                 "osrfMethodException",
4017                                                 ctx->request,
4018                                                 "Can't build default SELECT clause in JSON query"
4019                                         );
4020                                         free( join_clause );
4021                                         return NULL;
4022                                 }
4023                         }
4024
4025                         jsonObjectSetKey( selhash, core_class, default_list );
4026                 }
4027         }
4028
4029         // temp buffers for the SELECT list and GROUP BY clause
4030         growing_buffer* select_buf = buffer_init( 128 );
4031         growing_buffer* group_buf  = buffer_init( 128 );
4032
4033         int aggregate_found = 0;     // boolean
4034
4035         // Build a select list
4036         if( from_function )   // From a function we select everything
4037                 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4038         else {
4039
4040                 // Build the SELECT list as SQL
4041             int sel_pos = 1;
4042             first = 1;
4043             gfirst = 1;
4044             jsonIterator* selclass_itr = jsonNewIterator( selhash );
4045             while ( (selclass = jsonIteratorNext( selclass_itr )) ) {    // For each class
4046
4047                         const char* cname = selclass_itr->key;
4048
4049                         // Make sure the target relation is in the FROM clause.
4050
4051                         // At this point join_hash is a step down from the join_hash we
4052                         // received as a parameter.  If the original was a JSON_STRING,
4053                         // then json_hash is now NULL.  If the original was a JSON_HASH,
4054                         // then json_hash is now the first (and only) entry in it,
4055                         // denoting the core class.  We've already excluded the
4056                         // possibility that the original was a JSON_ARRAY, because in
4057                         // that case from_function would be non-NULL, and we wouldn't
4058                         // be here.
4059
4060                         // If the current table alias isn't in scope, bail out
4061                         ClassInfo* class_info = search_alias( cname );
4062                         if( ! class_info ) {
4063                                 osrfLogError(
4064                                         OSRF_LOG_MARK,
4065                                         "%s: SELECT clause references class not in FROM clause: \"%s\"",
4066                                         modulename,
4067                                         cname
4068                                 );
4069                                 if( ctx )
4070                                         osrfAppSessionStatus(
4071                                                 ctx->session,
4072                                                 OSRF_STATUS_INTERNALSERVERERROR,
4073                                                 "osrfMethodException",
4074                                                 ctx->request,
4075                                                 "Selected class not in FROM clause in JSON query"
4076                                         );
4077                                 jsonIteratorFree( selclass_itr );
4078                                 buffer_free( select_buf );
4079                                 buffer_free( group_buf );
4080                                 if( defaultselhash )
4081                                         jsonObjectFree( defaultselhash );
4082                                 free( join_clause );
4083                                 return NULL;
4084                         }
4085
4086                         if( selclass->type != JSON_ARRAY ) {
4087                                 osrfLogError(
4088                                         OSRF_LOG_MARK,
4089                                         "%s: Malformed SELECT list for class \"%s\"; not an array",
4090                                         modulename,
4091                                         cname
4092                                 );
4093                                 if( ctx )
4094                                         osrfAppSessionStatus(
4095                                                 ctx->session,
4096                                                 OSRF_STATUS_INTERNALSERVERERROR,
4097                                                 "osrfMethodException",
4098                                                 ctx->request,
4099                                                 "Selected class not in FROM clause in JSON query"
4100                                         );
4101
4102                                 jsonIteratorFree( selclass_itr );
4103                                 buffer_free( select_buf );
4104                                 buffer_free( group_buf );
4105                                 if( defaultselhash )
4106                                         jsonObjectFree( defaultselhash );
4107                                 free( join_clause );
4108                                 return NULL;
4109                         }
4110
4111                         // Look up some attributes of the current class
4112                         osrfHash* idlClass        = class_info->class_def;
4113                         osrfHash* class_field_set = class_info->fields;
4114                         const char* class_pkey    = osrfHashGet( idlClass, "primarykey" );
4115                         const char* class_tname   = osrfHashGet( idlClass, "tablename" );
4116
4117                         if( 0 == selclass->size ) {
4118                                 osrfLogWarning(
4119                                         OSRF_LOG_MARK,
4120                                         "%s: No columns selected from \"%s\"",
4121                                         modulename,
4122                                         cname
4123                                 );
4124                         }
4125
4126                         // stitch together the column list for the current table alias...
4127                         unsigned long field_idx = 0;
4128                         jsonObject* selfield = NULL;
4129                         while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4130
4131                                 // If we need a separator comma, add one
4132                                 if( first ) {
4133                                         first = 0;
4134                                 } else {
4135                                         OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4136                                 }
4137
4138                                 // if the field specification is a string, add it to the list
4139                                 if( selfield->type == JSON_STRING ) {
4140
4141                                         // Look up the field in the IDL
4142                                         const char* col_name = jsonObjectGetString( selfield );
4143                                         osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4144                                         if( !field_def ) {
4145                                                 // No such field in current class
4146                                                 osrfLogError(
4147                                                         OSRF_LOG_MARK,
4148                                                         "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4149                                                         modulename,
4150                                                         col_name,
4151                                                         cname
4152                                                 );
4153                                                 if( ctx )
4154                                                         osrfAppSessionStatus(
4155                                                                 ctx->session,
4156                                                                 OSRF_STATUS_INTERNALSERVERERROR,
4157                                                                 "osrfMethodException",
4158                                                                 ctx->request,
4159                                                                 "Selected column not defined in JSON query"
4160                                                         );
4161                                                 jsonIteratorFree( selclass_itr );
4162                                                 buffer_free( select_buf );
4163                                                 buffer_free( group_buf );
4164                                                 if( defaultselhash )
4165                                                         jsonObjectFree( defaultselhash );
4166                                                 free( join_clause );
4167                                                 return NULL;
4168                                         } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4169                                                 // Virtual field not allowed
4170                                                 osrfLogError(
4171                                                         OSRF_LOG_MARK,
4172                                                         "%s: Selected column \"%s\" for class \"%s\" is virtual",
4173                                                         modulename,
4174                                                         col_name,
4175                                                         cname
4176                                                 );
4177                                                 if( ctx )
4178                                                         osrfAppSessionStatus(
4179                                                                 ctx->session,
4180                                                                 OSRF_STATUS_INTERNALSERVERERROR,
4181                                                                 "osrfMethodException",
4182                                                                 ctx->request,
4183                                                                 "Selected column may not be virtual in JSON query"
4184                                                         );
4185                                                 jsonIteratorFree( selclass_itr );
4186                                                 buffer_free( select_buf );
4187                                                 buffer_free( group_buf );
4188                                                 if( defaultselhash )
4189                                                         jsonObjectFree( defaultselhash );
4190                                                 free( join_clause );
4191                                                 return NULL;
4192                                         }
4193
4194                                         if( locale ) {
4195                                                 const char* i18n;
4196                                                 if( flags & DISABLE_I18N )
4197                                                         i18n = NULL;
4198                                                 else
4199                                                         i18n = osrfHashGet( field_def, "i18n" );
4200
4201                                                 if( str_is_true( i18n ) ) {
4202                                                         buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4203                                                                 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4204                                                                 class_tname, cname, col_name, class_pkey,
4205                                                                 cname, class_pkey, locale, col_name );
4206                                                 } else {
4207                                                         buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4208                                                                 cname, col_name, col_name );
4209                                                 }
4210                                         } else {
4211                                                 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4212                                                                 cname, col_name, col_name );
4213                                         }
4214
4215                                 // ... but it could be an object, in which case we check for a Field Transform
4216                                 } else if( selfield->type == JSON_HASH ) {
4217
4218                                         const char* col_name = jsonObjectGetString(
4219                                                         jsonObjectGetKeyConst( selfield, "column" ) );
4220
4221                                         // Get the field definition from the IDL
4222                                         osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4223                                         if( !field_def ) {
4224                                                 // No such field in current class
4225                                                 osrfLogError(
4226                                                         OSRF_LOG_MARK,
4227                                                         "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4228                                                         modulename,
4229                                                         col_name,
4230                                                         cname
4231                                                 );
4232                                                 if( ctx )
4233                                                         osrfAppSessionStatus(
4234                                                                 ctx->session,
4235                                                                 OSRF_STATUS_INTERNALSERVERERROR,
4236                                                                 "osrfMethodException",
4237                                                                 ctx->request,
4238                                                                 "Selected column is not defined in JSON query"
4239                                                         );
4240                                                 jsonIteratorFree( selclass_itr );
4241                                                 buffer_free( select_buf );
4242                                                 buffer_free( group_buf );
4243                                                 if( defaultselhash )
4244                                                         jsonObjectFree( defaultselhash );
4245                                                 free( join_clause );
4246                                                 return NULL;
4247                                         } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4248                                                 // No such field in current class
4249                                                 osrfLogError(
4250                                                         OSRF_LOG_MARK,
4251                                                         "%s: Selected column \"%s\" is virtual for class \"%s\"",
4252                                                         modulename,
4253                                                         col_name,
4254                                                         cname
4255                                                 );
4256                                                 if( ctx )
4257                                                         osrfAppSessionStatus(
4258                                                                 ctx->session,
4259                                                                 OSRF_STATUS_INTERNALSERVERERROR,
4260                                                                 "osrfMethodException",
4261                                                                 ctx->request,
4262                                                                 "Selected column is virtual in JSON query"
4263                                                         );
4264                                                 jsonIteratorFree( selclass_itr );
4265                                                 buffer_free( select_buf );
4266                                                 buffer_free( group_buf );
4267                                                 if( defaultselhash )
4268                                                         jsonObjectFree( defaultselhash );
4269                                                 free( join_clause );
4270                                                 return NULL;
4271                                         }
4272
4273                                         // Decide what to use as a column alias
4274                                         const char* _alias;
4275                                         if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4276                                                 _alias = jsonObjectGetString( tmp_const );
4277                                         } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4278                                                 _alias = jsonObjectGetString( tmp_const );
4279                                         } else {         // Use field name as the alias
4280                                                 _alias = col_name;
4281                                         }
4282
4283                                         if( jsonObjectGetKeyConst( selfield, "transform" )) {
4284                                                 char* transform_str = searchFieldTransform(
4285                                                         class_info->alias, field_def, selfield );
4286                                                 if( transform_str ) {
4287                                                         buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4288                                                         free( transform_str );
4289                                                 } else {
4290                                                         if( ctx )
4291                                                                 osrfAppSessionStatus(
4292                                                                         ctx->session,
4293                                                                         OSRF_STATUS_INTERNALSERVERERROR,
4294                                                                         "osrfMethodException",
4295                                                                         ctx->request,
4296                                                                         "Unable to generate transform function in JSON query"
4297                                                                 );
4298                                                         jsonIteratorFree( selclass_itr );
4299                                                         buffer_free( select_buf );
4300                                                         buffer_free( group_buf );
4301                                                         if( defaultselhash )
4302                                                                 jsonObjectFree( defaultselhash );
4303                                                         free( join_clause );
4304                                                         return NULL;
4305                                                 }
4306                                         } else {
4307
4308                                                 if( locale ) {
4309                                                         const char* i18n;
4310                                                         if( flags & DISABLE_I18N )
4311                                                                 i18n = NULL;
4312                                                         else
4313                                                                 i18n = osrfHashGet( field_def, "i18n" );
4314
4315                                                         if( str_is_true( i18n ) ) {
4316                                                                 buffer_fadd( select_buf,
4317                                                                         " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4318                                                                         "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4319                                                                         class_tname, cname, col_name, class_pkey, cname,
4320                                                                         class_pkey, locale, _alias );
4321                                                         } else {
4322                                                                 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4323                                                                         cname, col_name, _alias );
4324                                                         }
4325                                                 } else {
4326                                                         buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4327                                                                 cname, col_name, _alias );
4328                                                 }
4329                                         }
4330                                 }
4331                                 else {
4332                                         osrfLogError(
4333                                                 OSRF_LOG_MARK,
4334                                                 "%s: Selected item is unexpected JSON type: %s",
4335                                                 modulename,
4336                                                 json_type( selfield->type )
4337                                         );
4338                                         if( ctx )
4339                                                 osrfAppSessionStatus(
4340                                                         ctx->session,
4341                                                         OSRF_STATUS_INTERNALSERVERERROR,
4342                                                         "osrfMethodException",
4343                                                         ctx->request,
4344                                                         "Ill-formed SELECT item in JSON query"
4345                                                 );
4346                                         jsonIteratorFree( selclass_itr );
4347                                         buffer_free( select_buf );
4348                                         buffer_free( group_buf );
4349                                         if( defaultselhash )
4350                                                 jsonObjectFree( defaultselhash );
4351                                         free( join_clause );
4352                                         return NULL;
4353                                 }
4354
4355                                 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4356                                 if( obj_is_true( agg_obj ) )
4357                                         aggregate_found = 1;
4358                                 else {
4359                                         // Append a comma (except for the first one)
4360                                         // and add the column to a GROUP BY clause
4361                                         if( gfirst )
4362                                                 gfirst = 0;
4363                                         else
4364                                                 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4365
4366                                         buffer_fadd( group_buf, " %d", sel_pos );
4367                                 }
4368
4369 #if 0
4370                             if (is_agg->size || (flags & SELECT_DISTINCT)) {
4371
4372                                         const jsonObject* aggregate_obj = jsonObjectGetKeyConst( elfield, "aggregate");
4373                                     if ( ! obj_is_true( aggregate_obj ) ) {
4374                                             if (gfirst) {
4375                                                     gfirst = 0;
4376                                             } else {
4377                                                         OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4378                                             }
4379
4380                                             buffer_fadd(group_buf, " %d", sel_pos);
4381
4382                                         /*
4383                                     } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4384                                             if (gfirst) {
4385                                                     gfirst = 0;
4386                                             } else {
4387                                                         OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4388                                             }
4389
4390                                             _column = searchFieldTransform(class_info->alias, field, selfield);
4391                                                 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4392                                                 OSRF_BUFFER_ADD(group_buf, _column);
4393                                             _column = searchFieldTransform(class_info->alias, field, selfield);
4394                                         */
4395                                     }
4396                             }
4397 #endif
4398
4399                                 sel_pos++;
4400                         } // end while -- iterating across SELECT columns
4401
4402                 } // end while -- iterating across classes
4403
4404                 jsonIteratorFree( selclass_itr );
4405         }
4406
4407         char* col_list = buffer_release( select_buf );
4408
4409         // Make sure the SELECT list isn't empty.  This can happen, for example,
4410         // if we try to build a default SELECT clause from a non-core table.
4411
4412         if( ! *col_list ) {
4413                 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4414                 if( ctx )
4415                         osrfAppSessionStatus(
4416                                 ctx->session,
4417                                 OSRF_STATUS_INTERNALSERVERERROR,
4418                                 "osrfMethodException",
4419                                 ctx->request,
4420                                 "SELECT list is empty"
4421                 );
4422                 free( col_list );
4423                 buffer_free( group_buf );
4424                 if( defaultselhash )
4425                         jsonObjectFree( defaultselhash );
4426                 free( join_clause );
4427                 return NULL;
4428         }
4429
4430         char* table = NULL;
4431         if( from_function )
4432                 table = searchValueTransform( join_hash );
4433         else
4434                 table = strdup( curr_query->core.source_def );
4435
4436         if( !table ) {
4437                 if( ctx )
4438                         osrfAppSessionStatus(
4439                                 ctx->session,
4440                                 OSRF_STATUS_INTERNALSERVERERROR,
4441                                 "osrfMethodException",
4442                                 ctx->request,
4443                                 "Unable to identify table for core class"
4444                         );
4445                 free( col_list );
4446                 buffer_free( group_buf );
4447                 if( defaultselhash )
4448                         jsonObjectFree( defaultselhash );
4449                 free( join_clause );
4450                 return NULL;
4451         }
4452
4453         // Put it all together
4454         growing_buffer* sql_buf = buffer_init( 128 );
4455         buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4456         free( col_list );
4457         free( table );
4458
4459         // Append the join clause, if any
4460         if( join_clause ) {
4461                 buffer_add(sql_buf, join_clause );
4462                 free( join_clause );
4463         }
4464
4465         char* order_by_list = NULL;
4466         char* having_buf = NULL;
4467
4468         if( !from_function ) {
4469
4470                 // Build a WHERE clause, if there is one
4471                 if( search_hash ) {
4472                         buffer_add( sql_buf, " WHERE " );
4473
4474                         // and it's on the WHERE clause
4475                         char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4476                         if( ! pred ) {
4477                                 if( ctx ) {
4478                                         osrfAppSessionStatus(
4479                                                 ctx->session,
4480                                                 OSRF_STATUS_INTERNALSERVERERROR,
4481                                                 "osrfMethodException",
4482                                                 ctx->request,
4483                                                 "Severe query error in WHERE predicate -- see error log for more details"
4484                                         );
4485                                 }
4486                                 buffer_free( group_buf );
4487                                 buffer_free( sql_buf );
4488                                 if( defaultselhash )
4489                                         jsonObjectFree( defaultselhash );
4490                                 return NULL;
4491                         }
4492
4493                         buffer_add( sql_buf, pred );
4494                         free( pred );
4495                 }
4496
4497                 // Build a HAVING clause, if there is one
4498                 if( having_hash ) {
4499
4500                         // and it's on the the WHERE clause
4501                         having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4502
4503                         if( ! having_buf ) {
4504                                 if( ctx ) {
4505                                                 osrfAppSessionStatus(
4506                                                 ctx->session,
4507                                                 OSRF_STATUS_INTERNALSERVERERROR,
4508                                                 "osrfMethodException",
4509                                                 ctx->request,
4510                                                 "Severe query error in HAVING predicate -- see error log for more details"
4511                                         );
4512                                 }
4513                                 buffer_free( group_buf );
4514                                 buffer_free( sql_buf );
4515                                 if( defaultselhash )
4516                                         jsonObjectFree( defaultselhash );
4517                                 return NULL;
4518                         }
4519                 }
4520
4521                 // Build an ORDER BY clause, if there is one
4522                 if( NULL == order_hash )
4523                         ;  // No ORDER BY? do nothing
4524                 else if( JSON_ARRAY == order_hash->type ) {
4525                         order_by_list = buildOrderByFromArray( ctx, order_hash );
4526                         if( !order_by_list ) {
4527                                 free( having_buf );
4528                                 buffer_free( group_buf );
4529                                 buffer_free( sql_buf );
4530                                 if( defaultselhash )
4531                                         jsonObjectFree( defaultselhash );
4532                                 return NULL;
4533                         }
4534                 } else if( JSON_HASH == order_hash->type ) {
4535                         // This hash is keyed on class alias.  Each class has either
4536                         // an array of field names or a hash keyed on field name.
4537                         growing_buffer* order_buf = NULL;  // to collect ORDER BY list
4538                         jsonIterator* class_itr = jsonNewIterator( order_hash );
4539                         while( (snode = jsonIteratorNext( class_itr )) ) {
4540
4541                                 ClassInfo* order_class_info = search_alias( class_itr->key );
4542                                 if( ! order_class_info ) {
4543                                         osrfLogError( OSRF_LOG_MARK,
4544                                                 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4545                                                 modulename, class_itr->key );
4546                                         if( ctx )
4547                                                 osrfAppSessionStatus(
4548                                                         ctx->session,
4549                                                         OSRF_STATUS_INTERNALSERVERERROR,
4550                                                         "osrfMethodException",
4551                                                         ctx->request,
4552                                                         "Invalid class referenced in ORDER BY clause -- "
4553                                                                 "see error log for more details"
4554                                                 );
4555                                         jsonIteratorFree( class_itr );
4556                                         buffer_free( order_buf );
4557                                         free( having_buf );
4558                                         buffer_free( group_buf );
4559                                         buffer_free( sql_buf );
4560                                         if( defaultselhash )
4561                                                 jsonObjectFree( defaultselhash );
4562                                         return NULL;
4563                                 }
4564
4565                                 osrfHash* field_list_def = order_class_info->fields;
4566
4567                                 if( snode->type == JSON_HASH ) {
4568
4569                                         // Hash is keyed on field names from the current class.  For each field
4570                                         // there is another layer of hash to define the sorting details, if any,
4571                                         // or a string to indicate direction of sorting.
4572                                         jsonIterator* order_itr = jsonNewIterator( snode );
4573                                         while( (onode = jsonIteratorNext( order_itr )) ) {
4574
4575                                                 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4576                                                 if( !field_def ) {
4577                                                         osrfLogError( OSRF_LOG_MARK,
4578                                                                 "%s: Invalid field \"%s\" in ORDER BY clause",
4579                                                                 modulename, order_itr->key );
4580                                                         if( ctx )
4581                                                                 osrfAppSessionStatus(
4582                                                                         ctx->session,
4583                                                                         OSRF_STATUS_INTERNALSERVERERROR,
4584                                                                         "osrfMethodException",
4585                                                                         ctx->request,
4586                                                                         "Invalid field in ORDER BY clause -- "
4587                                                                         "see error log for more details"
4588                                                                 );
4589                                                         jsonIteratorFree( order_itr );
4590                                                         jsonIteratorFree( class_itr );
4591                                                         buffer_free( order_buf );
4592                                                         free( having_buf );
4593                                                         buffer_free( group_buf );
4594                                                         buffer_free( sql_buf );
4595                                                         if( defaultselhash )
4596                                                                 jsonObjectFree( defaultselhash );
4597                                                         return NULL;
4598                                                 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4599                                                         osrfLogError( OSRF_LOG_MARK,
4600                                                                 "%s: Virtual field \"%s\" in ORDER BY clause",
4601                                                                 modulename, order_itr->key );
4602                                                         if( ctx )
4603                                                                 osrfAppSessionStatus(
4604                                                                         ctx->session,
4605                                                                         OSRF_STATUS_INTERNALSERVERERROR,
4606                                                                         "osrfMethodException",
4607                                                                         ctx->request,
4608                                                                         "Virtual field in ORDER BY clause -- "
4609                                                                         "see error log for more details"
4610                                                         );
4611                                                         jsonIteratorFree( order_itr );
4612                                                         jsonIteratorFree( class_itr );
4613                                                         buffer_free( order_buf );
4614                                                         free( having_buf );
4615                                                         buffer_free( group_buf );
4616                                                         buffer_free( sql_buf );
4617                                                         if( defaultselhash )
4618                                                                 jsonObjectFree( defaultselhash );
4619                                                         return NULL;
4620                                                 }
4621
4622                                                 const char* direction = NULL;
4623                                                 if( onode->type == JSON_HASH ) {
4624                                                         if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4625                                                                 string = searchFieldTransform(
4626                                                                         class_itr->key,
4627                                                                         osrfHashGet( field_list_def, order_itr->key ),
4628                                                                         onode
4629                                                                 );
4630                                                                 if( ! string ) {
4631                                                                         if( ctx ) osrfAppSessionStatus(
4632                                                                                 ctx->session,
4633                                                                                 OSRF_STATUS_INTERNALSERVERERROR,
4634                                                                                 "osrfMethodException",
4635                                                                                 ctx->request,
4636                                                                                 "Severe query error in ORDER BY clause -- "
4637                                                                                 "see error log for more details"
4638                                                                         );
4639                                                                         jsonIteratorFree( order_itr );
4640                                                                         jsonIteratorFree( class_itr );
4641                                                                         free( having_buf );
4642                                                                         buffer_free( group_buf );
4643                                                                         buffer_free( order_buf);
4644                                                                         buffer_free( sql_buf );
4645                                                                         if( defaultselhash )
4646                                                                                 jsonObjectFree( defaultselhash );
4647                                                                         return NULL;
4648                                                                 }
4649                                                         } else {
4650                                                                 growing_buffer* field_buf = buffer_init( 16 );
4651                                                                 buffer_fadd( field_buf, "\"%s\".%s",
4652                                                                         class_itr->key, order_itr->key );
4653                                                                 string = buffer_release( field_buf );
4654                                                         }
4655
4656                                                         if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4657                                                                 const char* dir = jsonObjectGetString( tmp_const );
4658                                                                 if(!strncasecmp( dir, "d", 1 )) {
4659                                                                         direction = " DESC";
4660                                                                 } else {
4661                                                                         direction = " ASC";
4662                                                                 }
4663                                                         }
4664
4665                                                 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4666                                                         osrfLogError( OSRF_LOG_MARK,
4667                                                                 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4668                                                                 modulename, json_type( onode->type ) );
4669                                                         if( ctx )
4670                                                                 osrfAppSessionStatus(
4671                                                                         ctx->session,
4672                                                                         OSRF_STATUS_INTERNALSERVERERROR,
4673                                                                         "osrfMethodException",
4674                                                                         ctx->request,
4675                                                                         "Malformed ORDER BY clause -- see error log for more details"
4676                                                                 );
4677                                                         jsonIteratorFree( order_itr );
4678                                                         jsonIteratorFree( class_itr );
4679                                                         free( having_buf );
4680                                                         buffer_free( group_buf );
4681                                                         buffer_free( order_buf );
4682                                                         buffer_free( sql_buf );
4683                                                         if( defaultselhash )
4684                                                                 jsonObjectFree( defaultselhash );
4685                                                         return NULL;
4686
4687                                                 } else {
4688                                                         string = strdup( order_itr->key );
4689                                                         const char* dir = jsonObjectGetString( onode );
4690                                                         if( !strncasecmp( dir, "d", 1 )) {
4691                                                                 direction = " DESC";
4692                                                         } else {
4693                                                                 direction = " ASC";
4694                                                         }
4695                                                 }
4696
4697                                                 if( order_buf )
4698                                                         OSRF_BUFFER_ADD( order_buf, ", " );
4699                                                 else
4700                                                         order_buf = buffer_init( 128 );
4701
4702                                                 OSRF_BUFFER_ADD( order_buf, string );
4703                                                 free( string );
4704
4705                                                 if( direction ) {
4706                                                          OSRF_BUFFER_ADD( order_buf, direction );
4707                                                 }
4708
4709                                         } // end while
4710                                         jsonIteratorFree( order_itr );
4711
4712                                 } else if( snode->type == JSON_ARRAY ) {
4713
4714                                         // Array is a list of fields from the current class
4715                                         unsigned long order_idx = 0;
4716                                         while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4717
4718                                                 const char* _f = jsonObjectGetString( onode );
4719
4720                                                 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4721                                                 if( !field_def ) {
4722                                                         osrfLogError( OSRF_LOG_MARK,
4723                                                                         "%s: Invalid field \"%s\" in ORDER BY clause",
4724                                                                         modulename, _f );
4725                                                         if( ctx )
4726                                                                 osrfAppSessionStatus(
4727                                                                         ctx->session,
4728                                                                         OSRF_STATUS_INTERNALSERVERERROR,
4729                                                                         "osrfMethodException",
4730                                                                         ctx->request,
4731                                                                         "Invalid field in ORDER BY clause -- "
4732                                                                         "see error log for more details"
4733                                                                 );
4734                                                         jsonIteratorFree( class_itr );
4735                                                         buffer_free( order_buf );
4736                                                         free( having_buf );
4737                                                         buffer_free( group_buf );
4738                                                         buffer_free( sql_buf );
4739                                                         if( defaultselhash )
4740                                                                 jsonObjectFree( defaultselhash );
4741                                                         return NULL;
4742                                                 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4743                                                         osrfLogError( OSRF_LOG_MARK,
4744                                                                 "%s: Virtual field \"%s\" in ORDER BY clause",
4745                                                                 modulename, _f );
4746                                                         if( ctx )
4747                                                                 osrfAppSessionStatus(
4748                                                                         ctx->session,
4749                                                                         OSRF_STATUS_INTERNALSERVERERROR,
4750                                                                         "osrfMethodException",
4751                                                                         ctx->request,
4752                                                                         "Virtual field in ORDER BY clause -- "
4753                                                                         "see error log for more details"
4754                                                                 );
4755                                                         jsonIteratorFree( class_itr );
4756                                                         buffer_free( order_buf );
4757                                                         free( having_buf );
4758                                                         buffer_free( group_buf );
4759                                                         buffer_free( sql_buf );
4760                                                         if( defaultselhash )
4761                                                                 jsonObjectFree( defaultselhash );
4762                                                         return NULL;
4763                                                 }
4764
4765                                                 if( order_buf )
4766                                                         OSRF_BUFFER_ADD( order_buf, ", " );
4767                                                 else
4768                                                         order_buf = buffer_init( 128 );
4769
4770                                                 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4771
4772                                         } // end while
4773
4774                                 // IT'S THE OOOOOOOOOOOLD STYLE!
4775                                 } else {
4776                                         osrfLogError( OSRF_LOG_MARK,
4777                                                 "%s: Possible SQL injection attempt; direct order by is not allowed",
4778                                                 modulename );
4779                                         if(ctx) {
4780                                                 osrfAppSessionStatus(
4781                                                         ctx->session,
4782                                                         OSRF_STATUS_INTERNALSERVERERROR,
4783                                                         "osrfMethodException",
4784                                                         ctx->request,
4785                                                         "Severe query error -- see error log for more details"
4786                                                 );
4787                                         }
4788
4789                                         free( having_buf );
4790                                         buffer_free( group_buf );
4791                                         buffer_free( order_buf );
4792                                         buffer_free( sql_buf );
4793                                         if( defaultselhash )
4794                                                 jsonObjectFree( defaultselhash );
4795                                         jsonIteratorFree( class_itr );
4796                                         return NULL;
4797                                 }
4798                         } // end while
4799                         jsonIteratorFree( class_itr );
4800                         if( order_buf )
4801                                 order_by_list = buffer_release( order_buf );
4802                 } else {
4803                         osrfLogError( OSRF_LOG_MARK,
4804                                 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4805                                 modulename, json_type( order_hash->type ) );
4806                         if( ctx )
4807                                 osrfAppSessionStatus(
4808                                         ctx->session,
4809                                         OSRF_STATUS_INTERNALSERVERERROR,
4810                                         "osrfMethodException",
4811                                         ctx->request,
4812                                         "Malformed ORDER BY clause -- see error log for more details"
4813                                 );
4814                         free( having_buf );
4815                         buffer_free( group_buf );
4816                         buffer_free( sql_buf );
4817                         if( defaultselhash )
4818                                 jsonObjectFree( defaultselhash );
4819                         return NULL;
4820                 }
4821         }
4822
4823         string = buffer_release( group_buf );
4824
4825         if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4826                 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4827                 OSRF_BUFFER_ADD( sql_buf, string );
4828         }
4829
4830         free( string );
4831
4832         if( having_buf && *having_buf ) {
4833                 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4834                 OSRF_BUFFER_ADD( sql_buf, having_buf );
4835                 free( having_buf );
4836         }
4837
4838         if( order_by_list ) {
4839
4840                 if( *order_by_list ) {
4841                         OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4842                         OSRF_BUFFER_ADD( sql_buf, order_by_list );
4843                 }
4844
4845                 free( order_by_list );
4846         }
4847
4848         if( limit ){
4849                 const char* str = jsonObjectGetString( limit );
4850                 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4851         }
4852
4853         if( offset ) {
4854                 const char* str = jsonObjectGetString( offset );
4855                 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4856         }
4857
4858         if( !(flags & SUBSELECT) )
4859                 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4860
4861         if( defaultselhash )
4862                  jsonObjectFree( defaultselhash );
4863
4864         return buffer_release( sql_buf );
4865
4866 } // end of SELECT()
4867
4868 /**
4869         @brief Build a list of ORDER BY expressions.
4870         @param ctx Pointer to the method context.
4871         @param order_array Pointer to a JSON_ARRAY of field specifications.
4872         @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
4873         Each expression may be either a column reference or a function call whose first parameter
4874         is a column reference.
4875
4876         Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
4877         It may optionally include entries for "direction" and/or "transform".
4878
4879         The calling code is responsible for freeing the returned string.
4880 */
4881 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
4882         if( ! order_array ) {
4883                 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
4884                         modulename );
4885                 if( ctx )
4886                         osrfAppSessionStatus(
4887                                 ctx->session,
4888                                 OSRF_STATUS_INTERNALSERVERERROR,
4889                                 "osrfMethodException",
4890                                 ctx->request,
4891                                 "Logic error: ORDER BY clause expected, not found; "
4892                                         "see error log for more details"
4893                         );
4894                 return NULL;
4895         } else if( order_array->type != JSON_ARRAY ) {
4896                 osrfLogError( OSRF_LOG_MARK,
4897                         "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
4898                 if( ctx )
4899                         osrfAppSessionStatus(
4900                         ctx->session,
4901                         OSRF_STATUS_INTERNALSERVERERROR,
4902                         "osrfMethodException",
4903                         ctx->request,
4904                         "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
4905                 return NULL;
4906         }
4907
4908         growing_buffer* order_buf = buffer_init( 128 );
4909         int first = 1;        // boolean
4910         int order_idx = 0;
4911         jsonObject* order_spec;
4912         while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
4913
4914                 if( JSON_HASH != order_spec->type ) {
4915                         osrfLogError( OSRF_LOG_MARK,
4916                                 "%s: Malformed field specification in ORDER BY clause; "
4917                                 "expected JSON_HASH, found %s",
4918                                 modulename, json_type( order_spec->type ) );
4919                         if( ctx )
4920                                 osrfAppSessionStatus(
4921                                          ctx->session,
4922                                         OSRF_STATUS_INTERNALSERVERERROR,
4923                                         "osrfMethodException",
4924                                         ctx->request,
4925                                         "Malformed ORDER BY clause -- see error log for more details"
4926                                 );
4927                         buffer_free( order_buf );
4928                         return NULL;
4929                 }
4930
4931                 const char* class_alias =
4932                         jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
4933                 const char* field =
4934                         jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
4935
4936                 if( !field || !class_alias ) {
4937                         osrfLogError( OSRF_LOG_MARK,
4938                                 "%s: Missing class or field name in field specification of ORDER BY clause",
4939                                 modulename );
4940                         if( ctx )
4941                                 osrfAppSessionStatus(
4942                                         ctx->session,
4943                                         OSRF_STATUS_INTERNALSERVERERROR,
4944                                         "osrfMethodException",
4945                                         ctx->request,
4946                                         "Malformed ORDER BY clause -- see error log for more details"
4947                                 );
4948                         buffer_free( order_buf );
4949                         return NULL;
4950                 }
4951
4952                 const ClassInfo* order_class_info = search_alias( class_alias );
4953                 if( ! order_class_info ) {
4954                         osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4955                                 "not in FROM clause, skipping it", modulename, class_alias );
4956                         continue;
4957                 }
4958
4959                 // Add a separating comma, except at the beginning
4960                 if( first )
4961                         first = 0;
4962                 else
4963                         OSRF_BUFFER_ADD( order_buf, ", " );
4964
4965                 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4966                 if( !field_def ) {
4967                         osrfLogError( OSRF_LOG_MARK,
4968                                 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4969                                 modulename, class_alias, field );
4970                         if( ctx )
4971                                 osrfAppSessionStatus(
4972                                         ctx->session,
4973                                         OSRF_STATUS_INTERNALSERVERERROR,
4974                                         "osrfMethodException",
4975                                         ctx->request,
4976                                         "Invalid field referenced in ORDER BY clause -- "
4977                                         "see error log for more details"
4978                                 );
4979                         free( order_buf );
4980                         return NULL;
4981                 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4982                         osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4983                                 modulename, field );
4984                         if( ctx )
4985                                 osrfAppSessionStatus(
4986                                         ctx->session,
4987                                         OSRF_STATUS_INTERNALSERVERERROR,
4988                                         "osrfMethodException",
4989                                         ctx->request,
4990                                         "Virtual field in ORDER BY clause -- see error log for more details"
4991                                 );
4992                         buffer_free( order_buf );
4993                         return NULL;
4994                 }
4995
4996                 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
4997                         char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
4998                         if( ! transform_str ) {
4999                                 if( ctx )
5000                                         osrfAppSessionStatus(
5001                                                 ctx->session,
5002                                                 OSRF_STATUS_INTERNALSERVERERROR,
5003                                                 "osrfMethodException",
5004                                                 ctx->request,
5005                                                 "Severe query error in ORDER BY clause -- "
5006                                                 "see error log for more details"
5007                                         );
5008                                 buffer_free( order_buf );
5009                                 return NULL;
5010                         }
5011
5012                         OSRF_BUFFER_ADD( order_buf, transform_str );
5013                         free( transform_str );
5014                 }
5015                 else
5016                         buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5017
5018                 const char* direction =
5019                         jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5020                 if( direction ) {
5021                         if( direction[ 0 ] || 'D' == direction[ 0 ] )
5022                                 OSRF_BUFFER_ADD( order_buf, " DESC" );
5023                         else
5024                                 OSRF_BUFFER_ADD( order_buf, " ASC" );
5025                 }
5026         }
5027
5028         return buffer_release( order_buf );
5029 }
5030
5031 /**
5032         @brief Build a SELECT statement.
5033         @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5034         @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5035         @param meta Pointer to the class metadata for the core class.
5036         @param ctx Pointer to the method context.
5037         @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5038
5039         Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5040         "order_by", "limit", and "offset".
5041
5042         The SELECT statements built here are distinct from those built for the json_query method.
5043 */
5044 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5045         osrfHash* meta, osrfMethodContext* ctx ) {
5046
5047         const char* locale = osrf_message_get_last_locale();
5048
5049         osrfHash* fields = osrfHashGet( meta, "fields" );
5050         const char* core_class = osrfHashGet( meta, "classname" );
5051
5052         const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5053
5054         jsonObject* selhash = NULL;
5055         jsonObject* defaultselhash = NULL;
5056
5057         growing_buffer* sql_buf = buffer_init( 128 );
5058         growing_buffer* select_buf = buffer_init( 128 );
5059
5060         if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5061                 defaultselhash = jsonNewObjectType( JSON_HASH );
5062                 selhash = defaultselhash;
5063         }
5064
5065         // If there's no SELECT list for the core class, build one
5066         if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5067                 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5068
5069                 // Add every non-virtual field to the field list
5070                 osrfHash* field_def = NULL;
5071                 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5072                 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5073                         if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5074                                 const char* field = osrfHashIteratorKey( field_itr );
5075                                 jsonObjectPush( field_list, jsonNewObject( field ) );
5076                         }
5077                 }
5078                 osrfHashIteratorFree( field_itr );
5079                 jsonObjectSetKey( selhash, core_class, field_list );
5080         }
5081
5082         // Build a list of columns for the SELECT clause
5083         int first = 1;
5084         const jsonObject* snode = NULL;
5085         jsonIterator* class_itr = jsonNewIterator( selhash );
5086         while( (snode = jsonIteratorNext( class_itr )) ) {        // For each class
5087
5088                 // If the class isn't in the IDL, ignore it
5089                 const char* cname = class_itr->key;
5090                 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5091                 if( !idlClass )
5092                         continue;
5093
5094                 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5095                 if( strcmp( core_class, class_itr->key )) {
5096                         if( !join_hash )
5097                                 continue;
5098
5099                         jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5100                         if( !found->size ) {
5101                                 jsonObjectFree( found );
5102                                 continue;
5103                         }
5104
5105                         jsonObjectFree( found );
5106                 }
5107
5108                 const jsonObject* node = NULL;
5109                 jsonIterator* select_itr = jsonNewIterator( snode );
5110                 while( (node = jsonIteratorNext( select_itr )) ) {
5111                         const char* item_str = jsonObjectGetString( node );
5112                         osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5113                         char* fname = osrfHashGet( field, "name" );
5114
5115                         if( !field )
5116                                 continue;
5117
5118                         if( first ) {
5119                                 first = 0;
5120                         } else {
5121                                 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5122                         }
5123
5124                         if( locale ) {
5125                                 const char* i18n;
5126                                 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5127                                 if( obj_is_true( no_i18n_obj ) )    // Suppress internationalization?
5128                                         i18n = NULL;
5129                                 else
5130                                         i18n = osrfHashGet( field, "i18n" );
5131
5132                                 if( str_is_true( i18n ) ) {
5133                                         char* pkey = osrfHashGet( idlClass, "primarykey" );
5134                                         char* tname = osrfHashGet( idlClass, "tablename" );
5135
5136                                         buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5137                                                         "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5138                                                         tname, cname, fname, pkey, cname, pkey, locale, fname );
5139                                 } else {
5140                                         buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5141                                 }
5142                         } else {
5143                                 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5144                         }
5145                 }
5146
5147                 jsonIteratorFree( select_itr );
5148         }
5149
5150         jsonIteratorFree( class_itr );
5151
5152         char* col_list = buffer_release( select_buf );
5153         char* table = oilsGetRelation( meta );
5154         if( !table )
5155                 table = strdup( "(null)" );
5156
5157         buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5158         free( col_list );
5159         free( table );
5160
5161         // Clear the query stack (as a fail-safe precaution against possible
5162         // leftover garbage); then push the first query frame onto the stack.
5163         clear_query_stack();
5164         push_query_frame();
5165         if( add_query_core( NULL, core_class ) ) {
5166                 if( ctx )
5167                         osrfAppSessionStatus(
5168                                 ctx->session,
5169                                 OSRF_STATUS_INTERNALSERVERERROR,
5170                                 "osrfMethodException",
5171                                 ctx->request,
5172                                 "Unable to build query frame for core class"
5173                         );
5174                 buffer_free( sql_buf );
5175                 if( defaultselhash )
5176                         jsonObjectFree( defaultselhash );
5177                 return NULL;
5178         }
5179
5180         // Add the JOIN clauses, if any
5181         if( join_hash ) {
5182                 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5183                 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5184                 OSRF_BUFFER_ADD( sql_buf, join_clause );
5185                 free( join_clause );
5186         }
5187
5188         osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL =  %s",
5189                 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5190
5191         OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5192
5193         // Add the conditions in the WHERE clause
5194         char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5195         if( !pred ) {
5196                 osrfAppSessionStatus(
5197                         ctx->session,
5198                         OSRF_STATUS_INTERNALSERVERERROR,
5199                                 "osrfMethodException",
5200                                 ctx->request,
5201                                 "Severe query error -- see error log for more details"
5202                         );
5203                 buffer_free( sql_buf );
5204                 if( defaultselhash )
5205                         jsonObjectFree( defaultselhash );
5206                 clear_query_stack();
5207                 return NULL;
5208         } else {
5209                 buffer_add( sql_buf, pred );
5210                 free( pred );
5211         }
5212
5213         // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5214         if( rest_of_query ) {
5215                 const jsonObject* order_by = NULL;
5216                 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5217
5218                         char* order_by_list = NULL;
5219
5220                         if( JSON_ARRAY == order_by->type ) {
5221                                 order_by_list = buildOrderByFromArray( ctx, order_by );
5222                                 if( !order_by_list ) {
5223                                         buffer_free( sql_buf );
5224                                         if( defaultselhash )
5225                                                 jsonObjectFree( defaultselhash );
5226                                         clear_query_stack();
5227                                         return NULL;
5228                                 }
5229                         } else if( JSON_HASH == order_by->type ) {
5230                                 // We expect order_by to be a JSON_HASH keyed on class names.  Traverse it
5231                                 // and build a list of ORDER BY expressions.
5232                                 growing_buffer* order_buf = buffer_init( 128 );
5233                                 first = 1;
5234                                 jsonIterator* class_itr = jsonNewIterator( order_by );
5235                                 while( (snode = jsonIteratorNext( class_itr )) ) {  // For each class:
5236
5237                                         ClassInfo* order_class_info = search_alias( class_itr->key );
5238                                         if( ! order_class_info )
5239                                                 continue;    // class not referenced by FROM clause?  Ignore it.
5240
5241                                         if( JSON_HASH == snode->type ) {
5242
5243                                                 // If the data for the current class is a JSON_HASH, then it is
5244                                                 // keyed on field name.
5245
5246                                                 const jsonObject* onode = NULL;
5247                                                 jsonIterator* order_itr = jsonNewIterator( snode );
5248                                                 while( (onode = jsonIteratorNext( order_itr )) ) {  // For each field
5249
5250                                                         osrfHash* field_def = osrfHashGet(
5251                                                                 order_class_info->fields, order_itr->key );
5252                                                         if( !field_def )
5253                                                                 continue;    // Field not defined in IDL?  Ignore it.
5254                                                         if( str_is_true( osrfHashGet( field_def, "virtual")))
5255                                                                 continue;    // Field is virtual?  Ignore it.
5256
5257                                                         char* field_str = NULL;
5258                                                         char* direction = NULL;
5259                                                         if( onode->type == JSON_HASH ) {
5260                                                                 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5261                                                                         field_str = searchFieldTransform(
5262                                                                                 class_itr->key, field_def, onode );
5263                                                                         if( ! field_str ) {
5264                                                                                 osrfAppSessionStatus(
5265                                                                                         ctx->session,
5266                                                                                         OSRF_STATUS_INTERNALSERVERERROR,
5267                                                                                         "osrfMethodException",
5268                                                                                         ctx->request,
5269                                                                                         "Severe query error in ORDER BY clause -- "
5270                                                                                         "see error log for more details"
5271                                                                                 );
5272                                                                                 jsonIteratorFree( order_itr );
5273                                                                                 jsonIteratorFree( class_itr );
5274                                                                                 buffer_free( order_buf );
5275                                                                                 buffer_free( sql_buf );
5276                                                                                 if( defaultselhash )
5277                                                                                         jsonObjectFree( defaultselhash );
5278                                                                                 clear_query_stack();
5279                                                                                 return NULL;
5280                                                                         }
5281                                                                 } else {
5282                                                                         growing_buffer* field_buf = buffer_init( 16 );
5283                                                                         buffer_fadd( field_buf, "\"%s\".%s",
5284                                                                                 class_itr->key, order_itr->key );
5285                                                                         field_str = buffer_release( field_buf );
5286                                                                 }
5287
5288                                                                 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5289                                                                         const char* dir = jsonObjectGetString( order_by );
5290                                                                         if(!strncasecmp( dir, "d", 1 )) {
5291                                                                                 direction = " DESC";
5292                                                                         }
5293                                                                 }
5294                                                         } else {
5295                                                                 field_str = strdup( order_itr->key );
5296                                                                 const char* dir = jsonObjectGetString( onode );
5297                                                                 if( !strncasecmp( dir, "d", 1 )) {
5298                                                                         direction = " DESC";
5299                                                                 } else {
5300                                                                         direction = " ASC";
5301                                                                 }
5302                                                         }
5303
5304                                                         if( first ) {
5305                                                                 first = 0;
5306                                                         } else {
5307                                                                 buffer_add( order_buf, ", " );
5308                                                         }
5309
5310                                                         buffer_add( order_buf, field_str );
5311                                                         free( field_str );
5312
5313                                                         if( direction ) {
5314                                                                 buffer_add( order_buf, direction );
5315                                                         }
5316                                                 } // end while; looping over ORDER BY expressions
5317
5318                                                 jsonIteratorFree( order_itr );
5319
5320                                         } else if( JSON_STRING == snode->type ) {
5321                                                 // We expect a comma-separated list of sort fields.
5322                                                 const char* str = jsonObjectGetString( snode );
5323                                                 if( strchr( str, ';' )) {
5324                                                         // No semicolons allowed.  It is theoretically possible for a
5325                                                         // legitimate semicolon to occur within quotes, but it's not likely
5326                                                         // to occur in practice in the context of an ORDER BY list.
5327                                                         osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5328                                                                 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5329                                                         if( ctx ) {
5330                                                                 osrfAppSessionStatus(
5331                                                                         ctx->session,
5332                                                                         OSRF_STATUS_INTERNALSERVERERROR,
5333                                                                         "osrfMethodException",
5334                                                                         ctx->request,
5335                                                                         "Possible attempt at SOL injection -- "
5336                                                                                 "semicolon found in ORDER BY list"
5337                                                                 );
5338                                                         }
5339                                                         jsonIteratorFree( class_itr );
5340                                                         buffer_free( order_buf );
5341                                                         buffer_free( sql_buf );
5342                                                         if( defaultselhash )
5343                                                                 jsonObjectFree( defaultselhash );
5344                                                         clear_query_stack();
5345                                                         return NULL;
5346                                                 }
5347                                                 buffer_add( order_buf, str );
5348                                                 break;
5349                                         }
5350
5351                                 } // end while; looping over order_by classes
5352
5353                                 jsonIteratorFree( class_itr );
5354                                 order_by_list = buffer_release( order_buf );
5355
5356                         } else {
5357                                 osrfLogWarning( OSRF_LOG_MARK,
5358                                         "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5359                                         "no ORDER BY generated" );
5360                         }
5361
5362                         if( order_by_list && *order_by_list ) {
5363                                 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5364                                 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5365                         }
5366
5367                         free( order_by_list );
5368                 }
5369
5370                 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5371                 if( limit ) {
5372                         const char* str = jsonObjectGetString( limit );
5373                         buffer_fadd(
5374                                 sql_buf,
5375                                 " LIMIT %d",
5376                                 atoi(str)
5377                         );
5378                 }
5379
5380                 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5381                 if( offset ) {
5382                         const char* str = jsonObjectGetString( offset );
5383                         buffer_fadd(
5384                                 sql_buf,
5385                                 " OFFSET %d",
5386                                 atoi( str )
5387                         );
5388                 }
5389         }
5390
5391         if( defaultselhash )
5392                 jsonObjectFree( defaultselhash );
5393         clear_query_stack();
5394
5395         OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5396         return buffer_release( sql_buf );
5397 }
5398
5399 int doJSONSearch ( osrfMethodContext* ctx ) {
5400         if(osrfMethodVerifyContext( ctx )) {
5401                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
5402                 return -1;
5403         }
5404
5405         osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5406
5407         int err = 0;
5408
5409         jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5410
5411         int flags = 0;
5412
5413         if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5414                 flags |= SELECT_DISTINCT;
5415
5416         if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5417                 flags |= DISABLE_I18N;
5418
5419         osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5420         clear_query_stack();       // a possibly needless precaution
5421         char* sql = buildQuery( ctx, hash, flags );
5422         clear_query_stack();
5423
5424         if( !sql ) {
5425                 err = -1;
5426                 return err;
5427         }
5428
5429         osrfLogDebug( OSRF_LOG_MARK, "%s SQL =  %s", modulename, sql );
5430
5431         // XXX for now...
5432         dbhandle = writehandle;
5433
5434         dbi_result result = dbi_conn_query( dbhandle, sql );
5435
5436         if( result ) {
5437                 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5438
5439                 if( dbi_result_first_row( result )) {
5440                         /* JSONify the result */
5441                         osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5442
5443                         do {
5444                                 jsonObject* return_val = oilsMakeJSONFromResult( result );
5445                                 osrfAppRespond( ctx, return_val );
5446                                 jsonObjectFree( return_val );
5447                         } while( dbi_result_next_row( result ));
5448
5449                 } else {
5450                         osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5451                 }
5452
5453                 osrfAppRespondComplete( ctx, NULL );
5454
5455                 /* clean up the query */
5456                 dbi_result_free( result );
5457
5458         } else {
5459                 err = -1;
5460                 const char* msg;
5461                 int errnum = dbi_conn_error( dbhandle, &msg );
5462                 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5463                         modulename, sql, errnum, msg ? msg : "(No description available)" );
5464                 osrfAppSessionStatus(
5465                         ctx->session,
5466                         OSRF_STATUS_INTERNALSERVERERROR,
5467                         "osrfMethodException",
5468                         ctx->request,
5469                         "Severe query error -- see error log for more details"
5470                 );
5471                 if( !oilsIsDBConnected( dbhandle ))
5472                         osrfAppSessionPanic( ctx->session );
5473         }
5474
5475         free( sql );
5476         return err;
5477 }
5478
5479 // The last parameter, err, is used to report an error condition by updating an int owned by
5480 // the calling code.
5481
5482 // In case of an error, we set *err to -1.  If there is no error, *err is left unchanged.
5483 // It is the responsibility of the calling code to initialize *err before the
5484 // call, so that it will be able to make sense of the result.
5485
5486 // Note also that we return NULL if and only if we set *err to -1.  So the err parameter is
5487 // redundant anyway.
5488 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5489                 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5490
5491         // XXX for now...
5492         dbhandle = writehandle;
5493
5494         char* core_class = osrfHashGet( class_meta, "classname" );
5495         char* pkey = osrfHashGet( class_meta, "primarykey" );
5496
5497         const jsonObject* _tmp;
5498
5499         char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5500         if( !sql ) {
5501                 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5502                 *err = -1;
5503                 return NULL;
5504         }
5505
5506         osrfLogDebug( OSRF_LOG_MARK, "%s SQL =  %s", modulename, sql );
5507
5508         dbi_result result = dbi_conn_query( dbhandle, sql );
5509         if( NULL == result ) {
5510                 const char* msg;
5511                 int errnum = dbi_conn_error( dbhandle, &msg );
5512                 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5513                         modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5514                         msg ? msg : "(No description available)" );
5515                 if( !oilsIsDBConnected( dbhandle ))
5516                         osrfAppSessionPanic( ctx->session );
5517                 osrfAppSessionStatus(
5518                         ctx->session,
5519                         OSRF_STATUS_INTERNALSERVERERROR,
5520                         "osrfMethodException",
5521                         ctx->request,
5522                         "Severe query error -- see error log for more details"
5523                 );
5524                 *err = -1;
5525                 free( sql );
5526                 return NULL;
5527
5528         } else {
5529                 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5530         }
5531
5532         jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5533         jsonObject* row_obj = NULL;
5534
5535         if( dbi_result_first_row( result )) {
5536
5537                 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5538                 // in a JSON_ARRAY of rows.  If two or more rows have the same key value, then
5539                 // eliminate the duplicates.
5540                 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5541                 osrfHash* dedup = osrfNewHash();
5542                 do {
5543                         row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5544                         char* pkey_val = oilsFMGetString( row_obj, pkey );
5545                         if( osrfHashGet( dedup, pkey_val ) ) {
5546                                 jsonObjectFree( row_obj );
5547                                 free( pkey_val );
5548                         } else {
5549                                 osrfHashSet( dedup, pkey_val, pkey_val );
5550                                 jsonObjectPush( res_list, row_obj );
5551                         }
5552                 } while( dbi_result_next_row( result ));
5553                 osrfHashFree( dedup );
5554
5555         } else {
5556                 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5557                         modulename, sql );
5558         }
5559
5560         /* clean up the query */
5561         dbi_result_free( result );
5562         free( sql );
5563
5564         // If we're asked to flesh, and there's anything to flesh, then flesh it
5565         // (but not for PCRUD, lest the user to bypass permissions by fleshing
5566         // something that he has no permission to look at).
5567         if( res_list->size && query_hash && ! enforce_pcrud ) {
5568                 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5569                 if( _tmp ) {
5570                         // Get the flesh depth
5571                         int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5572                         if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5573                                 flesh_depth = max_flesh_depth;
5574
5575                         // We need a non-zero flesh depth, and a list of fields to flesh
5576                         const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5577                         if( temp_blob && flesh_depth > 0 ) {
5578
5579                                 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5580                                 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5581
5582                                 osrfStringArray* link_fields = NULL;
5583                                 osrfHash* links = osrfHashGet( class_meta, "links" );
5584
5585                                 // Make an osrfStringArray of the names of fields to be fleshed
5586                                 if( flesh_fields ) {
5587                                         if( flesh_fields->size == 1 ) {
5588                                                 const char* _t = jsonObjectGetString(
5589                                                         jsonObjectGetIndex( flesh_fields, 0 ) );
5590                                                 if( !strcmp( _t, "*" ))
5591                                                         link_fields = osrfHashKeys( links );
5592                                         }
5593
5594                                         if( !link_fields ) {
5595                                                 jsonObject* _f;
5596                                                 link_fields = osrfNewStringArray( 1 );
5597                                                 jsonIterator* _i = jsonNewIterator( flesh_fields );
5598                                                 while ((_f = jsonIteratorNext( _i ))) {
5599                                                         osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5600                                                 }
5601                                                 jsonIteratorFree( _i );
5602                                         }
5603                                 }
5604
5605                                 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5606
5607                                 // Iterate over the JSON_ARRAY of rows
5608                                 jsonObject* cur;
5609                                 unsigned long res_idx = 0;
5610                                 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5611
5612                                         int i = 0;
5613                                         const char* link_field;
5614
5615                                         // Iterate over the list of fleshable fields
5616                                         while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5617
5618                                                 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5619
5620                                                 osrfHash* kid_link = osrfHashGet( links, link_field );
5621                                                 if( !kid_link )
5622                                                         continue;     // Not a link field; skip it
5623
5624                                                 osrfHash* field = osrfHashGet( fields, link_field );
5625                                                 if( !field )
5626                                                         continue;     // Not a field at all; skip it (IDL is ill-formed)
5627
5628                                                 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5629                                                         osrfHashGet( kid_link, "class" ));
5630                                                 if( !kid_idl )
5631                                                         continue;   // The class it links to doesn't exist; skip it
5632
5633                                                 const char* reltype = osrfHashGet( kid_link, "reltype" );
5634                                                 if( !reltype )
5635                                                         continue;   // No reltype; skip it (IDL is ill-formed)
5636
5637                                                 osrfHash* value_field = field;
5638
5639                                                 if(    !strcmp( reltype, "has_many" )
5640                                                         || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5641                                                         value_field = osrfHashGet(
5642                                                                 fields, osrfHashGet( class_meta, "primarykey" ) );
5643                                                 }
5644
5645                                                 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5646
5647                                                 if( link_map->size > 0 ) {
5648                                                         jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5649                                                         jsonObjectPush(
5650                                                                 _kid_key,
5651                                                                 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5652                                                         );
5653
5654                                                         jsonObjectSetKey(
5655                                                                 flesh_blob,
5656                                                                 osrfHashGet( kid_link, "class" ),
5657                                                                 _kid_key
5658                                                         );
5659                                                 };
5660
5661                                                 osrfLogDebug(
5662                                                         OSRF_LOG_MARK,
5663                                                         "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5664                                                         osrfHashGet( kid_link, "field" ),
5665                                                         osrfHashGet( kid_link, "class" ),
5666                                                         osrfHashGet( kid_link, "key" ),
5667                                                         osrfHashGet( kid_link, "reltype" )
5668                                                 );
5669
5670                                                 const char* search_key = jsonObjectGetString(
5671                                                         jsonObjectGetIndex( cur,
5672                                                                 atoi( osrfHashGet( value_field, "array_position" ) )
5673                                                         )
5674                                                 );
5675
5676                                                 if( !search_key ) {
5677                                                         osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5678                                                         continue;
5679                                                 }
5680
5681                                                 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5682
5683                                                 // construct WHERE clause
5684                                                 jsonObject* where_clause  = jsonNewObjectType( JSON_HASH );
5685                                                 jsonObjectSetKey(
5686                                                         where_clause,
5687                                                         osrfHashGet( kid_link, "key" ),
5688                                                         jsonNewObject( search_key )
5689                                                 );
5690
5691                                                 // construct the rest of the query, mostly
5692                                                 // by copying pieces of the previous level of query
5693                                                 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5694                                                 jsonObjectSetKey( rest_of_query, "flesh",
5695                                                         jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5696                                                 );
5697
5698                                                 if( flesh_blob )
5699                                                         jsonObjectSetKey( rest_of_query, "flesh_fields",
5700                                                                 jsonObjectClone( flesh_blob ));
5701
5702                                                 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5703                                                         jsonObjectSetKey( rest_of_query, "order_by",
5704                                                                 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5705                                                         );
5706                                                 }
5707
5708                                                 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5709                                                         jsonObjectSetKey( rest_of_query, "select",
5710                                                                 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5711                                                         );
5712                                                 }
5713
5714                                                 // do the query, recursively, to expand the fleshable field
5715                                                 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5716                                                         where_clause, rest_of_query, err );
5717
5718                                                 jsonObjectFree( where_clause );
5719                                                 jsonObjectFree( rest_of_query );
5720
5721                                                 if( *err ) {
5722                                                         osrfStringArrayFree( link_fields );
5723                                                         jsonObjectFree( res_list );
5724                                                         jsonObjectFree( flesh_blob );
5725                                                         return NULL;
5726                                                 }
5727
5728                                                 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5729                                                         osrfHashGet( kid_link, "class" ), kids->size );
5730
5731                                                 // Traverse the result set
5732                                                 jsonObject* X = NULL;
5733                                                 if( link_map->size > 0 && kids->size > 0 ) {
5734                                                         X = kids;
5735                                                         kids = jsonNewObjectType( JSON_ARRAY );
5736
5737                                                         jsonObject* _k_node;
5738                                                         unsigned long res_idx = 0;
5739                                                         while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5740                                                                 jsonObjectPush(
5741                                                                         kids,
5742                                                                         jsonObjectClone(
5743                                                                                 jsonObjectGetIndex(
5744                                                                                         _k_node,
5745                                                                                         (unsigned long) atoi(
5746                                                                                                 osrfHashGet(
5747                                                                                                         osrfHashGet(
5748                                                                                                                 osrfHashGet(
5749                                                                                                                         osrfHashGet(
5750                                                                                                                                 oilsIDL(),
5751                                                                                                                                 osrfHashGet( kid_link, "class" )
5752                                                                                                                         ),
5753                                                                                                                         "fields"
5754                                                                                                                 ),
5755                                                                                                                 osrfStringArrayGetString( link_map, 0 )
5756                                                                                                         ),
5757                                                                                                         "array_position"
5758                                                                                                 )
5759                                                                                         )
5760                                                                                 )
5761                                                                         )
5762                                                                 );
5763                                                         } // end while loop traversing X
5764                                                 }
5765
5766                                                 if(    !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5767                                                         || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5768                                                         osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5769                                                                 osrfHashGet( kid_link, "field" ));
5770                                                         jsonObjectSetIndex(
5771                                                                 cur,
5772                                                                 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5773                                                                 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5774                                                         );
5775                                                 }
5776
5777                                                 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5778                                                         // has_many
5779                                                         osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5780                                                                 osrfHashGet( kid_link, "field" ) );
5781                                                         jsonObjectSetIndex(
5782                                                                 cur,
5783                                                                 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5784                                                                 jsonObjectClone( kids )
5785                                                         );
5786                                                 }
5787
5788                                                 if( X ) {
5789                                                         jsonObjectFree( kids );
5790                                                         kids = X;
5791                                                 }
5792
5793                                                 jsonObjectFree( kids );
5794
5795                                                 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5796                                                         osrfHashGet( kid_link, "field" ) );
5797                                                 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5798
5799                                         } // end while loop traversing list of fleshable fields
5800                                 } // end while loop traversing res_list
5801                                 jsonObjectFree( flesh_blob );
5802                                 osrfStringArrayFree( link_fields );
5803                         }
5804                 }
5805         }
5806
5807         return res_list;
5808 }
5809
5810
5811 int doUpdate( osrfMethodContext* ctx ) {
5812         if( osrfMethodVerifyContext( ctx )) {
5813                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5814                 return -1;
5815         }
5816
5817         if( enforce_pcrud )
5818                 timeout_needs_resetting = 1;
5819
5820         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5821
5822         jsonObject* target = NULL;
5823         if( enforce_pcrud )
5824                 target = jsonObjectGetIndex( ctx->params, 1 );
5825         else
5826                 target = jsonObjectGetIndex( ctx->params, 0 );
5827
5828         if(!verifyObjectClass( ctx, target )) {
5829                 osrfAppRespondComplete( ctx, NULL );
5830                 return -1;
5831         }
5832
5833         if( getXactId( ctx ) == NULL ) {
5834                 osrfAppSessionStatus(
5835                         ctx->session,
5836                         OSRF_STATUS_BADREQUEST,
5837                         "osrfMethodException",
5838                         ctx->request,
5839                         "No active transaction -- required for UPDATE"
5840                 );
5841                 osrfAppRespondComplete( ctx, NULL );
5842                 return -1;
5843         }
5844
5845         // The following test is harmless but redundant.  If a class is
5846         // readonly, we don't register an update method for it.
5847         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5848                 osrfAppSessionStatus(
5849                         ctx->session,
5850                         OSRF_STATUS_BADREQUEST,
5851                         "osrfMethodException",
5852                         ctx->request,
5853                         "Cannot UPDATE readonly class"
5854                 );
5855                 osrfAppRespondComplete( ctx, NULL );
5856                 return -1;
5857         }
5858
5859         const char* trans_id = getXactId( ctx );
5860
5861         // Set the last_xact_id
5862         int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5863         if( index > -1 ) {
5864                 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5865                                 trans_id, target->classname, index );
5866                 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5867         }
5868
5869         char* pkey = osrfHashGet( meta, "primarykey" );
5870         osrfHash* fields = osrfHashGet( meta, "fields" );
5871
5872         char* id = oilsFMGetString( target, pkey );
5873
5874         osrfLogDebug(
5875                 OSRF_LOG_MARK,
5876                 "%s updating %s object with %s = %s",
5877                 modulename,
5878                 osrfHashGet( meta, "fieldmapper" ),
5879                 pkey,
5880                 id
5881         );
5882
5883         dbhandle = writehandle;
5884         growing_buffer* sql = buffer_init( 128 );
5885         buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5886
5887         int first = 1;
5888         osrfHash* field_def = NULL;
5889         osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5890         while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5891
5892                 // Skip virtual fields, and the primary key
5893                 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5894                         continue;
5895
5896                 const char* field_name = osrfHashIteratorKey( field_itr );
5897                 if( ! strcmp( field_name, pkey ) )
5898                         continue;
5899
5900                 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5901
5902                 int value_is_numeric = 0;    // boolean
5903                 char* value;
5904                 if( field_object && field_object->classname ) {
5905                         value = oilsFMGetString(
5906                                 field_object,
5907                                 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5908                         );
5909                 } else if( field_object && JSON_BOOL == field_object->type ) {
5910                         if( jsonBoolIsTrue( field_object ) )
5911                                 value = strdup( "t" );
5912                         else
5913                                 value = strdup( "f" );
5914                 } else {
5915                         value = jsonObjectToSimpleString( field_object );
5916                         if( field_object && JSON_NUMBER == field_object->type )
5917                                 value_is_numeric = 1;
5918                 }
5919
5920                 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5921                                 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5922
5923                 if( !field_object || field_object->type == JSON_NULL ) {
5924                         if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5925                                         && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5926                                 if( first )
5927                                         first = 0;
5928                                 else
5929                                         OSRF_BUFFER_ADD_CHAR( sql, ',' );
5930                                 buffer_fadd( sql, " %s = NULL", field_name );
5931                         }
5932
5933                 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5934                         if( first )
5935                                 first = 0;
5936                         else
5937                                 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5938
5939                         const char* numtype = get_datatype( field_def );
5940                         if( !strncmp( numtype, "INT", 3 ) ) {
5941                                 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5942                         } else if( !strcmp( numtype, "NUMERIC" ) ) {
5943                                 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5944                         } else {
5945                                 // Must really be intended as a string, so quote it
5946                                 if( dbi_conn_quote_string( dbhandle, &value )) {
5947                                         buffer_fadd( sql, " %s = %s", field_name, value );
5948                                 } else {
5949                                         osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5950                                                 modulename, value );
5951                                         osrfAppSessionStatus(
5952                                                 ctx->session,
5953                                                 OSRF_STATUS_INTERNALSERVERERROR,
5954                                                 "osrfMethodException",
5955                                                 ctx->request,
5956                                                 "Error quoting string -- please see the error log for more details"
5957                                         );
5958                                         free( value );
5959                                         free( id );
5960                                         osrfHashIteratorFree( field_itr );
5961                                         buffer_free( sql );
5962                                         osrfAppRespondComplete( ctx, NULL );
5963                                         return -1;
5964                                 }
5965                         }
5966
5967                         osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5968
5969                 } else {
5970                         if( dbi_conn_quote_string( dbhandle, &value ) ) {
5971                                 if( first )
5972                                         first = 0;
5973                                 else
5974                                         OSRF_BUFFER_ADD_CHAR( sql, ',' );
5975                                 buffer_fadd( sql, " %s = %s", field_name, value );
5976                         } else {
5977                                 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5978                                 osrfAppSessionStatus(
5979                                         ctx->session,
5980                                         OSRF_STATUS_INTERNALSERVERERROR,
5981                                         "osrfMethodException",
5982                                         ctx->request,
5983                                         "Error quoting string -- please see the error log for more details"
5984                                 );
5985                                 free( value );
5986                                 free( id );
5987                                 osrfHashIteratorFree( field_itr );
5988                                 buffer_free( sql );
5989                                 osrfAppRespondComplete( ctx, NULL );
5990                                 return -1;
5991                         }
5992                 }
5993
5994                 free( value );
5995
5996         } // end while
5997
5998         osrfHashIteratorFree( field_itr );
5999
6000         jsonObject* obj = jsonNewObject( id );
6001
6002         if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6003                 dbi_conn_quote_string( dbhandle, &id );
6004
6005         buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6006
6007         char* query = buffer_release( sql );
6008         osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6009
6010         dbi_result result = dbi_conn_query( dbhandle, query );
6011         free( query );
6012
6013         int rc = 0;
6014         if( !result ) {
6015                 jsonObjectFree( obj );
6016                 obj = jsonNewObject( NULL );
6017                 const char* msg;
6018                 int errnum = dbi_conn_error( dbhandle, &msg );
6019                 osrfLogError(
6020                         OSRF_LOG_MARK,
6021                         "%s ERROR updating %s object with %s = %s: %d %s",
6022                         modulename,
6023                         osrfHashGet( meta, "fieldmapper" ),
6024                         pkey,
6025                         id,
6026                         errnum,
6027                         msg ? msg : "(No description available)"
6028                 );
6029                 osrfAppSessionStatus(
6030                         ctx->session,
6031                         OSRF_STATUS_INTERNALSERVERERROR,
6032                         "osrfMethodException",
6033                         ctx->request,
6034                         "Error in updating a row -- please see the error log for more details"
6035                 );
6036                 if( !oilsIsDBConnected( dbhandle ))
6037                         osrfAppSessionPanic( ctx->session );
6038                 rc = -1;
6039         } else
6040                 dbi_result_free( result );
6041
6042         free( id );
6043         osrfAppRespondComplete( ctx, obj );
6044         jsonObjectFree( obj );
6045         return rc;
6046 }
6047
6048 int doDelete( osrfMethodContext* ctx ) {
6049         if( osrfMethodVerifyContext( ctx )) {
6050                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6051                 return -1;
6052         }
6053
6054         if( enforce_pcrud )
6055                 timeout_needs_resetting = 1;
6056
6057         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6058
6059         if( getXactId( ctx ) == NULL ) {
6060                 osrfAppSessionStatus(
6061                         ctx->session,
6062                         OSRF_STATUS_BADREQUEST,
6063                         "osrfMethodException",
6064                         ctx->request,
6065                         "No active transaction -- required for DELETE"
6066                 );
6067                 osrfAppRespondComplete( ctx, NULL );
6068                 return -1;
6069         }
6070
6071         // The following test is harmless but redundant.  If a class is
6072         // readonly, we don't register a delete method for it.
6073         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6074                 osrfAppSessionStatus(
6075                         ctx->session,
6076                         OSRF_STATUS_BADREQUEST,
6077                         "osrfMethodException",
6078                         ctx->request,
6079                         "Cannot DELETE readonly class"
6080                 );
6081                 osrfAppRespondComplete( ctx, NULL );
6082                 return -1;
6083         }
6084
6085         dbhandle = writehandle;
6086
6087         char* pkey = osrfHashGet( meta, "primarykey" );
6088
6089         int _obj_pos = 0;
6090         if( enforce_pcrud )
6091                 _obj_pos = 1;
6092
6093         char* id;
6094         if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6095                 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6096                         osrfAppRespondComplete( ctx, NULL );
6097                         return -1;
6098                 }
6099
6100                 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6101         } else {
6102                 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL, 1 )) {
6103                         osrfAppRespondComplete( ctx, NULL );
6104                         return -1;
6105                 }
6106                 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6107         }
6108
6109         osrfLogDebug(
6110                 OSRF_LOG_MARK,
6111                 "%s deleting %s object with %s = %s",
6112                 modulename,
6113                 osrfHashGet( meta, "fieldmapper" ),
6114                 pkey,
6115                 id
6116         );
6117
6118         jsonObject* obj = jsonNewObject( id );
6119
6120         if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6121                 dbi_conn_quote_string( writehandle, &id );
6122
6123         dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6124                 osrfHashGet( meta, "tablename" ), pkey, id );
6125
6126         int rc = 0;
6127         if( !result ) {
6128                 rc = -1;
6129                 jsonObjectFree( obj );
6130                 obj = jsonNewObject( NULL );
6131                 const char* msg;
6132                 int errnum = dbi_conn_error( writehandle, &msg );
6133                 osrfLogError(
6134                         OSRF_LOG_MARK,
6135                         "%s ERROR deleting %s object with %s = %s: %d %s",
6136                         modulename,
6137                         osrfHashGet( meta, "fieldmapper" ),
6138                         pkey,
6139                         id,
6140                         errnum,
6141                         msg ? msg : "(No description available)"
6142                 );
6143                 osrfAppSessionStatus(
6144                         ctx->session,
6145                         OSRF_STATUS_INTERNALSERVERERROR,
6146                         "osrfMethodException",
6147                         ctx->request,
6148                         "Error in deleting a row -- please see the error log for more details"
6149                 );
6150                 if( !oilsIsDBConnected( writehandle ))
6151                         osrfAppSessionPanic( ctx->session );
6152         } else
6153                 dbi_result_free( result );
6154
6155         free( id );
6156
6157         osrfAppRespondComplete( ctx, obj );
6158         jsonObjectFree( obj );
6159         return rc;
6160 }
6161
6162 /**
6163         @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6164         @param result An iterator for a result set; we only look at the current row.
6165         @param @meta Pointer to the class metadata for the core class.
6166         @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6167
6168         If a column is not defined in the IDL, or if it has no array_position defined for it in
6169         the IDL, or if it is defined as virtual, ignore it.
6170
6171         Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6172         or JSON_STRING.  Then insert this jsonObject into the JSON_ARRAY according to its
6173         array_position in the IDL.
6174
6175         A field defined in the IDL but not represented in the returned row will leave a hole
6176         in the JSON_ARRAY.  In effect it will be treated as a null value.
6177
6178         In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6179         regardless of their sequence in the SELECT statement.  The JSON_ARRAY is assigned the
6180         classname corresponding to the @a meta argument.
6181
6182         The calling code is responsible for freeing the the resulting jsonObject by calling
6183         jsonObjectFree().
6184 */
6185 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6186         if( !( result && meta )) return NULL;
6187
6188         jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6189         jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6190         osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6191
6192         osrfHash* fields = osrfHashGet( meta, "fields" );
6193
6194         int columnIndex = 1;
6195         const char* columnName;
6196
6197         /* cycle through the columns in the row returned from the database */
6198         while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6199
6200                 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6201
6202                 int fmIndex = -1;  // Will be set to the IDL's sequence number for this field
6203
6204                 /* determine the field type and storage attributes */
6205                 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6206                 int attr            = dbi_result_get_field_attribs_idx( result, columnIndex );
6207
6208                 // Fetch the IDL's sequence number for the field.  If the field isn't in the IDL,
6209                 // or if it has no sequence number there, or if it's virtual, skip it.
6210                 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6211                 if( _f ) {
6212
6213                         if( str_is_true( osrfHashGet( _f, "virtual" )))
6214                                 continue;   // skip this column: IDL says it's virtual
6215
6216                         const char* pos = (char*) osrfHashGet( _f, "array_position" );
6217                         if( !pos )      // IDL has no sequence number for it.  This shouldn't happen,
6218                                 continue;    // since we assign sequence numbers dynamically as we load the IDL.
6219
6220                         fmIndex = atoi( pos );
6221                         osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6222                 } else {
6223                         continue;     // This field is not defined in the IDL
6224                 }
6225
6226                 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6227                 // sequence number from the IDL (which is likely to be different from the sequence
6228                 // of columns in the SELECT clause).
6229                 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6230                         jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6231                 } else {
6232
6233                         switch( type ) {
6234
6235                                 case DBI_TYPE_INTEGER :
6236
6237                                         if( attr & DBI_INTEGER_SIZE8 )
6238                                                 jsonObjectSetIndex( object, fmIndex,
6239                                                         jsonNewNumberObject(
6240                                                                 dbi_result_get_longlong_idx( result, columnIndex )));
6241                                         else
6242                                                 jsonObjectSetIndex( object, fmIndex,
6243                                                         jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6244
6245                                         break;
6246
6247                                 case DBI_TYPE_DECIMAL :
6248                                         jsonObjectSetIndex( object, fmIndex,
6249                                                         jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6250                                         break;
6251
6252                                 case DBI_TYPE_STRING :
6253
6254                                         jsonObjectSetIndex(
6255                                                 object,
6256                                                 fmIndex,
6257                                                 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6258                                         );
6259
6260                                         break;
6261
6262                                 case DBI_TYPE_DATETIME : {
6263
6264                                         char dt_string[ 256 ] = "";
6265                                         struct tm gmdt;
6266
6267                                         // Fetch the date column as a time_t
6268                                         time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6269
6270                                         // Translate the time_t to a human-readable string
6271                                         if( !( attr & DBI_DATETIME_DATE )) {
6272                                                 gmtime_r( &_tmp_dt, &gmdt );
6273                                                 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6274                                         } else if( !( attr & DBI_DATETIME_TIME )) {
6275                                                 localtime_r( &_tmp_dt, &gmdt );
6276                                                 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6277                                         } else {
6278                                                 localtime_r( &_tmp_dt, &gmdt );
6279                                                 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6280                                         }
6281
6282                                         jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6283
6284                                         break;
6285                                 }
6286                                 case DBI_TYPE_BINARY :
6287                                         osrfLogError( OSRF_LOG_MARK,
6288                                                 "Can't do binary at column %s : index %d", columnName, columnIndex );
6289                         } // End switch
6290                 }
6291                 ++columnIndex;
6292         } // End while
6293
6294         return object;
6295 }
6296
6297 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6298         if( !result ) return NULL;
6299
6300         jsonObject* object = jsonNewObject( NULL );
6301
6302         time_t _tmp_dt;
6303         char dt_string[ 256 ];
6304         struct tm gmdt;
6305
6306         int fmIndex;
6307         int columnIndex = 1;
6308         int attr;
6309         unsigned short type;
6310         const char* columnName;
6311
6312         /* cycle through the column list */
6313         while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6314
6315                 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6316
6317                 fmIndex = -1; // reset the position
6318
6319                 /* determine the field type and storage attributes */
6320                 type = dbi_result_get_field_type_idx( result, columnIndex );
6321                 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6322
6323                 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6324                         jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6325                 } else {
6326
6327                         switch( type ) {
6328
6329                                 case DBI_TYPE_INTEGER :
6330
6331                                         if( attr & DBI_INTEGER_SIZE8 )
6332                                                 jsonObjectSetKey( object, columnName,
6333                                                                 jsonNewNumberObject( dbi_result_get_longlong_idx(
6334                                                                                 result, columnIndex )) );
6335                                         else
6336                                                 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6337                                                                 dbi_result_get_int_idx( result, columnIndex )) );
6338                                         break;
6339
6340                                 case DBI_TYPE_DECIMAL :
6341                                         jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6342                                                 dbi_result_get_double_idx( result, columnIndex )) );
6343                                         break;
6344
6345                                 case DBI_TYPE_STRING :
6346                                         jsonObjectSetKey( object, columnName,
6347                                                 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6348                                         break;
6349
6350                                 case DBI_TYPE_DATETIME :
6351
6352                                         memset( dt_string, '\0', sizeof( dt_string ));
6353                                         memset( &gmdt, '\0', sizeof( gmdt ));
6354
6355                                         _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6356
6357                                         if( !( attr & DBI_DATETIME_DATE )) {
6358                                                 gmtime_r( &_tmp_dt, &gmdt );
6359                                                 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6360                                         } else if( !( attr & DBI_DATETIME_TIME )) {
6361                                                 localtime_r( &_tmp_dt, &gmdt );
6362                                                 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6363                                         } else {
6364                                                 localtime_r( &_tmp_dt, &gmdt );
6365                                                 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6366                                         }
6367
6368                                         jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6369                                         break;
6370
6371                                 case DBI_TYPE_BINARY :
6372                                         osrfLogError( OSRF_LOG_MARK,
6373                                                 "Can't do binary at column %s : index %d", columnName, columnIndex );
6374                         }
6375                 }
6376                 ++columnIndex;
6377         } // end while loop traversing result
6378
6379         return object;
6380 }
6381
6382 // Interpret a string as true or false
6383 int str_is_true( const char* str ) {
6384         if( NULL == str || strcasecmp( str, "true" ) )
6385                 return 0;
6386         else
6387                 return 1;
6388 }
6389
6390 // Interpret a jsonObject as true or false
6391 static int obj_is_true( const jsonObject* obj ) {
6392         if( !obj )
6393                 return 0;
6394         else switch( obj->type )
6395         {
6396                 case JSON_BOOL :
6397                         if( obj->value.b )
6398                                 return 1;
6399                         else
6400                                 return 0;
6401                 case JSON_STRING :
6402                         if( strcasecmp( obj->value.s, "true" ) )
6403                                 return 0;
6404                         else
6405                                 return 1;
6406                 case JSON_NUMBER :          // Support 1/0 for perl's sake
6407                         if( jsonObjectGetNumber( obj ) == 1.0 )
6408                                 return 1;
6409                         else
6410                                 return 0;
6411                 default :
6412                         return 0;
6413         }
6414 }
6415
6416 // Translate a numeric code into a text string identifying a type of
6417 // jsonObject.  To be used for building error messages.
6418 static const char* json_type( int code ) {
6419         switch ( code )
6420         {
6421                 case 0 :
6422                         return "JSON_HASH";
6423                 case 1 :
6424                         return "JSON_ARRAY";
6425                 case 2 :
6426                         return "JSON_STRING";
6427                 case 3 :
6428                         return "JSON_NUMBER";
6429                 case 4 :
6430                         return "JSON_NULL";
6431                 case 5 :
6432                         return "JSON_BOOL";
6433                 default :
6434                         return "(unrecognized)";
6435         }
6436 }
6437
6438 // Extract the "primitive" attribute from an IDL field definition.
6439 // If we haven't initialized the app, then we must be running in
6440 // some kind of testbed.  In that case, default to "string".
6441 static const char* get_primitive( osrfHash* field ) {
6442         const char* s = osrfHashGet( field, "primitive" );
6443         if( !s ) {
6444                 if( child_initialized )
6445                         osrfLogError(
6446                                 OSRF_LOG_MARK,
6447                                 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6448                                 modulename,
6449                                 osrfHashGet( field, "name" )
6450                         );
6451
6452                 s = "string";
6453         }
6454         return s;
6455 }
6456
6457 // Extract the "datatype" attribute from an IDL field definition.
6458 // If we haven't initialized the app, then we must be running in
6459 // some kind of testbed.  In that case, default to to NUMERIC,
6460 // since we look at the datatype only for numbers.
6461 static const char* get_datatype( osrfHash* field ) {
6462         const char* s = osrfHashGet( field, "datatype" );
6463         if( !s ) {
6464                 if( child_initialized )
6465                         osrfLogError(
6466                                 OSRF_LOG_MARK,
6467                                 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6468                                 modulename,
6469                                 osrfHashGet( field, "name" )
6470                         );
6471                 else
6472                         s = "NUMERIC";
6473         }
6474         return s;
6475 }
6476
6477 /**
6478         @brief Determine whether a string is potentially a valid SQL identifier.
6479         @param s The identifier to be tested.
6480         @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6481
6482         Purpose: to prevent certain kinds of SQL injection.  To that end we don't necessarily
6483         need to follow all the rules exactly, such as requiring that the first character not
6484         be a digit.
6485
6486         We allow leading and trailing white space.  In between, we do not allow punctuation
6487         (except for underscores and dollar signs), control characters, or embedded white space.
6488
6489         More pedantically we should allow quoted identifiers containing arbitrary characters, but
6490         for the foreseeable future such quoted identifiers are not likely to be an issue.
6491 */
6492 int is_identifier( const char* s) {
6493         if( !s )
6494                 return 0;
6495
6496         // Skip leading white space
6497         while( isspace( (unsigned char) *s ) )
6498                 ++s;
6499
6500         if( !s )
6501                 return 0;   // Nothing but white space?  Not okay.
6502
6503         // Check each character until we reach white space or
6504         // end-of-string.  Letters, digits, underscores, and
6505         // dollar signs are okay. With the exception of periods
6506         // (as in schema.identifier), control characters and other
6507         // punctuation characters are not okay.  Anything else
6508         // is okay -- it could for example be part of a multibyte
6509         // UTF8 character such as a letter with diacritical marks,
6510         // and those are allowed.
6511         do {
6512                 if( isalnum( (unsigned char) *s )
6513                         || '.' == *s
6514                         || '_' == *s
6515                         || '$' == *s )
6516                         ;  // Fine; keep going
6517                 else if(   ispunct( (unsigned char) *s )
6518                                 || iscntrl( (unsigned char) *s ) )
6519                         return 0;
6520                         ++s;
6521         } while( *s && ! isspace( (unsigned char) *s ) );
6522
6523         // If we found any white space in the above loop,
6524         // the rest had better be all white space.
6525
6526         while( isspace( (unsigned char) *s ) )
6527                 ++s;
6528
6529         if( *s )
6530                 return 0;   // White space was embedded within non-white space
6531
6532         return 1;
6533 }
6534
6535 /**
6536         @brief Determine whether to accept a character string as a comparison operator.
6537         @param op The candidate comparison operator.
6538         @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6539
6540         We don't validate the operator for real.  We just make sure that it doesn't contain
6541         any semicolons or white space (with special exceptions for a few specific operators).
6542         The idea is to block certain kinds of SQL injection.  If it has no semicolons or white
6543         space but it's still not a valid operator, then the database will complain.
6544
6545         Another approach would be to compare the string against a short list of approved operators.
6546         We don't do that because we want to allow custom operators like ">100*", which at this
6547         writing would be difficult or impossible to express otherwise in a JSON query.
6548 */
6549 int is_good_operator( const char* op ) {
6550         if( !op ) return 0;   // Sanity check
6551
6552         const char* s = op;
6553         while( *s ) {
6554                 if( isspace( (unsigned char) *s ) ) {
6555                         // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6556                         // and IS NOT DISTINCT FROM.
6557                         if( !strcasecmp( op, "similar to" ) )
6558                                 return 1;
6559                         else if( !strcasecmp( op, "is distinct from" ) )
6560                                 return 1;
6561                         else if( !strcasecmp( op, "is not distinct from" ) )
6562                                 return 1;
6563                         else
6564                                 return 0;
6565                 }
6566                 else if( ';' == *s )
6567                         return 0;
6568                 ++s;
6569         }
6570         return 1;
6571 }
6572
6573 /**
6574         @name Query Frame Management
6575
6576         The following machinery supports a stack of query frames for use by SELECT().
6577
6578         A query frame caches information about one level of a SELECT query.  When we enter
6579         a subquery, we push another query frame onto the stack, and pop it off when we leave.
6580
6581         The query frame stores information about the core class, and about any joined classes
6582         in the FROM clause.
6583
6584         The main purpose is to map table aliases to classes and tables, so that a query can
6585         join to the same table more than once.  A secondary goal is to reduce the number of
6586         lookups in the IDL by caching the results.
6587 */
6588 /*@{*/
6589
6590 #define STATIC_CLASS_INFO_COUNT 3
6591
6592 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6593
6594 /**
6595         @brief Allocate a ClassInfo as raw memory.
6596         @return Pointer to the newly allocated ClassInfo.
6597
6598         Except for the in_use flag, which is used only by the allocation and deallocation
6599         logic, we don't initialize the ClassInfo here.
6600 */
6601 static ClassInfo* allocate_class_info( void ) {
6602         // In order to reduce the number of mallocs and frees, we return a static
6603         // instance of ClassInfo, if we can find one that we're not already using.
6604         // We rely on the fact that the compiler will implicitly initialize the
6605         // static instances so that in_use == 0.
6606
6607         int i;
6608         for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6609                 if( ! static_class_info[ i ].in_use ) {
6610                         static_class_info[ i ].in_use = 1;
6611                         return static_class_info + i;
6612                 }
6613         }
6614
6615         // The static ones are all in use.  Malloc one.
6616
6617         return safe_malloc( sizeof( ClassInfo ) );
6618 }
6619
6620 /**
6621         @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6622         @param info Pointer to the ClassInfo to be cleared.
6623 */
6624 static void clear_class_info( ClassInfo* info ) {
6625         // Sanity check
6626         if( ! info )
6627                 return;
6628
6629         // Free any malloc'd strings
6630
6631         if( info->alias != info->alias_store )
6632                 free( info->alias );
6633
6634         if( info->class_name != info->class_name_store )
6635                 free( info->class_name );
6636
6637         free( info->source_def );
6638
6639         info->alias = info->class_name = info->source_def = NULL;
6640         info->next = NULL;
6641 }
6642
6643 /**
6644         @brief Free a ClassInfo and everything it owns.
6645         @param info Pointer to the ClassInfo to be freed.
6646 */
6647 static void free_class_info( ClassInfo* info ) {
6648         // Sanity check
6649         if( ! info )
6650                 return;
6651
6652         clear_class_info( info );
6653
6654         // If it's one of the static instances, just mark it as not in use
6655
6656         int i;
6657         for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6658                 if( info == static_class_info + i ) {
6659                         static_class_info[ i ].in_use = 0;
6660                         return;
6661                 }
6662         }
6663
6664         // Otherwise it must have been malloc'd, so free it
6665
6666         free( info );
6667 }
6668
6669 /**
6670         @brief Populate an already-allocated ClassInfo.
6671         @param info Pointer to the ClassInfo to be populated.
6672         @param alias Alias for the class.  If it is NULL, or an empty string, use the class
6673         name for an alias.
6674         @param class Name of the class.
6675         @return Zero if successful, or 1 if not.
6676
6677         Populate the ClassInfo with copies of the alias and class name, and with pointers to
6678         the relevant portions of the IDL for the specified class.
6679 */
6680 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6681         // Sanity checks
6682         if( ! info ){
6683                 osrfLogError( OSRF_LOG_MARK,
6684                                           "%s ERROR: No ClassInfo available to populate", modulename );
6685                 info->alias = info->class_name = info->source_def = NULL;
6686                 info->class_def = info->fields = info->links = NULL;
6687                 return 1;
6688         }
6689
6690         if( ! class ) {
6691                 osrfLogError( OSRF_LOG_MARK,
6692                                           "%s ERROR: No class name provided for lookup", modulename );
6693                 info->alias = info->class_name = info->source_def = NULL;
6694                 info->class_def = info->fields = info->links = NULL;
6695                 return 1;
6696         }
6697
6698         // Alias defaults to class name if not supplied
6699         if( ! alias || ! alias[ 0 ] )
6700                 alias = class;
6701
6702         // Look up class info in the IDL
6703         osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6704         if( ! class_def ) {
6705                 osrfLogError( OSRF_LOG_MARK,
6706                                           "%s ERROR: Class %s not defined in IDL", modulename, class );
6707                 info->alias = info->class_name = info->source_def = NULL;
6708                 info->class_def = info->fields = info->links = NULL;
6709                 return 1;
6710         } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6711                 osrfLogError( OSRF_LOG_MARK,
6712                                           "%s ERROR: Class %s is defined as virtual", modulename, class );
6713                 info->alias = info->class_name = info->source_def = NULL;
6714                 info->class_def = info->fields = info->links = NULL;
6715                 return 1;
6716         }
6717
6718         osrfHash* links = osrfHashGet( class_def, "links" );
6719         if( ! links ) {
6720                 osrfLogError( OSRF_LOG_MARK,
6721                                           "%s ERROR: No links defined in IDL for class %s", modulename, class );
6722                 info->alias = info->class_name = info->source_def = NULL;
6723                 info->class_def = info->fields = info->links = NULL;
6724                 return 1;
6725         }
6726
6727         osrfHash* fields = osrfHashGet( class_def, "fields" );
6728         if( ! fields ) {
6729                 osrfLogError( OSRF_LOG_MARK,
6730                                           "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6731                 info->alias = info->class_name = info->source_def = NULL;
6732                 info->class_def = info->fields = info->links = NULL;
6733                 return 1;
6734         }
6735
6736         char* source_def = oilsGetRelation( class_def );
6737         if( ! source_def )
6738                 return 1;
6739
6740         // We got everything we need, so populate the ClassInfo
6741         if( strlen( alias ) > ALIAS_STORE_SIZE )
6742                 info->alias = strdup( alias );
6743         else {
6744                 strcpy( info->alias_store, alias );
6745                 info->alias = info->alias_store;
6746         }
6747
6748         if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6749                 info->class_name = strdup( class );
6750         else {
6751                 strcpy( info->class_name_store, class );
6752                 info->class_name = info->class_name_store;
6753         }
6754
6755         info->source_def = source_def;
6756
6757         info->class_def = class_def;
6758         info->links     = links;
6759         info->fields    = fields;
6760
6761         return 0;
6762 }
6763
6764 #define STATIC_FRAME_COUNT 3
6765
6766 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6767
6768 /**
6769         @brief Allocate a QueryFrame as raw memory.
6770         @return Pointer to the newly allocated QueryFrame.
6771
6772         Except for the in_use flag, which is used only by the allocation and deallocation
6773         logic, we don't initialize the QueryFrame here.
6774 */
6775 static QueryFrame* allocate_frame( void ) {
6776         // In order to reduce the number of mallocs and frees, we return a static
6777         // instance of QueryFrame, if we can find one that we're not already using.
6778         // We rely on the fact that the compiler will implicitly initialize the
6779         // static instances so that in_use == 0.
6780
6781         int i;
6782         for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6783                 if( ! static_frame[ i ].in_use ) {
6784                         static_frame[ i ].in_use = 1;
6785                         return static_frame + i;
6786                 }
6787         }
6788
6789         // The static ones are all in use.  Malloc one.
6790
6791         return safe_malloc( sizeof( QueryFrame ) );
6792 }
6793
6794 /**
6795         @brief Free a QueryFrame, and all the memory it owns.
6796         @param frame Pointer to the QueryFrame to be freed.
6797 */
6798 static void free_query_frame( QueryFrame* frame ) {
6799         // Sanity check
6800         if( ! frame )
6801                 return;
6802
6803         clear_class_info( &frame->core );
6804
6805         // Free the join list
6806         ClassInfo* temp;
6807         ClassInfo* info = frame->join_list;
6808         while( info ) {
6809                 temp = info->next;
6810                 free_class_info( info );
6811                 info = temp;
6812         }
6813
6814         frame->join_list = NULL;
6815         frame->next = NULL;
6816
6817         // If the frame is a static instance, just mark it as unused
6818         int i;
6819         for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6820                 if( frame == static_frame + i ) {
6821                         static_frame[ i ].in_use = 0;
6822                         return;
6823                 }
6824         }
6825
6826         // Otherwise it must have been malloc'd, so free it
6827
6828         free( frame );
6829 }
6830
6831 /**
6832         @brief Search a given QueryFrame for a specified alias.
6833         @param frame Pointer to the QueryFrame to be searched.
6834         @param target The alias for which to search.
6835         @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6836 */
6837 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6838         if( ! frame || ! target ) {
6839                 return NULL;
6840         }
6841
6842         ClassInfo* found_class = NULL;
6843
6844         if( !strcmp( target, frame->core.alias ) )
6845                 return &(frame->core);
6846         else {
6847                 ClassInfo* curr_class = frame->join_list;
6848                 while( curr_class ) {
6849                         if( strcmp( target, curr_class->alias ) )
6850                                 curr_class = curr_class->next;
6851                         else {
6852                                 found_class = curr_class;
6853                                 break;
6854                         }
6855                 }
6856         }
6857
6858         return found_class;
6859 }
6860
6861 /**
6862         @brief Push a new (blank) QueryFrame onto the stack.
6863 */
6864 static void push_query_frame( void ) {
6865         QueryFrame* frame = allocate_frame();
6866         frame->join_list = NULL;
6867         frame->next = curr_query;
6868
6869         // Initialize the ClassInfo for the core class
6870         ClassInfo* core = &frame->core;
6871         core->alias = core->class_name = core->source_def = NULL;
6872         core->class_def = core->fields = core->links = NULL;
6873
6874         curr_query = frame;
6875 }
6876
6877 /**
6878         @brief Pop a QueryFrame off the stack and destroy it.
6879 */
6880 static void pop_query_frame( void ) {
6881         // Sanity check
6882         if( ! curr_query )
6883                 return;
6884
6885         QueryFrame* popped = curr_query;
6886         curr_query = popped->next;
6887
6888         free_query_frame( popped );
6889 }
6890
6891 /**
6892         @brief Populate the ClassInfo for the core class.
6893         @param alias Alias for the core class.  If it is NULL or an empty string, we use the
6894         class name as an alias.
6895         @param class_name Name of the core class.
6896         @return Zero if successful, or 1 if not.
6897
6898         Populate the ClassInfo of the core class with copies of the alias and class name, and
6899         with pointers to the relevant portions of the IDL for the core class.
6900 */
6901 static int add_query_core( const char* alias, const char* class_name ) {
6902
6903         // Sanity checks
6904         if( ! curr_query ) {
6905                 osrfLogError( OSRF_LOG_MARK,
6906                                           "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6907                 return 1;
6908         } else if( curr_query->core.alias ) {
6909                 osrfLogError( OSRF_LOG_MARK,
6910                                           "%s ERROR: Core class %s already populated as %s",
6911                                           modulename, curr_query->core.class_name, curr_query->core.alias );
6912                 return 1;
6913         }
6914
6915         build_class_info( &curr_query->core, alias, class_name );
6916         if( curr_query->core.alias )
6917                 return 0;
6918         else {
6919                 osrfLogError( OSRF_LOG_MARK,
6920                                           "%s ERROR: Unable to look up core class %s", modulename, class_name );
6921                 return 1;
6922         }
6923 }
6924
6925 /**
6926         @brief Search the current QueryFrame for a specified alias.
6927         @param target The alias for which to search.
6928         @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6929 */
6930 static inline ClassInfo* search_alias( const char* target ) {
6931         return search_alias_in_frame( curr_query, target );
6932 }
6933
6934 /**
6935         @brief Search all levels of query for a specified alias, starting with the current query.
6936         @param target The alias for which to search.
6937         @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6938 */
6939 static ClassInfo* search_all_alias( const char* target ) {
6940         ClassInfo* found_class = NULL;
6941         QueryFrame* curr_frame = curr_query;
6942
6943         while( curr_frame ) {
6944                 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6945                         break;
6946                 else
6947                         curr_frame = curr_frame->next;
6948         }
6949
6950         return found_class;
6951 }
6952
6953 /**
6954         @brief Add a class to the list of classes joined to the current query.
6955         @param alias Alias of the class to be added.  If it is NULL or an empty string, we use
6956         the class name as an alias.
6957         @param classname The name of the class to be added.
6958         @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6959 */
6960 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6961
6962         if( ! classname || ! *classname ) {    // sanity check
6963                 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6964                 return NULL;
6965         }
6966
6967         if( ! alias )
6968                 alias = classname;
6969
6970         const ClassInfo* conflict = search_alias( alias );
6971         if( conflict ) {
6972                 osrfLogError( OSRF_LOG_MARK,
6973                                           "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6974                                           modulename, alias, conflict->class_name );
6975                 return NULL;
6976         }
6977
6978         ClassInfo* info = allocate_class_info();
6979
6980         if( build_class_info( info, alias, classname ) ) {
6981                 free_class_info( info );
6982                 return NULL;
6983         }
6984
6985         // Add the new ClassInfo to the join list of the current QueryFrame
6986         info->next = curr_query->join_list;
6987         curr_query->join_list = info;
6988
6989         return info;
6990 }
6991
6992 /**
6993         @brief Destroy all nodes on the query stack.
6994 */
6995 static void clear_query_stack( void ) {
6996         while( curr_query )
6997                 pop_query_frame();
6998 }
6999
7000 /*@}*/