]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/c-apps/oils_sql.c
LP#845763 remote added content breaks
[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                 if( !field || !class_alias ) {
4954                         osrfLogError( OSRF_LOG_MARK,
4955                                 "%s: Missing class or field name in field specification of ORDER BY clause",
4956                                 modulename );
4957                         if( ctx )
4958                                 osrfAppSessionStatus(
4959                                         ctx->session,
4960                                         OSRF_STATUS_INTERNALSERVERERROR,
4961                                         "osrfMethodException",
4962                                         ctx->request,
4963                                         "Malformed ORDER BY clause -- see error log for more details"
4964                                 );
4965                         buffer_free( order_buf );
4966                         return NULL;
4967                 }
4968
4969                 const ClassInfo* order_class_info = search_alias( class_alias );
4970                 if( ! order_class_info ) {
4971                         osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4972                                 "not in FROM clause, skipping it", modulename, class_alias );
4973                         continue;
4974                 }
4975
4976                 // Add a separating comma, except at the beginning
4977                 if( first )
4978                         first = 0;
4979                 else
4980                         OSRF_BUFFER_ADD( order_buf, ", " );
4981
4982                 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4983                 if( !field_def ) {
4984                         osrfLogError( OSRF_LOG_MARK,
4985                                 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4986                                 modulename, class_alias, field );
4987                         if( ctx )
4988                                 osrfAppSessionStatus(
4989                                         ctx->session,
4990                                         OSRF_STATUS_INTERNALSERVERERROR,
4991                                         "osrfMethodException",
4992                                         ctx->request,
4993                                         "Invalid field referenced in ORDER BY clause -- "
4994                                         "see error log for more details"
4995                                 );
4996                         free( order_buf );
4997                         return NULL;
4998                 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4999                         osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5000                                 modulename, field );
5001                         if( ctx )
5002                                 osrfAppSessionStatus(
5003                                         ctx->session,
5004                                         OSRF_STATUS_INTERNALSERVERERROR,
5005                                         "osrfMethodException",
5006                                         ctx->request,
5007                                         "Virtual field in ORDER BY clause -- see error log for more details"
5008                                 );
5009                         buffer_free( order_buf );
5010                         return NULL;
5011                 }
5012
5013                 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5014                         char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5015                         if( ! transform_str ) {
5016                                 if( ctx )
5017                                         osrfAppSessionStatus(
5018                                                 ctx->session,
5019                                                 OSRF_STATUS_INTERNALSERVERERROR,
5020                                                 "osrfMethodException",
5021                                                 ctx->request,
5022                                                 "Severe query error in ORDER BY clause -- "
5023                                                 "see error log for more details"
5024                                         );
5025                                 buffer_free( order_buf );
5026                                 return NULL;
5027                         }
5028
5029                         OSRF_BUFFER_ADD( order_buf, transform_str );
5030                         free( transform_str );
5031                 }
5032                 else
5033                         buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5034
5035                 const char* direction =
5036                         jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5037                 if( direction ) {
5038                         if( direction[ 0 ] || 'D' == direction[ 0 ] )
5039                                 OSRF_BUFFER_ADD( order_buf, " DESC" );
5040                         else
5041                                 OSRF_BUFFER_ADD( order_buf, " ASC" );
5042                 }
5043         }
5044
5045         return buffer_release( order_buf );
5046 }
5047
5048 /**
5049         @brief Build a SELECT statement.
5050         @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5051         @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5052         @param meta Pointer to the class metadata for the core class.
5053         @param ctx Pointer to the method context.
5054         @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5055
5056         Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5057         "order_by", "limit", and "offset".
5058
5059         The SELECT statements built here are distinct from those built for the json_query method.
5060 */
5061 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5062         osrfHash* meta, osrfMethodContext* ctx ) {
5063
5064         const char* locale = osrf_message_get_last_locale();
5065
5066         osrfHash* fields = osrfHashGet( meta, "fields" );
5067         const char* core_class = osrfHashGet( meta, "classname" );
5068
5069         const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5070
5071         jsonObject* selhash = NULL;
5072         jsonObject* defaultselhash = NULL;
5073
5074         growing_buffer* sql_buf = buffer_init( 128 );
5075         growing_buffer* select_buf = buffer_init( 128 );
5076
5077         if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5078                 defaultselhash = jsonNewObjectType( JSON_HASH );
5079                 selhash = defaultselhash;
5080         }
5081
5082         // If there's no SELECT list for the core class, build one
5083         if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5084                 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5085
5086                 // Add every non-virtual field to the field list
5087                 osrfHash* field_def = NULL;
5088                 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5089                 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5090                         if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5091                                 const char* field = osrfHashIteratorKey( field_itr );
5092                                 jsonObjectPush( field_list, jsonNewObject( field ) );
5093                         }
5094                 }
5095                 osrfHashIteratorFree( field_itr );
5096                 jsonObjectSetKey( selhash, core_class, field_list );
5097         }
5098
5099         // Build a list of columns for the SELECT clause
5100         int first = 1;
5101         const jsonObject* snode = NULL;
5102         jsonIterator* class_itr = jsonNewIterator( selhash );
5103         while( (snode = jsonIteratorNext( class_itr )) ) {        // For each class
5104
5105                 // If the class isn't in the IDL, ignore it
5106                 const char* cname = class_itr->key;
5107                 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5108                 if( !idlClass )
5109                         continue;
5110
5111                 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5112                 if( strcmp( core_class, class_itr->key )) {
5113                         if( !join_hash )
5114                                 continue;
5115
5116                         jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5117                         if( !found->size ) {
5118                                 jsonObjectFree( found );
5119                                 continue;
5120                         }
5121
5122                         jsonObjectFree( found );
5123                 }
5124
5125                 const jsonObject* node = NULL;
5126                 jsonIterator* select_itr = jsonNewIterator( snode );
5127                 while( (node = jsonIteratorNext( select_itr )) ) {
5128                         const char* item_str = jsonObjectGetString( node );
5129                         osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5130                         char* fname = osrfHashGet( field, "name" );
5131
5132                         if( !field )
5133                                 continue;
5134
5135                         if( first ) {
5136                                 first = 0;
5137                         } else {
5138                                 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5139                         }
5140
5141                         if( locale ) {
5142                                 const char* i18n;
5143                                 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5144                                 if( obj_is_true( no_i18n_obj ) )    // Suppress internationalization?
5145                                         i18n = NULL;
5146                                 else
5147                                         i18n = osrfHashGet( field, "i18n" );
5148
5149                                 if( str_is_true( i18n ) ) {
5150                                         char* pkey = osrfHashGet( idlClass, "primarykey" );
5151                                         char* tname = osrfHashGet( idlClass, "tablename" );
5152
5153                                         buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5154                                                         "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5155                                                         tname, cname, fname, pkey, cname, pkey, locale, fname );
5156                                 } else {
5157                                         buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5158                                 }
5159                         } else {
5160                                 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5161                         }
5162                 }
5163
5164                 jsonIteratorFree( select_itr );
5165         }
5166
5167         jsonIteratorFree( class_itr );
5168
5169         char* col_list = buffer_release( select_buf );
5170         char* table = oilsGetRelation( meta );
5171         if( !table )
5172                 table = strdup( "(null)" );
5173
5174         buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5175         free( col_list );
5176         free( table );
5177
5178         // Clear the query stack (as a fail-safe precaution against possible
5179         // leftover garbage); then push the first query frame onto the stack.
5180         clear_query_stack();
5181         push_query_frame();
5182         if( add_query_core( NULL, core_class ) ) {
5183                 if( ctx )
5184                         osrfAppSessionStatus(
5185                                 ctx->session,
5186                                 OSRF_STATUS_INTERNALSERVERERROR,
5187                                 "osrfMethodException",
5188                                 ctx->request,
5189                                 "Unable to build query frame for core class"
5190                         );
5191                 buffer_free( sql_buf );
5192                 if( defaultselhash )
5193                         jsonObjectFree( defaultselhash );
5194                 return NULL;
5195         }
5196
5197         // Add the JOIN clauses, if any
5198         if( join_hash ) {
5199                 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5200                 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5201                 OSRF_BUFFER_ADD( sql_buf, join_clause );
5202                 free( join_clause );
5203         }
5204
5205         osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL =  %s",
5206                 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5207
5208         OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5209
5210         // Add the conditions in the WHERE clause
5211         char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5212         if( !pred ) {
5213                 osrfAppSessionStatus(
5214                         ctx->session,
5215                         OSRF_STATUS_INTERNALSERVERERROR,
5216                                 "osrfMethodException",
5217                                 ctx->request,
5218                                 "Severe query error -- see error log for more details"
5219                         );
5220                 buffer_free( sql_buf );
5221                 if( defaultselhash )
5222                         jsonObjectFree( defaultselhash );
5223                 clear_query_stack();
5224                 return NULL;
5225         } else {
5226                 buffer_add( sql_buf, pred );
5227                 free( pred );
5228         }
5229
5230         // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5231         if( rest_of_query ) {
5232                 const jsonObject* order_by = NULL;
5233                 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5234
5235                         char* order_by_list = NULL;
5236
5237                         if( JSON_ARRAY == order_by->type ) {
5238                                 order_by_list = buildOrderByFromArray( ctx, order_by );
5239                                 if( !order_by_list ) {
5240                                         buffer_free( sql_buf );
5241                                         if( defaultselhash )
5242                                                 jsonObjectFree( defaultselhash );
5243                                         clear_query_stack();
5244                                         return NULL;
5245                                 }
5246                         } else if( JSON_HASH == order_by->type ) {
5247                                 // We expect order_by to be a JSON_HASH keyed on class names.  Traverse it
5248                                 // and build a list of ORDER BY expressions.
5249                                 growing_buffer* order_buf = buffer_init( 128 );
5250                                 first = 1;
5251                                 jsonIterator* class_itr = jsonNewIterator( order_by );
5252                                 while( (snode = jsonIteratorNext( class_itr )) ) {  // For each class:
5253
5254                                         ClassInfo* order_class_info = search_alias( class_itr->key );
5255                                         if( ! order_class_info )
5256                                                 continue;    // class not referenced by FROM clause?  Ignore it.
5257
5258                                         if( JSON_HASH == snode->type ) {
5259
5260                                                 // If the data for the current class is a JSON_HASH, then it is
5261                                                 // keyed on field name.
5262
5263                                                 const jsonObject* onode = NULL;
5264                                                 jsonIterator* order_itr = jsonNewIterator( snode );
5265                                                 while( (onode = jsonIteratorNext( order_itr )) ) {  // For each field
5266
5267                                                         osrfHash* field_def = osrfHashGet(
5268                                                                 order_class_info->fields, order_itr->key );
5269                                                         if( !field_def )
5270                                                                 continue;    // Field not defined in IDL?  Ignore it.
5271                                                         if( str_is_true( osrfHashGet( field_def, "virtual")))
5272                                                                 continue;    // Field is virtual?  Ignore it.
5273
5274                                                         char* field_str = NULL;
5275                                                         char* direction = NULL;
5276                                                         if( onode->type == JSON_HASH ) {
5277                                                                 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5278                                                                         field_str = searchFieldTransform(
5279                                                                                 class_itr->key, field_def, onode );
5280                                                                         if( ! field_str ) {
5281                                                                                 osrfAppSessionStatus(
5282                                                                                         ctx->session,
5283                                                                                         OSRF_STATUS_INTERNALSERVERERROR,
5284                                                                                         "osrfMethodException",
5285                                                                                         ctx->request,
5286                                                                                         "Severe query error in ORDER BY clause -- "
5287                                                                                         "see error log for more details"
5288                                                                                 );
5289                                                                                 jsonIteratorFree( order_itr );
5290                                                                                 jsonIteratorFree( class_itr );
5291                                                                                 buffer_free( order_buf );
5292                                                                                 buffer_free( sql_buf );
5293                                                                                 if( defaultselhash )
5294                                                                                         jsonObjectFree( defaultselhash );
5295                                                                                 clear_query_stack();
5296                                                                                 return NULL;
5297                                                                         }
5298                                                                 } else {
5299                                                                         growing_buffer* field_buf = buffer_init( 16 );
5300                                                                         buffer_fadd( field_buf, "\"%s\".%s",
5301                                                                                 class_itr->key, order_itr->key );
5302                                                                         field_str = buffer_release( field_buf );
5303                                                                 }
5304
5305                                                                 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5306                                                                         const char* dir = jsonObjectGetString( order_by );
5307                                                                         if(!strncasecmp( dir, "d", 1 )) {
5308                                                                                 direction = " DESC";
5309                                                                         }
5310                                                                 }
5311                                                         } else {
5312                                                                 field_str = strdup( order_itr->key );
5313                                                                 const char* dir = jsonObjectGetString( onode );
5314                                                                 if( !strncasecmp( dir, "d", 1 )) {
5315                                                                         direction = " DESC";
5316                                                                 } else {
5317                                                                         direction = " ASC";
5318                                                                 }
5319                                                         }
5320
5321                                                         if( first ) {
5322                                                                 first = 0;
5323                                                         } else {
5324                                                                 buffer_add( order_buf, ", " );
5325                                                         }
5326
5327                                                         buffer_add( order_buf, field_str );
5328                                                         free( field_str );
5329
5330                                                         if( direction ) {
5331                                                                 buffer_add( order_buf, direction );
5332                                                         }
5333                                                 } // end while; looping over ORDER BY expressions
5334
5335                                                 jsonIteratorFree( order_itr );
5336
5337                                         } else if( JSON_STRING == snode->type ) {
5338                                                 // We expect a comma-separated list of sort fields.
5339                                                 const char* str = jsonObjectGetString( snode );
5340                                                 if( strchr( str, ';' )) {
5341                                                         // No semicolons allowed.  It is theoretically possible for a
5342                                                         // legitimate semicolon to occur within quotes, but it's not likely
5343                                                         // to occur in practice in the context of an ORDER BY list.
5344                                                         osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5345                                                                 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5346                                                         if( ctx ) {
5347                                                                 osrfAppSessionStatus(
5348                                                                         ctx->session,
5349                                                                         OSRF_STATUS_INTERNALSERVERERROR,
5350                                                                         "osrfMethodException",
5351                                                                         ctx->request,
5352                                                                         "Possible attempt at SOL injection -- "
5353                                                                                 "semicolon found in ORDER BY list"
5354                                                                 );
5355                                                         }
5356                                                         jsonIteratorFree( class_itr );
5357                                                         buffer_free( order_buf );
5358                                                         buffer_free( sql_buf );
5359                                                         if( defaultselhash )
5360                                                                 jsonObjectFree( defaultselhash );
5361                                                         clear_query_stack();
5362                                                         return NULL;
5363                                                 }
5364                                                 buffer_add( order_buf, str );
5365                                                 break;
5366                                         }
5367
5368                                 } // end while; looping over order_by classes
5369
5370                                 jsonIteratorFree( class_itr );
5371                                 order_by_list = buffer_release( order_buf );
5372
5373                         } else {
5374                                 osrfLogWarning( OSRF_LOG_MARK,
5375                                         "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5376                                         "no ORDER BY generated" );
5377                         }
5378
5379                         if( order_by_list && *order_by_list ) {
5380                                 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5381                                 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5382                         }
5383
5384                         free( order_by_list );
5385                 }
5386
5387                 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5388                 if( limit ) {
5389                         const char* str = jsonObjectGetString( limit );
5390                         buffer_fadd(
5391                                 sql_buf,
5392                                 " LIMIT %d",
5393                                 atoi(str)
5394                         );
5395                 }
5396
5397                 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5398                 if( offset ) {
5399                         const char* str = jsonObjectGetString( offset );
5400                         buffer_fadd(
5401                                 sql_buf,
5402                                 " OFFSET %d",
5403                                 atoi( str )
5404                         );
5405                 }
5406         }
5407
5408         if( defaultselhash )
5409                 jsonObjectFree( defaultselhash );
5410         clear_query_stack();
5411
5412         OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5413         return buffer_release( sql_buf );
5414 }
5415
5416 int doJSONSearch ( osrfMethodContext* ctx ) {
5417         if(osrfMethodVerifyContext( ctx )) {
5418                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
5419                 return -1;
5420         }
5421
5422         osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5423
5424         int err = 0;
5425
5426         jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5427
5428         int flags = 0;
5429
5430         if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5431                 flags |= SELECT_DISTINCT;
5432
5433         if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5434                 flags |= DISABLE_I18N;
5435
5436         osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5437         clear_query_stack();       // a possibly needless precaution
5438         char* sql = buildQuery( ctx, hash, flags );
5439         clear_query_stack();
5440
5441         if( !sql ) {
5442                 err = -1;
5443                 return err;
5444         }
5445
5446         osrfLogDebug( OSRF_LOG_MARK, "%s SQL =  %s", modulename, sql );
5447
5448         // XXX for now...
5449         dbhandle = writehandle;
5450
5451         dbi_result result = dbi_conn_query( dbhandle, sql );
5452
5453         if( result ) {
5454                 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5455
5456                 if( dbi_result_first_row( result )) {
5457                         /* JSONify the result */
5458                         osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5459
5460                         do {
5461                                 jsonObject* return_val = oilsMakeJSONFromResult( result );
5462                                 osrfAppRespond( ctx, return_val );
5463                                 jsonObjectFree( return_val );
5464                         } while( dbi_result_next_row( result ));
5465
5466                 } else {
5467                         osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5468                 }
5469
5470                 osrfAppRespondComplete( ctx, NULL );
5471
5472                 /* clean up the query */
5473                 dbi_result_free( result );
5474
5475         } else {
5476                 err = -1;
5477                 const char* msg;
5478                 int errnum = dbi_conn_error( dbhandle, &msg );
5479                 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5480                         modulename, sql, errnum, msg ? msg : "(No description available)" );
5481                 osrfAppSessionStatus(
5482                         ctx->session,
5483                         OSRF_STATUS_INTERNALSERVERERROR,
5484                         "osrfMethodException",
5485                         ctx->request,
5486                         "Severe query error -- see error log for more details"
5487                 );
5488                 if( !oilsIsDBConnected( dbhandle ))
5489                         osrfAppSessionPanic( ctx->session );
5490         }
5491
5492         free( sql );
5493         return err;
5494 }
5495
5496 // The last parameter, err, is used to report an error condition by updating an int owned by
5497 // the calling code.
5498
5499 // In case of an error, we set *err to -1.  If there is no error, *err is left unchanged.
5500 // It is the responsibility of the calling code to initialize *err before the
5501 // call, so that it will be able to make sense of the result.
5502
5503 // Note also that we return NULL if and only if we set *err to -1.  So the err parameter is
5504 // redundant anyway.
5505 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5506                 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5507
5508         // XXX for now...
5509         dbhandle = writehandle;
5510
5511         char* core_class = osrfHashGet( class_meta, "classname" );
5512         char* pkey = osrfHashGet( class_meta, "primarykey" );
5513
5514         const jsonObject* _tmp;
5515
5516         char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5517         if( !sql ) {
5518                 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5519                 *err = -1;
5520                 return NULL;
5521         }
5522
5523         osrfLogDebug( OSRF_LOG_MARK, "%s SQL =  %s", modulename, sql );
5524
5525         dbi_result result = dbi_conn_query( dbhandle, sql );
5526         if( NULL == result ) {
5527                 const char* msg;
5528                 int errnum = dbi_conn_error( dbhandle, &msg );
5529                 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5530                         modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5531                         msg ? msg : "(No description available)" );
5532                 if( !oilsIsDBConnected( dbhandle ))
5533                         osrfAppSessionPanic( ctx->session );
5534                 osrfAppSessionStatus(
5535                         ctx->session,
5536                         OSRF_STATUS_INTERNALSERVERERROR,
5537                         "osrfMethodException",
5538                         ctx->request,
5539                         "Severe query error -- see error log for more details"
5540                 );
5541                 *err = -1;
5542                 free( sql );
5543                 return NULL;
5544
5545         } else {
5546                 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5547         }
5548
5549         jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5550         jsonObject* row_obj = NULL;
5551
5552         if( dbi_result_first_row( result )) {
5553
5554                 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5555                 // in a JSON_ARRAY of rows.  If two or more rows have the same key value, then
5556                 // eliminate the duplicates.
5557                 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5558                 osrfHash* dedup = osrfNewHash();
5559                 do {
5560                         row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5561                         char* pkey_val = oilsFMGetString( row_obj, pkey );
5562                         if( osrfHashGet( dedup, pkey_val ) ) {
5563                                 jsonObjectFree( row_obj );
5564                                 free( pkey_val );
5565                         } else {
5566                                 osrfHashSet( dedup, pkey_val, pkey_val );
5567                                 jsonObjectPush( res_list, row_obj );
5568                         }
5569                 } while( dbi_result_next_row( result ));
5570                 osrfHashFree( dedup );
5571
5572         } else {
5573                 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5574                         modulename, sql );
5575         }
5576
5577         /* clean up the query */
5578         dbi_result_free( result );
5579         free( sql );
5580
5581         // If we're asked to flesh, and there's anything to flesh, then flesh it
5582         // (but not for PCRUD, lest the user to bypass permissions by fleshing
5583         // something that he has no permission to look at).
5584         if( res_list->size && query_hash && ! enforce_pcrud ) {
5585                 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5586                 if( _tmp ) {
5587                         // Get the flesh depth
5588                         int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5589                         if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5590                                 flesh_depth = max_flesh_depth;
5591
5592                         // We need a non-zero flesh depth, and a list of fields to flesh
5593                         const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5594                         if( temp_blob && flesh_depth > 0 ) {
5595
5596                                 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5597                                 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5598
5599                                 osrfStringArray* link_fields = NULL;
5600                                 osrfHash* links = osrfHashGet( class_meta, "links" );
5601
5602                                 // Make an osrfStringArray of the names of fields to be fleshed
5603                                 if( flesh_fields ) {
5604                                         if( flesh_fields->size == 1 ) {
5605                                                 const char* _t = jsonObjectGetString(
5606                                                         jsonObjectGetIndex( flesh_fields, 0 ) );
5607                                                 if( !strcmp( _t, "*" ))
5608                                                         link_fields = osrfHashKeys( links );
5609                                         }
5610
5611                                         if( !link_fields ) {
5612                                                 jsonObject* _f;
5613                                                 link_fields = osrfNewStringArray( 1 );
5614                                                 jsonIterator* _i = jsonNewIterator( flesh_fields );
5615                                                 while ((_f = jsonIteratorNext( _i ))) {
5616                                                         osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5617                                                 }
5618                                                 jsonIteratorFree( _i );
5619                                         }
5620                                 }
5621
5622                                 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5623
5624                                 // Iterate over the JSON_ARRAY of rows
5625                                 jsonObject* cur;
5626                                 unsigned long res_idx = 0;
5627                                 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5628
5629                                         int i = 0;
5630                                         const char* link_field;
5631
5632                                         // Iterate over the list of fleshable fields
5633                                         while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5634
5635                                                 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5636
5637                                                 osrfHash* kid_link = osrfHashGet( links, link_field );
5638                                                 if( !kid_link )
5639                                                         continue;     // Not a link field; skip it
5640
5641                                                 osrfHash* field = osrfHashGet( fields, link_field );
5642                                                 if( !field )
5643                                                         continue;     // Not a field at all; skip it (IDL is ill-formed)
5644
5645                                                 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5646                                                         osrfHashGet( kid_link, "class" ));
5647                                                 if( !kid_idl )
5648                                                         continue;   // The class it links to doesn't exist; skip it
5649
5650                                                 const char* reltype = osrfHashGet( kid_link, "reltype" );
5651                                                 if( !reltype )
5652                                                         continue;   // No reltype; skip it (IDL is ill-formed)
5653
5654                                                 osrfHash* value_field = field;
5655
5656                                                 if(    !strcmp( reltype, "has_many" )
5657                                                         || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5658                                                         value_field = osrfHashGet(
5659                                                                 fields, osrfHashGet( class_meta, "primarykey" ) );
5660                                                 }
5661
5662                                                 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5663
5664                                                 if( link_map->size > 0 ) {
5665                                                         jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5666                                                         jsonObjectPush(
5667                                                                 _kid_key,
5668                                                                 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5669                                                         );
5670
5671                                                         jsonObjectSetKey(
5672                                                                 flesh_blob,
5673                                                                 osrfHashGet( kid_link, "class" ),
5674                                                                 _kid_key
5675                                                         );
5676                                                 };
5677
5678                                                 osrfLogDebug(
5679                                                         OSRF_LOG_MARK,
5680                                                         "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5681                                                         osrfHashGet( kid_link, "field" ),
5682                                                         osrfHashGet( kid_link, "class" ),
5683                                                         osrfHashGet( kid_link, "key" ),
5684                                                         osrfHashGet( kid_link, "reltype" )
5685                                                 );
5686
5687                                                 const char* search_key = jsonObjectGetString(
5688                                                         jsonObjectGetIndex( cur,
5689                                                                 atoi( osrfHashGet( value_field, "array_position" ) )
5690                                                         )
5691                                                 );
5692
5693                                                 if( !search_key ) {
5694                                                         osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5695                                                         continue;
5696                                                 }
5697
5698                                                 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5699
5700                                                 // construct WHERE clause
5701                                                 jsonObject* where_clause  = jsonNewObjectType( JSON_HASH );
5702                                                 jsonObjectSetKey(
5703                                                         where_clause,
5704                                                         osrfHashGet( kid_link, "key" ),
5705                                                         jsonNewObject( search_key )
5706                                                 );
5707
5708                                                 // construct the rest of the query, mostly
5709                                                 // by copying pieces of the previous level of query
5710                                                 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5711                                                 jsonObjectSetKey( rest_of_query, "flesh",
5712                                                         jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5713                                                 );
5714
5715                                                 if( flesh_blob )
5716                                                         jsonObjectSetKey( rest_of_query, "flesh_fields",
5717                                                                 jsonObjectClone( flesh_blob ));
5718
5719                                                 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5720                                                         jsonObjectSetKey( rest_of_query, "order_by",
5721                                                                 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5722                                                         );
5723                                                 }
5724
5725                                                 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5726                                                         jsonObjectSetKey( rest_of_query, "select",
5727                                                                 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5728                                                         );
5729                                                 }
5730
5731                                                 // do the query, recursively, to expand the fleshable field
5732                                                 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5733                                                         where_clause, rest_of_query, err );
5734
5735                                                 jsonObjectFree( where_clause );
5736                                                 jsonObjectFree( rest_of_query );
5737
5738                                                 if( *err ) {
5739                                                         osrfStringArrayFree( link_fields );
5740                                                         jsonObjectFree( res_list );
5741                                                         jsonObjectFree( flesh_blob );
5742                                                         return NULL;
5743                                                 }
5744
5745                                                 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5746                                                         osrfHashGet( kid_link, "class" ), kids->size );
5747
5748                                                 // Traverse the result set
5749                                                 jsonObject* X = NULL;
5750                                                 if( link_map->size > 0 && kids->size > 0 ) {
5751                                                         X = kids;
5752                                                         kids = jsonNewObjectType( JSON_ARRAY );
5753
5754                                                         jsonObject* _k_node;
5755                                                         unsigned long res_idx = 0;
5756                                                         while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5757                                                                 jsonObjectPush(
5758                                                                         kids,
5759                                                                         jsonObjectClone(
5760                                                                                 jsonObjectGetIndex(
5761                                                                                         _k_node,
5762                                                                                         (unsigned long) atoi(
5763                                                                                                 osrfHashGet(
5764                                                                                                         osrfHashGet(
5765                                                                                                                 osrfHashGet(
5766                                                                                                                         osrfHashGet(
5767                                                                                                                                 oilsIDL(),
5768                                                                                                                                 osrfHashGet( kid_link, "class" )
5769                                                                                                                         ),
5770                                                                                                                         "fields"
5771                                                                                                                 ),
5772                                                                                                                 osrfStringArrayGetString( link_map, 0 )
5773                                                                                                         ),
5774                                                                                                         "array_position"
5775                                                                                                 )
5776                                                                                         )
5777                                                                                 )
5778                                                                         )
5779                                                                 );
5780                                                         } // end while loop traversing X
5781                                                 }
5782
5783                                                 if(    !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5784                                                         || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5785                                                         osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5786                                                                 osrfHashGet( kid_link, "field" ));
5787                                                         jsonObjectSetIndex(
5788                                                                 cur,
5789                                                                 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5790                                                                 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5791                                                         );
5792                                                 }
5793
5794                                                 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5795                                                         // has_many
5796                                                         osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5797                                                                 osrfHashGet( kid_link, "field" ) );
5798                                                         jsonObjectSetIndex(
5799                                                                 cur,
5800                                                                 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5801                                                                 jsonObjectClone( kids )
5802                                                         );
5803                                                 }
5804
5805                                                 if( X ) {
5806                                                         jsonObjectFree( kids );
5807                                                         kids = X;
5808                                                 }
5809
5810                                                 jsonObjectFree( kids );
5811
5812                                                 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5813                                                         osrfHashGet( kid_link, "field" ) );
5814                                                 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5815
5816                                         } // end while loop traversing list of fleshable fields
5817                                 } // end while loop traversing res_list
5818                                 jsonObjectFree( flesh_blob );
5819                                 osrfStringArrayFree( link_fields );
5820                         }
5821                 }
5822         }
5823
5824         return res_list;
5825 }
5826
5827
5828 int doUpdate( osrfMethodContext* ctx ) {
5829         if( osrfMethodVerifyContext( ctx )) {
5830                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5831                 return -1;
5832         }
5833
5834         if( enforce_pcrud )
5835                 timeout_needs_resetting = 1;
5836
5837         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5838
5839         jsonObject* target = NULL;
5840         if( enforce_pcrud )
5841                 target = jsonObjectGetIndex( ctx->params, 1 );
5842         else
5843                 target = jsonObjectGetIndex( ctx->params, 0 );
5844
5845         if(!verifyObjectClass( ctx, target )) {
5846                 osrfAppRespondComplete( ctx, NULL );
5847                 return -1;
5848         }
5849
5850         if( getXactId( ctx ) == NULL ) {
5851                 osrfAppSessionStatus(
5852                         ctx->session,
5853                         OSRF_STATUS_BADREQUEST,
5854                         "osrfMethodException",
5855                         ctx->request,
5856                         "No active transaction -- required for UPDATE"
5857                 );
5858                 osrfAppRespondComplete( ctx, NULL );
5859                 return -1;
5860         }
5861
5862         // The following test is harmless but redundant.  If a class is
5863         // readonly, we don't register an update method for it.
5864         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5865                 osrfAppSessionStatus(
5866                         ctx->session,
5867                         OSRF_STATUS_BADREQUEST,
5868                         "osrfMethodException",
5869                         ctx->request,
5870                         "Cannot UPDATE readonly class"
5871                 );
5872                 osrfAppRespondComplete( ctx, NULL );
5873                 return -1;
5874         }
5875
5876         const char* trans_id = getXactId( ctx );
5877
5878         // Set the last_xact_id
5879         int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5880         if( index > -1 ) {
5881                 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5882                                 trans_id, target->classname, index );
5883                 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5884         }
5885
5886         char* pkey = osrfHashGet( meta, "primarykey" );
5887         osrfHash* fields = osrfHashGet( meta, "fields" );
5888
5889         char* id = oilsFMGetString( target, pkey );
5890
5891         osrfLogDebug(
5892                 OSRF_LOG_MARK,
5893                 "%s updating %s object with %s = %s",
5894                 modulename,
5895                 osrfHashGet( meta, "fieldmapper" ),
5896                 pkey,
5897                 id
5898         );
5899
5900         dbhandle = writehandle;
5901         growing_buffer* sql = buffer_init( 128 );
5902         buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5903
5904         int first = 1;
5905         osrfHash* field_def = NULL;
5906         osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5907         while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5908
5909                 // Skip virtual fields, and the primary key
5910                 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5911                         continue;
5912
5913                 const char* field_name = osrfHashIteratorKey( field_itr );
5914                 if( ! strcmp( field_name, pkey ) )
5915                         continue;
5916
5917                 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5918
5919                 int value_is_numeric = 0;    // boolean
5920                 char* value;
5921                 if( field_object && field_object->classname ) {
5922                         value = oilsFMGetString(
5923                                 field_object,
5924                                 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5925                         );
5926                 } else if( field_object && JSON_BOOL == field_object->type ) {
5927                         if( jsonBoolIsTrue( field_object ) )
5928                                 value = strdup( "t" );
5929                         else
5930                                 value = strdup( "f" );
5931                 } else {
5932                         value = jsonObjectToSimpleString( field_object );
5933                         if( field_object && JSON_NUMBER == field_object->type )
5934                                 value_is_numeric = 1;
5935                 }
5936
5937                 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5938                                 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5939
5940                 if( !field_object || field_object->type == JSON_NULL ) {
5941                         if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5942                                         && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5943                                 if( first )
5944                                         first = 0;
5945                                 else
5946                                         OSRF_BUFFER_ADD_CHAR( sql, ',' );
5947                                 buffer_fadd( sql, " %s = NULL", field_name );
5948                         }
5949
5950                 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5951                         if( first )
5952                                 first = 0;
5953                         else
5954                                 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5955
5956                         const char* numtype = get_datatype( field_def );
5957                         if( !strncmp( numtype, "INT", 3 ) ) {
5958                                 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5959                         } else if( !strcmp( numtype, "NUMERIC" ) ) {
5960                                 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5961                         } else {
5962                                 // Must really be intended as a string, so quote it
5963                                 if( dbi_conn_quote_string( dbhandle, &value )) {
5964                                         buffer_fadd( sql, " %s = %s", field_name, value );
5965                                 } else {
5966                                         osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5967                                                 modulename, value );
5968                                         osrfAppSessionStatus(
5969                                                 ctx->session,
5970                                                 OSRF_STATUS_INTERNALSERVERERROR,
5971                                                 "osrfMethodException",
5972                                                 ctx->request,
5973                                                 "Error quoting string -- please see the error log for more details"
5974                                         );
5975                                         free( value );
5976                                         free( id );
5977                                         osrfHashIteratorFree( field_itr );
5978                                         buffer_free( sql );
5979                                         osrfAppRespondComplete( ctx, NULL );
5980                                         return -1;
5981                                 }
5982                         }
5983
5984                         osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5985
5986                 } else {
5987                         if( dbi_conn_quote_string( dbhandle, &value ) ) {
5988                                 if( first )
5989                                         first = 0;
5990                                 else
5991                                         OSRF_BUFFER_ADD_CHAR( sql, ',' );
5992                                 buffer_fadd( sql, " %s = %s", field_name, value );
5993                         } else {
5994                                 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5995                                 osrfAppSessionStatus(
5996                                         ctx->session,
5997                                         OSRF_STATUS_INTERNALSERVERERROR,
5998                                         "osrfMethodException",
5999                                         ctx->request,
6000                                         "Error quoting string -- please see the error log for more details"
6001                                 );
6002                                 free( value );
6003                                 free( id );
6004                                 osrfHashIteratorFree( field_itr );
6005                                 buffer_free( sql );
6006                                 osrfAppRespondComplete( ctx, NULL );
6007                                 return -1;
6008                         }
6009                 }
6010
6011                 free( value );
6012
6013         } // end while
6014
6015         osrfHashIteratorFree( field_itr );
6016
6017         jsonObject* obj = jsonNewObject( id );
6018
6019         if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6020                 dbi_conn_quote_string( dbhandle, &id );
6021
6022         buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6023
6024         char* query = buffer_release( sql );
6025         osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6026
6027         dbi_result result = dbi_conn_query( dbhandle, query );
6028         free( query );
6029
6030         int rc = 0;
6031         if( !result ) {
6032                 jsonObjectFree( obj );
6033                 obj = jsonNewObject( NULL );
6034                 const char* msg;
6035                 int errnum = dbi_conn_error( dbhandle, &msg );
6036                 osrfLogError(
6037                         OSRF_LOG_MARK,
6038                         "%s ERROR updating %s object with %s = %s: %d %s",
6039                         modulename,
6040                         osrfHashGet( meta, "fieldmapper" ),
6041                         pkey,
6042                         id,
6043                         errnum,
6044                         msg ? msg : "(No description available)"
6045                 );
6046                 osrfAppSessionStatus(
6047                         ctx->session,
6048                         OSRF_STATUS_INTERNALSERVERERROR,
6049                         "osrfMethodException",
6050                         ctx->request,
6051                         "Error in updating a row -- please see the error log for more details"
6052                 );
6053                 if( !oilsIsDBConnected( dbhandle ))
6054                         osrfAppSessionPanic( ctx->session );
6055                 rc = -1;
6056         } else
6057                 dbi_result_free( result );
6058
6059         free( id );
6060         osrfAppRespondComplete( ctx, obj );
6061         jsonObjectFree( obj );
6062         return rc;
6063 }
6064
6065 int doDelete( osrfMethodContext* ctx ) {
6066         if( osrfMethodVerifyContext( ctx )) {
6067                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6068                 return -1;
6069         }
6070
6071         if( enforce_pcrud )
6072                 timeout_needs_resetting = 1;
6073
6074         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6075
6076         if( getXactId( ctx ) == NULL ) {
6077                 osrfAppSessionStatus(
6078                         ctx->session,
6079                         OSRF_STATUS_BADREQUEST,
6080                         "osrfMethodException",
6081                         ctx->request,
6082                         "No active transaction -- required for DELETE"
6083                 );
6084                 osrfAppRespondComplete( ctx, NULL );
6085                 return -1;
6086         }
6087
6088         // The following test is harmless but redundant.  If a class is
6089         // readonly, we don't register a delete method for it.
6090         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6091                 osrfAppSessionStatus(
6092                         ctx->session,
6093                         OSRF_STATUS_BADREQUEST,
6094                         "osrfMethodException",
6095                         ctx->request,
6096                         "Cannot DELETE readonly class"
6097                 );
6098                 osrfAppRespondComplete( ctx, NULL );
6099                 return -1;
6100         }
6101
6102         dbhandle = writehandle;
6103
6104         char* pkey = osrfHashGet( meta, "primarykey" );
6105
6106         int _obj_pos = 0;
6107         if( enforce_pcrud )
6108                 _obj_pos = 1;
6109
6110         char* id;
6111         if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6112                 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6113                         osrfAppRespondComplete( ctx, NULL );
6114                         return -1;
6115                 }
6116
6117                 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6118         } else {
6119                 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL, 1 )) {
6120                         osrfAppRespondComplete( ctx, NULL );
6121                         return -1;
6122                 }
6123                 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6124         }
6125
6126         osrfLogDebug(
6127                 OSRF_LOG_MARK,
6128                 "%s deleting %s object with %s = %s",
6129                 modulename,
6130                 osrfHashGet( meta, "fieldmapper" ),
6131                 pkey,
6132                 id
6133         );
6134
6135         jsonObject* obj = jsonNewObject( id );
6136
6137         if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6138                 dbi_conn_quote_string( writehandle, &id );
6139
6140         dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6141                 osrfHashGet( meta, "tablename" ), pkey, id );
6142
6143         int rc = 0;
6144         if( !result ) {
6145                 rc = -1;
6146                 jsonObjectFree( obj );
6147                 obj = jsonNewObject( NULL );
6148                 const char* msg;
6149                 int errnum = dbi_conn_error( writehandle, &msg );
6150                 osrfLogError(
6151                         OSRF_LOG_MARK,
6152                         "%s ERROR deleting %s object with %s = %s: %d %s",
6153                         modulename,
6154                         osrfHashGet( meta, "fieldmapper" ),
6155                         pkey,
6156                         id,
6157                         errnum,
6158                         msg ? msg : "(No description available)"
6159                 );
6160                 osrfAppSessionStatus(
6161                         ctx->session,
6162                         OSRF_STATUS_INTERNALSERVERERROR,
6163                         "osrfMethodException",
6164                         ctx->request,
6165                         "Error in deleting a row -- please see the error log for more details"
6166                 );
6167                 if( !oilsIsDBConnected( writehandle ))
6168                         osrfAppSessionPanic( ctx->session );
6169         } else
6170                 dbi_result_free( result );
6171
6172         free( id );
6173
6174         osrfAppRespondComplete( ctx, obj );
6175         jsonObjectFree( obj );
6176         return rc;
6177 }
6178
6179 /**
6180         @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6181         @param result An iterator for a result set; we only look at the current row.
6182         @param @meta Pointer to the class metadata for the core class.
6183         @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6184
6185         If a column is not defined in the IDL, or if it has no array_position defined for it in
6186         the IDL, or if it is defined as virtual, ignore it.
6187
6188         Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6189         or JSON_STRING.  Then insert this jsonObject into the JSON_ARRAY according to its
6190         array_position in the IDL.
6191
6192         A field defined in the IDL but not represented in the returned row will leave a hole
6193         in the JSON_ARRAY.  In effect it will be treated as a null value.
6194
6195         In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6196         regardless of their sequence in the SELECT statement.  The JSON_ARRAY is assigned the
6197         classname corresponding to the @a meta argument.
6198
6199         The calling code is responsible for freeing the the resulting jsonObject by calling
6200         jsonObjectFree().
6201 */
6202 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6203         if( !( result && meta )) return NULL;
6204
6205         jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6206         jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6207         osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6208
6209         osrfHash* fields = osrfHashGet( meta, "fields" );
6210
6211         int columnIndex = 1;
6212         const char* columnName;
6213
6214         /* cycle through the columns in the row returned from the database */
6215         while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6216
6217                 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6218
6219                 int fmIndex = -1;  // Will be set to the IDL's sequence number for this field
6220
6221                 /* determine the field type and storage attributes */
6222                 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6223                 int attr            = dbi_result_get_field_attribs_idx( result, columnIndex );
6224
6225                 // Fetch the IDL's sequence number for the field.  If the field isn't in the IDL,
6226                 // or if it has no sequence number there, or if it's virtual, skip it.
6227                 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6228                 if( _f ) {
6229
6230                         if( str_is_true( osrfHashGet( _f, "virtual" )))
6231                                 continue;   // skip this column: IDL says it's virtual
6232
6233                         const char* pos = (char*) osrfHashGet( _f, "array_position" );
6234                         if( !pos )      // IDL has no sequence number for it.  This shouldn't happen,
6235                                 continue;    // since we assign sequence numbers dynamically as we load the IDL.
6236
6237                         fmIndex = atoi( pos );
6238                         osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6239                 } else {
6240                         continue;     // This field is not defined in the IDL
6241                 }
6242
6243                 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6244                 // sequence number from the IDL (which is likely to be different from the sequence
6245                 // of columns in the SELECT clause).
6246                 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6247                         jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6248                 } else {
6249
6250                         switch( type ) {
6251
6252                                 case DBI_TYPE_INTEGER :
6253
6254                                         if( attr & DBI_INTEGER_SIZE8 )
6255                                                 jsonObjectSetIndex( object, fmIndex,
6256                                                         jsonNewNumberObject(
6257                                                                 dbi_result_get_longlong_idx( result, columnIndex )));
6258                                         else
6259                                                 jsonObjectSetIndex( object, fmIndex,
6260                                                         jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6261
6262                                         break;
6263
6264                                 case DBI_TYPE_DECIMAL :
6265                                         jsonObjectSetIndex( object, fmIndex,
6266                                                         jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6267                                         break;
6268
6269                                 case DBI_TYPE_STRING :
6270
6271                                         jsonObjectSetIndex(
6272                                                 object,
6273                                                 fmIndex,
6274                                                 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6275                                         );
6276
6277                                         break;
6278
6279                                 case DBI_TYPE_DATETIME : {
6280
6281                                         char dt_string[ 256 ] = "";
6282                                         struct tm gmdt;
6283
6284                                         // Fetch the date column as a time_t
6285                                         time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6286
6287                                         // Translate the time_t to a human-readable string
6288                                         if( !( attr & DBI_DATETIME_DATE )) {
6289                                                 gmtime_r( &_tmp_dt, &gmdt );
6290                                                 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6291                                         } else if( !( attr & DBI_DATETIME_TIME )) {
6292                                                 localtime_r( &_tmp_dt, &gmdt );
6293                                                 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6294                                         } else {
6295                                                 localtime_r( &_tmp_dt, &gmdt );
6296                                                 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6297                                         }
6298
6299                                         jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6300
6301                                         break;
6302                                 }
6303                                 case DBI_TYPE_BINARY :
6304                                         osrfLogError( OSRF_LOG_MARK,
6305                                                 "Can't do binary at column %s : index %d", columnName, columnIndex );
6306                         } // End switch
6307                 }
6308                 ++columnIndex;
6309         } // End while
6310
6311         return object;
6312 }
6313
6314 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6315         if( !result ) return NULL;
6316
6317         jsonObject* object = jsonNewObject( NULL );
6318
6319         time_t _tmp_dt;
6320         char dt_string[ 256 ];
6321         struct tm gmdt;
6322
6323         int fmIndex;
6324         int columnIndex = 1;
6325         int attr;
6326         unsigned short type;
6327         const char* columnName;
6328
6329         /* cycle through the column list */
6330         while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6331
6332                 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6333
6334                 fmIndex = -1; // reset the position
6335
6336                 /* determine the field type and storage attributes */
6337                 type = dbi_result_get_field_type_idx( result, columnIndex );
6338                 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6339
6340                 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6341                         jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6342                 } else {
6343
6344                         switch( type ) {
6345
6346                                 case DBI_TYPE_INTEGER :
6347
6348                                         if( attr & DBI_INTEGER_SIZE8 )
6349                                                 jsonObjectSetKey( object, columnName,
6350                                                                 jsonNewNumberObject( dbi_result_get_longlong_idx(
6351                                                                                 result, columnIndex )) );
6352                                         else
6353                                                 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6354                                                                 dbi_result_get_int_idx( result, columnIndex )) );
6355                                         break;
6356
6357                                 case DBI_TYPE_DECIMAL :
6358                                         jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6359                                                 dbi_result_get_double_idx( result, columnIndex )) );
6360                                         break;
6361
6362                                 case DBI_TYPE_STRING :
6363                                         jsonObjectSetKey( object, columnName,
6364                                                 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6365                                         break;
6366
6367                                 case DBI_TYPE_DATETIME :
6368
6369                                         memset( dt_string, '\0', sizeof( dt_string ));
6370                                         memset( &gmdt, '\0', sizeof( gmdt ));
6371
6372                                         _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6373
6374                                         if( !( attr & DBI_DATETIME_DATE )) {
6375                                                 gmtime_r( &_tmp_dt, &gmdt );
6376                                                 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6377                                         } else if( !( attr & DBI_DATETIME_TIME )) {
6378                                                 localtime_r( &_tmp_dt, &gmdt );
6379                                                 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6380                                         } else {
6381                                                 localtime_r( &_tmp_dt, &gmdt );
6382                                                 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6383                                         }
6384
6385                                         jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6386                                         break;
6387
6388                                 case DBI_TYPE_BINARY :
6389                                         osrfLogError( OSRF_LOG_MARK,
6390                                                 "Can't do binary at column %s : index %d", columnName, columnIndex );
6391                         }
6392                 }
6393                 ++columnIndex;
6394         } // end while loop traversing result
6395
6396         return object;
6397 }
6398
6399 // Interpret a string as true or false
6400 int str_is_true( const char* str ) {
6401         if( NULL == str || strcasecmp( str, "true" ) )
6402                 return 0;
6403         else
6404                 return 1;
6405 }
6406
6407 // Interpret a jsonObject as true or false
6408 static int obj_is_true( const jsonObject* obj ) {
6409         if( !obj )
6410                 return 0;
6411         else switch( obj->type )
6412         {
6413                 case JSON_BOOL :
6414                         if( obj->value.b )
6415                                 return 1;
6416                         else
6417                                 return 0;
6418                 case JSON_STRING :
6419                         if( strcasecmp( obj->value.s, "true" ) )
6420                                 return 0;
6421                         else
6422                                 return 1;
6423                 case JSON_NUMBER :          // Support 1/0 for perl's sake
6424                         if( jsonObjectGetNumber( obj ) == 1.0 )
6425                                 return 1;
6426                         else
6427                                 return 0;
6428                 default :
6429                         return 0;
6430         }
6431 }
6432
6433 // Translate a numeric code into a text string identifying a type of
6434 // jsonObject.  To be used for building error messages.
6435 static const char* json_type( int code ) {
6436         switch ( code )
6437         {
6438                 case 0 :
6439                         return "JSON_HASH";
6440                 case 1 :
6441                         return "JSON_ARRAY";
6442                 case 2 :
6443                         return "JSON_STRING";
6444                 case 3 :
6445                         return "JSON_NUMBER";
6446                 case 4 :
6447                         return "JSON_NULL";
6448                 case 5 :
6449                         return "JSON_BOOL";
6450                 default :
6451                         return "(unrecognized)";
6452         }
6453 }
6454
6455 // Extract the "primitive" attribute from an IDL field definition.
6456 // If we haven't initialized the app, then we must be running in
6457 // some kind of testbed.  In that case, default to "string".
6458 static const char* get_primitive( osrfHash* field ) {
6459         const char* s = osrfHashGet( field, "primitive" );
6460         if( !s ) {
6461                 if( child_initialized )
6462                         osrfLogError(
6463                                 OSRF_LOG_MARK,
6464                                 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6465                                 modulename,
6466                                 osrfHashGet( field, "name" )
6467                         );
6468
6469                 s = "string";
6470         }
6471         return s;
6472 }
6473
6474 // Extract the "datatype" attribute from an IDL field definition.
6475 // If we haven't initialized the app, then we must be running in
6476 // some kind of testbed.  In that case, default to to NUMERIC,
6477 // since we look at the datatype only for numbers.
6478 static const char* get_datatype( osrfHash* field ) {
6479         const char* s = osrfHashGet( field, "datatype" );
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                 else
6489                         s = "NUMERIC";
6490         }
6491         return s;
6492 }
6493
6494 /**
6495         @brief Determine whether a string is potentially a valid SQL identifier.
6496         @param s The identifier to be tested.
6497         @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6498
6499         Purpose: to prevent certain kinds of SQL injection.  To that end we don't necessarily
6500         need to follow all the rules exactly, such as requiring that the first character not
6501         be a digit.
6502
6503         We allow leading and trailing white space.  In between, we do not allow punctuation
6504         (except for underscores and dollar signs), control characters, or embedded white space.
6505
6506         More pedantically we should allow quoted identifiers containing arbitrary characters, but
6507         for the foreseeable future such quoted identifiers are not likely to be an issue.
6508 */
6509 int is_identifier( const char* s) {
6510         if( !s )
6511                 return 0;
6512
6513         // Skip leading white space
6514         while( isspace( (unsigned char) *s ) )
6515                 ++s;
6516
6517         if( !s )
6518                 return 0;   // Nothing but white space?  Not okay.
6519
6520         // Check each character until we reach white space or
6521         // end-of-string.  Letters, digits, underscores, and
6522         // dollar signs are okay. With the exception of periods
6523         // (as in schema.identifier), control characters and other
6524         // punctuation characters are not okay.  Anything else
6525         // is okay -- it could for example be part of a multibyte
6526         // UTF8 character such as a letter with diacritical marks,
6527         // and those are allowed.
6528         do {
6529                 if( isalnum( (unsigned char) *s )
6530                         || '.' == *s
6531                         || '_' == *s
6532                         || '$' == *s )
6533                         ;  // Fine; keep going
6534                 else if(   ispunct( (unsigned char) *s )
6535                                 || iscntrl( (unsigned char) *s ) )
6536                         return 0;
6537                         ++s;
6538         } while( *s && ! isspace( (unsigned char) *s ) );
6539
6540         // If we found any white space in the above loop,
6541         // the rest had better be all white space.
6542
6543         while( isspace( (unsigned char) *s ) )
6544                 ++s;
6545
6546         if( *s )
6547                 return 0;   // White space was embedded within non-white space
6548
6549         return 1;
6550 }
6551
6552 /**
6553         @brief Determine whether to accept a character string as a comparison operator.
6554         @param op The candidate comparison operator.
6555         @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6556
6557         We don't validate the operator for real.  We just make sure that it doesn't contain
6558         any semicolons or white space (with special exceptions for a few specific operators).
6559         The idea is to block certain kinds of SQL injection.  If it has no semicolons or white
6560         space but it's still not a valid operator, then the database will complain.
6561
6562         Another approach would be to compare the string against a short list of approved operators.
6563         We don't do that because we want to allow custom operators like ">100*", which at this
6564         writing would be difficult or impossible to express otherwise in a JSON query.
6565 */
6566 int is_good_operator( const char* op ) {
6567         if( !op ) return 0;   // Sanity check
6568
6569         const char* s = op;
6570         while( *s ) {
6571                 if( isspace( (unsigned char) *s ) ) {
6572                         // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6573                         // and IS NOT DISTINCT FROM.
6574                         if( !strcasecmp( op, "similar to" ) )
6575                                 return 1;
6576                         else if( !strcasecmp( op, "is distinct from" ) )
6577                                 return 1;
6578                         else if( !strcasecmp( op, "is not distinct from" ) )
6579                                 return 1;
6580                         else
6581                                 return 0;
6582                 }
6583                 else if( ';' == *s )
6584                         return 0;
6585                 ++s;
6586         }
6587         return 1;
6588 }
6589
6590 /**
6591         @name Query Frame Management
6592
6593         The following machinery supports a stack of query frames for use by SELECT().
6594
6595         A query frame caches information about one level of a SELECT query.  When we enter
6596         a subquery, we push another query frame onto the stack, and pop it off when we leave.
6597
6598         The query frame stores information about the core class, and about any joined classes
6599         in the FROM clause.
6600
6601         The main purpose is to map table aliases to classes and tables, so that a query can
6602         join to the same table more than once.  A secondary goal is to reduce the number of
6603         lookups in the IDL by caching the results.
6604 */
6605 /*@{*/
6606
6607 #define STATIC_CLASS_INFO_COUNT 3
6608
6609 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6610
6611 /**
6612         @brief Allocate a ClassInfo as raw memory.
6613         @return Pointer to the newly allocated ClassInfo.
6614
6615         Except for the in_use flag, which is used only by the allocation and deallocation
6616         logic, we don't initialize the ClassInfo here.
6617 */
6618 static ClassInfo* allocate_class_info( void ) {
6619         // In order to reduce the number of mallocs and frees, we return a static
6620         // instance of ClassInfo, if we can find one that we're not already using.
6621         // We rely on the fact that the compiler will implicitly initialize the
6622         // static instances so that in_use == 0.
6623
6624         int i;
6625         for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6626                 if( ! static_class_info[ i ].in_use ) {
6627                         static_class_info[ i ].in_use = 1;
6628                         return static_class_info + i;
6629                 }
6630         }
6631
6632         // The static ones are all in use.  Malloc one.
6633
6634         return safe_malloc( sizeof( ClassInfo ) );
6635 }
6636
6637 /**
6638         @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6639         @param info Pointer to the ClassInfo to be cleared.
6640 */
6641 static void clear_class_info( ClassInfo* info ) {
6642         // Sanity check
6643         if( ! info )
6644                 return;
6645
6646         // Free any malloc'd strings
6647
6648         if( info->alias != info->alias_store )
6649                 free( info->alias );
6650
6651         if( info->class_name != info->class_name_store )
6652                 free( info->class_name );
6653
6654         free( info->source_def );
6655
6656         info->alias = info->class_name = info->source_def = NULL;
6657         info->next = NULL;
6658 }
6659
6660 /**
6661         @brief Free a ClassInfo and everything it owns.
6662         @param info Pointer to the ClassInfo to be freed.
6663 */
6664 static void free_class_info( ClassInfo* info ) {
6665         // Sanity check
6666         if( ! info )
6667                 return;
6668
6669         clear_class_info( info );
6670
6671         // If it's one of the static instances, just mark it as not in use
6672
6673         int i;
6674         for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6675                 if( info == static_class_info + i ) {
6676                         static_class_info[ i ].in_use = 0;
6677                         return;
6678                 }
6679         }
6680
6681         // Otherwise it must have been malloc'd, so free it
6682
6683         free( info );
6684 }
6685
6686 /**
6687         @brief Populate an already-allocated ClassInfo.
6688         @param info Pointer to the ClassInfo to be populated.
6689         @param alias Alias for the class.  If it is NULL, or an empty string, use the class
6690         name for an alias.
6691         @param class Name of the class.
6692         @return Zero if successful, or 1 if not.
6693
6694         Populate the ClassInfo with copies of the alias and class name, and with pointers to
6695         the relevant portions of the IDL for the specified class.
6696 */
6697 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6698         // Sanity checks
6699         if( ! info ){
6700                 osrfLogError( OSRF_LOG_MARK,
6701                                           "%s ERROR: No ClassInfo available to populate", modulename );
6702                 info->alias = info->class_name = info->source_def = NULL;
6703                 info->class_def = info->fields = info->links = NULL;
6704                 return 1;
6705         }
6706
6707         if( ! class ) {
6708                 osrfLogError( OSRF_LOG_MARK,
6709                                           "%s ERROR: No class name provided for lookup", modulename );
6710                 info->alias = info->class_name = info->source_def = NULL;
6711                 info->class_def = info->fields = info->links = NULL;
6712                 return 1;
6713         }
6714
6715         // Alias defaults to class name if not supplied
6716         if( ! alias || ! alias[ 0 ] )
6717                 alias = class;
6718
6719         // Look up class info in the IDL
6720         osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6721         if( ! class_def ) {
6722                 osrfLogError( OSRF_LOG_MARK,
6723                                           "%s ERROR: Class %s not defined in IDL", modulename, class );
6724                 info->alias = info->class_name = info->source_def = NULL;
6725                 info->class_def = info->fields = info->links = NULL;
6726                 return 1;
6727         } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6728                 osrfLogError( OSRF_LOG_MARK,
6729                                           "%s ERROR: Class %s is defined as virtual", modulename, class );
6730                 info->alias = info->class_name = info->source_def = NULL;
6731                 info->class_def = info->fields = info->links = NULL;
6732                 return 1;
6733         }
6734
6735         osrfHash* links = osrfHashGet( class_def, "links" );
6736         if( ! links ) {
6737                 osrfLogError( OSRF_LOG_MARK,
6738                                           "%s ERROR: No links defined in IDL for class %s", modulename, class );
6739                 info->alias = info->class_name = info->source_def = NULL;
6740                 info->class_def = info->fields = info->links = NULL;
6741                 return 1;
6742         }
6743
6744         osrfHash* fields = osrfHashGet( class_def, "fields" );
6745         if( ! fields ) {
6746                 osrfLogError( OSRF_LOG_MARK,
6747                                           "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6748                 info->alias = info->class_name = info->source_def = NULL;
6749                 info->class_def = info->fields = info->links = NULL;
6750                 return 1;
6751         }
6752
6753         char* source_def = oilsGetRelation( class_def );
6754         if( ! source_def )
6755                 return 1;
6756
6757         // We got everything we need, so populate the ClassInfo
6758         if( strlen( alias ) > ALIAS_STORE_SIZE )
6759                 info->alias = strdup( alias );
6760         else {
6761                 strcpy( info->alias_store, alias );
6762                 info->alias = info->alias_store;
6763         }
6764
6765         if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6766                 info->class_name = strdup( class );
6767         else {
6768                 strcpy( info->class_name_store, class );
6769                 info->class_name = info->class_name_store;
6770         }
6771
6772         info->source_def = source_def;
6773
6774         info->class_def = class_def;
6775         info->links     = links;
6776         info->fields    = fields;
6777
6778         return 0;
6779 }
6780
6781 #define STATIC_FRAME_COUNT 3
6782
6783 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6784
6785 /**
6786         @brief Allocate a QueryFrame as raw memory.
6787         @return Pointer to the newly allocated QueryFrame.
6788
6789         Except for the in_use flag, which is used only by the allocation and deallocation
6790         logic, we don't initialize the QueryFrame here.
6791 */
6792 static QueryFrame* allocate_frame( void ) {
6793         // In order to reduce the number of mallocs and frees, we return a static
6794         // instance of QueryFrame, if we can find one that we're not already using.
6795         // We rely on the fact that the compiler will implicitly initialize the
6796         // static instances so that in_use == 0.
6797
6798         int i;
6799         for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6800                 if( ! static_frame[ i ].in_use ) {
6801                         static_frame[ i ].in_use = 1;
6802                         return static_frame + i;
6803                 }
6804         }
6805
6806         // The static ones are all in use.  Malloc one.
6807
6808         return safe_malloc( sizeof( QueryFrame ) );
6809 }
6810
6811 /**
6812         @brief Free a QueryFrame, and all the memory it owns.
6813         @param frame Pointer to the QueryFrame to be freed.
6814 */
6815 static void free_query_frame( QueryFrame* frame ) {
6816         // Sanity check
6817         if( ! frame )
6818                 return;
6819
6820         clear_class_info( &frame->core );
6821
6822         // Free the join list
6823         ClassInfo* temp;
6824         ClassInfo* info = frame->join_list;
6825         while( info ) {
6826                 temp = info->next;
6827                 free_class_info( info );
6828                 info = temp;
6829         }
6830
6831         frame->join_list = NULL;
6832         frame->next = NULL;
6833
6834         // If the frame is a static instance, just mark it as unused
6835         int i;
6836         for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6837                 if( frame == static_frame + i ) {
6838                         static_frame[ i ].in_use = 0;
6839                         return;
6840                 }
6841         }
6842
6843         // Otherwise it must have been malloc'd, so free it
6844
6845         free( frame );
6846 }
6847
6848 /**
6849         @brief Search a given QueryFrame for a specified alias.
6850         @param frame Pointer to the QueryFrame to be searched.
6851         @param target The alias for which to search.
6852         @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6853 */
6854 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6855         if( ! frame || ! target ) {
6856                 return NULL;
6857         }
6858
6859         ClassInfo* found_class = NULL;
6860
6861         if( !strcmp( target, frame->core.alias ) )
6862                 return &(frame->core);
6863         else {
6864                 ClassInfo* curr_class = frame->join_list;
6865                 while( curr_class ) {
6866                         if( strcmp( target, curr_class->alias ) )
6867                                 curr_class = curr_class->next;
6868                         else {
6869                                 found_class = curr_class;
6870                                 break;
6871                         }
6872                 }
6873         }
6874
6875         return found_class;
6876 }
6877
6878 /**
6879         @brief Push a new (blank) QueryFrame onto the stack.
6880 */
6881 static void push_query_frame( void ) {
6882         QueryFrame* frame = allocate_frame();
6883         frame->join_list = NULL;
6884         frame->next = curr_query;
6885
6886         // Initialize the ClassInfo for the core class
6887         ClassInfo* core = &frame->core;
6888         core->alias = core->class_name = core->source_def = NULL;
6889         core->class_def = core->fields = core->links = NULL;
6890
6891         curr_query = frame;
6892 }
6893
6894 /**
6895         @brief Pop a QueryFrame off the stack and destroy it.
6896 */
6897 static void pop_query_frame( void ) {
6898         // Sanity check
6899         if( ! curr_query )
6900                 return;
6901
6902         QueryFrame* popped = curr_query;
6903         curr_query = popped->next;
6904
6905         free_query_frame( popped );
6906 }
6907
6908 /**
6909         @brief Populate the ClassInfo for the core class.
6910         @param alias Alias for the core class.  If it is NULL or an empty string, we use the
6911         class name as an alias.
6912         @param class_name Name of the core class.
6913         @return Zero if successful, or 1 if not.
6914
6915         Populate the ClassInfo of the core class with copies of the alias and class name, and
6916         with pointers to the relevant portions of the IDL for the core class.
6917 */
6918 static int add_query_core( const char* alias, const char* class_name ) {
6919
6920         // Sanity checks
6921         if( ! curr_query ) {
6922                 osrfLogError( OSRF_LOG_MARK,
6923                                           "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6924                 return 1;
6925         } else if( curr_query->core.alias ) {
6926                 osrfLogError( OSRF_LOG_MARK,
6927                                           "%s ERROR: Core class %s already populated as %s",
6928                                           modulename, curr_query->core.class_name, curr_query->core.alias );
6929                 return 1;
6930         }
6931
6932         build_class_info( &curr_query->core, alias, class_name );
6933         if( curr_query->core.alias )
6934                 return 0;
6935         else {
6936                 osrfLogError( OSRF_LOG_MARK,
6937                                           "%s ERROR: Unable to look up core class %s", modulename, class_name );
6938                 return 1;
6939         }
6940 }
6941
6942 /**
6943         @brief Search the current QueryFrame for a specified alias.
6944         @param target The alias for which to search.
6945         @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6946 */
6947 static inline ClassInfo* search_alias( const char* target ) {
6948         return search_alias_in_frame( curr_query, target );
6949 }
6950
6951 /**
6952         @brief Search all levels of query for a specified alias, starting with the current query.
6953         @param target The alias for which to search.
6954         @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6955 */
6956 static ClassInfo* search_all_alias( const char* target ) {
6957         ClassInfo* found_class = NULL;
6958         QueryFrame* curr_frame = curr_query;
6959
6960         while( curr_frame ) {
6961                 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6962                         break;
6963                 else
6964                         curr_frame = curr_frame->next;
6965         }
6966
6967         return found_class;
6968 }
6969
6970 /**
6971         @brief Add a class to the list of classes joined to the current query.
6972         @param alias Alias of the class to be added.  If it is NULL or an empty string, we use
6973         the class name as an alias.
6974         @param classname The name of the class to be added.
6975         @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6976 */
6977 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6978
6979         if( ! classname || ! *classname ) {    // sanity check
6980                 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6981                 return NULL;
6982         }
6983
6984         if( ! alias )
6985                 alias = classname;
6986
6987         const ClassInfo* conflict = search_alias( alias );
6988         if( conflict ) {
6989                 osrfLogError( OSRF_LOG_MARK,
6990                                           "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6991                                           modulename, alias, conflict->class_name );
6992                 return NULL;
6993         }
6994
6995         ClassInfo* info = allocate_class_info();
6996
6997         if( build_class_info( info, alias, classname ) ) {
6998                 free_class_info( info );
6999                 return NULL;
7000         }
7001
7002         // Add the new ClassInfo to the join list of the current QueryFrame
7003         info->next = curr_query->join_list;
7004         curr_query->join_list = info;
7005
7006         return info;
7007 }
7008
7009 /**
7010         @brief Destroy all nodes on the query stack.
7011 */
7012 static void clear_query_stack( void ) {
7013         while( curr_query )
7014                 pop_query_frame();
7015 }
7016
7017 /*@}*/