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