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