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