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