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