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