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