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