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