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