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