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