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