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