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