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