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