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