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