]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/c-apps/oils_sql.c
Pull out into a separate function: the code in SELECT() that builds a
[working/Evergreen.git] / Open-ILS / src / c-apps / oils_sql.c
1 /**
2         @file oils_sql.c
3         @brief Utility routines for translating JSON into SQL.
4 */
5
6 #include <stdlib.h>
7 #include <string.h>
8 #include <time.h>
9 #include <ctype.h>
10 #include <dbi/dbi.h>
11 #include "opensrf/utils.h"
12 #include "opensrf/log.h"
13 #include "opensrf/osrf_application.h"
14 #include "openils/oils_utils.h"
15 #include "openils/oils_sql.h"
16
17 // The next four macros are OR'd together as needed to form a set
18 // of bitflags.  SUBCOMBO enables an extra pair of parentheses when
19 // nesting one UNION, INTERSECT or EXCEPT inside another.
20 // SUBSELECT tells us we're in a subquery, so don't add the
21 // terminal semicolon yet.
22 #define SUBCOMBO    8
23 #define SUBSELECT   4
24 #define DISABLE_I18N    2
25 #define SELECT_DISTINCT 1
26
27 #define AND_OP_JOIN     0
28 #define OR_OP_JOIN      1
29
30 struct ClassInfoStruct;
31 typedef struct ClassInfoStruct ClassInfo;
32
33 #define ALIAS_STORE_SIZE 16
34 #define CLASS_NAME_STORE_SIZE 16
35
36 struct ClassInfoStruct {
37         char* alias;
38         char* class_name;
39         char* source_def;
40         osrfHash* class_def;      // Points into IDL
41         osrfHash* fields;         // Points into IDL
42         osrfHash* links;          // Points into IDL
43
44         // The remaining members are private and internal.  Client code should not
45         // access them directly.
46
47         ClassInfo* next;          // Supports linked list of joined classes
48         int in_use;               // boolean
49
50         // We usually store the alias and class name in the following arrays, and
51         // point the corresponding pointers at them.  When the string is too big
52         // for the array (which will probably never happen in practice), we strdup it.
53
54         char alias_store[ ALIAS_STORE_SIZE + 1 ];
55         char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
56 };
57
58 struct QueryFrameStruct;
59 typedef struct QueryFrameStruct QueryFrame;
60
61 struct QueryFrameStruct {
62         ClassInfo core;
63         ClassInfo* join_list;  // linked list of classes joined to the core class
64         QueryFrame* next;      // implements stack as linked list
65         int in_use;            // boolean
66 };
67
68 static int timeout_needs_resetting;
69 static time_t time_next_reset;
70
71 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
72
73 static void setXactId( osrfMethodContext* ctx );
74 static inline const char* getXactId( osrfMethodContext* ctx );
75 static inline void clearXactId( osrfMethodContext* ctx );
76
77 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
78                 jsonObject* where_hash, jsonObject* query_hash, int* err );
79 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
80 static jsonObject* oilsMakeJSONFromResult( dbi_result );
81
82 static char* searchSimplePredicate ( const char* op, const char* class_alias,
83                                 osrfHash* field, const jsonObject* node );
84 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
85 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject* );
86 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*,
87                 const char* );
88 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
89 static char* searchINPredicate ( const char*, osrfHash*,
90                                                                  jsonObject*, const char*, osrfMethodContext* );
91 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
92 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
93 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo*, int, osrfMethodContext* );
94 static char* buildSELECT( const jsonObject*, jsonObject* rest_of_query,
95         osrfHash* meta, osrfMethodContext* ctx );
96 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array );
97
98 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
99
100 char* SELECT ( osrfMethodContext*, jsonObject*, const jsonObject*, const jsonObject*,
101         const jsonObject*, const jsonObject*, const jsonObject*, const jsonObject*, int );
102
103 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                 // Add a separating comma, except at the beginning
4813                 if( first )
4814                         first = 0;
4815                 else
4816                         OSRF_BUFFER_ADD( order_buf, ", " );
4817
4818                 if( !field || !class_alias ) {
4819                         osrfLogError( OSRF_LOG_MARK,
4820                                 "%s: Missing class or field name in field specification of ORDER BY clause",
4821                                 modulename );
4822                         if( ctx )
4823                                 osrfAppSessionStatus(
4824                                         ctx->session,
4825                                         OSRF_STATUS_INTERNALSERVERERROR,
4826                                         "osrfMethodException",
4827                                         ctx->request,
4828                                         "Malformed ORDER BY clause -- see error log for more details"
4829                                 );
4830                         buffer_free( order_buf );
4831                         return NULL;
4832                 }
4833
4834                 const ClassInfo* order_class_info = search_alias( class_alias );
4835                 if( ! order_class_info ) {
4836                         osrfLogError( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4837                                 "not in FROM clause", modulename, class_alias );
4838                         if( ctx )
4839                                 osrfAppSessionStatus(
4840                                         ctx->session,
4841                                         OSRF_STATUS_INTERNALSERVERERROR,
4842                                         "osrfMethodException",
4843                                         ctx->request,
4844                                         "Invalid class referenced in ORDER BY clause -- see error log for more details"
4845                                 );
4846                         free( order_buf );
4847                         return NULL;
4848                 }
4849
4850                 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4851                 if( !field_def ) {
4852                         osrfLogError( OSRF_LOG_MARK,
4853                                 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4854                                 modulename, class_alias, field );
4855                         if( ctx )
4856                                 osrfAppSessionStatus(
4857                                         ctx->session,
4858                                         OSRF_STATUS_INTERNALSERVERERROR,
4859                                         "osrfMethodException",
4860                                         ctx->request,
4861                                         "Invalid field referenced in ORDER BY clause -- "
4862                                         "see error log for more details"
4863                                 );
4864                         free( order_buf );
4865                         return NULL;
4866                 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4867                         osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4868                                 modulename, field );
4869                         if( ctx )
4870                                 osrfAppSessionStatus(
4871                                         ctx->session,
4872                                         OSRF_STATUS_INTERNALSERVERERROR,
4873                                         "osrfMethodException",
4874                                         ctx->request,
4875                                         "Virtual field in ORDER BY clause -- see error log for more details"
4876                                 );
4877                         buffer_free( order_buf );
4878                         return NULL;
4879                 }
4880
4881                 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
4882                         char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
4883                         if( ! transform_str ) {
4884                                 if( ctx )
4885                                         osrfAppSessionStatus(
4886                                                 ctx->session,
4887                                                 OSRF_STATUS_INTERNALSERVERERROR,
4888                                                 "osrfMethodException",
4889                                                 ctx->request,
4890                                                 "Severe query error in ORDER BY clause -- "
4891                                                 "see error log for more details"
4892                                         );
4893                                 buffer_free( order_buf );
4894                                 return NULL;
4895                         }
4896
4897                         OSRF_BUFFER_ADD( order_buf, transform_str );
4898                         free( transform_str );
4899                 }
4900                 else
4901                         buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4902
4903                 const char* direction =
4904                         jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4905                 if( direction ) {
4906                         if( direction[ 0 ] || 'D' == direction[ 0 ] )
4907                                 OSRF_BUFFER_ADD( order_buf, " DESC" );
4908                         else
4909                                 OSRF_BUFFER_ADD( order_buf, " ASC" );
4910                 }
4911         }
4912
4913         return buffer_release( order_buf );
4914 }
4915
4916 /**
4917         @brief Build a SELECT statement.
4918         @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
4919         @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
4920         @param meta Pointer to the class metadata for the core class.
4921         @param ctx Pointer to the method context.
4922         @return Pointer to a character string containing the WHERE clause; or NULL upon error.
4923
4924         Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
4925         "order_by", "limit", and "offset".
4926
4927         The SELECT statements built here are distinct from those built for the json_query method.
4928 */
4929 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
4930         osrfHash* meta, osrfMethodContext* ctx ) {
4931
4932         const char* locale = osrf_message_get_last_locale();
4933
4934         osrfHash* fields = osrfHashGet( meta, "fields" );
4935         const char* core_class = osrfHashGet( meta, "classname" );
4936
4937         const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
4938
4939         jsonObject* selhash = NULL;
4940         jsonObject* defaultselhash = NULL;
4941
4942         growing_buffer* sql_buf = buffer_init( 128 );
4943         growing_buffer* select_buf = buffer_init( 128 );
4944
4945         if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
4946                 defaultselhash = jsonNewObjectType( JSON_HASH );
4947                 selhash = defaultselhash;
4948         }
4949
4950         // If there's no SELECT list for the core class, build one
4951         if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
4952                 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4953
4954                 // Add every non-virtual field to the field list
4955                 osrfHash* field_def = NULL;
4956                 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4957                 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4958                         if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4959                                 const char* field = osrfHashIteratorKey( field_itr );
4960                                 jsonObjectPush( field_list, jsonNewObject( field ) );
4961                         }
4962                 }
4963                 osrfHashIteratorFree( field_itr );
4964                 jsonObjectSetKey( selhash, core_class, field_list );
4965         }
4966
4967         // Build a list of columns for the SELECT clause
4968         int first = 1;
4969         const jsonObject* snode = NULL;
4970         jsonIterator* class_itr = jsonNewIterator( selhash );
4971         while( (snode = jsonIteratorNext( class_itr )) ) {        // For each class
4972
4973                 // If the class isn't in the IDL, ignore it
4974                 const char* cname = class_itr->key;
4975                 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4976                 if( !idlClass )
4977                         continue;
4978
4979                 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
4980                 if( strcmp( core_class, class_itr->key )) {
4981                         if( !join_hash )
4982                                 continue;
4983
4984                         jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
4985                         if( !found->size ) {
4986                                 jsonObjectFree( found );
4987                                 continue;
4988                         }
4989
4990                         jsonObjectFree( found );
4991                 }
4992
4993                 const jsonObject* node = NULL;
4994                 jsonIterator* select_itr = jsonNewIterator( snode );
4995                 while( (node = jsonIteratorNext( select_itr )) ) {
4996                         const char* item_str = jsonObjectGetString( node );
4997                         osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4998                         char* fname = osrfHashGet( field, "name" );
4999
5000                         if( !field )
5001                                 continue;
5002
5003                         if( first ) {
5004                                 first = 0;
5005                         } else {
5006                                 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5007                         }
5008
5009                         if( locale ) {
5010                                 const char* i18n;
5011                                 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5012                                 if( obj_is_true( no_i18n_obj ) )    // Suppress internationalization?
5013                                         i18n = NULL;
5014                                 else
5015                                         i18n = osrfHashGet( field, "i18n" );
5016
5017                                 if( str_is_true( i18n ) ) {
5018                                         char* pkey = osrfHashGet( idlClass, "primarykey" );
5019                                         char* tname = osrfHashGet( idlClass, "tablename" );
5020
5021                                         buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5022                                                         "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5023                                                         tname, cname, fname, pkey, cname, pkey, locale, fname );
5024                                 } else {
5025                                         buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5026                                 }
5027                         } else {
5028                                 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5029                         }
5030                 }
5031
5032                 jsonIteratorFree( select_itr );
5033         }
5034
5035         jsonIteratorFree( class_itr );
5036
5037         char* col_list = buffer_release( select_buf );
5038         char* table = oilsGetRelation( meta );
5039         if( !table )
5040                 table = strdup( "(null)" );
5041
5042         buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5043         free( col_list );
5044         free( table );
5045
5046         // Clear the query stack (as a fail-safe precaution against possible
5047         // leftover garbage); then push the first query frame onto the stack.
5048         clear_query_stack();
5049         push_query_frame();
5050         if( add_query_core( NULL, core_class ) ) {
5051                 if( ctx )
5052                         osrfAppSessionStatus(
5053                                 ctx->session,
5054                                 OSRF_STATUS_INTERNALSERVERERROR,
5055                                 "osrfMethodException",
5056                                 ctx->request,
5057                                 "Unable to build query frame for core class"
5058                         );
5059                 buffer_free( sql_buf );
5060                 if( defaultselhash )
5061                         jsonObjectFree( defaultselhash );
5062                 return NULL;
5063         }
5064
5065         // Add the JOIN clauses, if any
5066         if( join_hash ) {
5067                 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5068                 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5069                 OSRF_BUFFER_ADD( sql_buf, join_clause );
5070                 free( join_clause );
5071         }
5072
5073         osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL =  %s",
5074                 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5075
5076         OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5077
5078         // Add the conditions in the WHERE clause
5079         char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5080         if( !pred ) {
5081                 osrfAppSessionStatus(
5082                         ctx->session,
5083                         OSRF_STATUS_INTERNALSERVERERROR,
5084                                 "osrfMethodException",
5085                                 ctx->request,
5086                                 "Severe query error -- see error log for more details"
5087                         );
5088                 buffer_free( sql_buf );
5089                 if( defaultselhash )
5090                         jsonObjectFree( defaultselhash );
5091                 clear_query_stack();
5092                 return NULL;
5093         } else {
5094                 buffer_add( sql_buf, pred );
5095                 free( pred );
5096         }
5097
5098         // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5099         if( rest_of_query ) {
5100                 const jsonObject* order_by = NULL;
5101                 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5102
5103                         char* order_by_list = NULL;
5104
5105                         if( JSON_ARRAY == order_by->type ) {
5106                                 order_by_list = buildOrderByFromArray( ctx, order_by );
5107                                 if( !order_by_list ) {
5108                                         buffer_free( sql_buf );
5109                                         if( defaultselhash )
5110                                                 jsonObjectFree( defaultselhash );
5111                                         clear_query_stack();
5112                                         return NULL;
5113                                 }
5114                         } else if( JSON_HASH == order_by->type ) {
5115                                 // We expect order_by to be a JSON_HASH keyed on class names.  Traverse it
5116                                 // and build a list of ORDER BY expressions.
5117                                 growing_buffer* order_buf = buffer_init( 128 );
5118                                 first = 1;
5119                                 jsonIterator* class_itr = jsonNewIterator( order_by );
5120                                 while( (snode = jsonIteratorNext( class_itr )) ) {  // For each class:
5121
5122                                         if( !jsonObjectGetKeyConst( selhash, class_itr->key ))
5123                                                 continue;    // class not referenced by SELECT clause?  Ignore it.
5124
5125                                         if( snode->type == JSON_HASH ) {
5126
5127                                                 // If the data for the current class is a JSON_HASH, then it is
5128                                                 // keyed on field name.
5129
5130                                                 const jsonObject* onode = NULL;
5131                                                 jsonIterator* order_itr = jsonNewIterator( snode );
5132                                                 while( (onode = jsonIteratorNext( order_itr )) ) {  // For each field
5133
5134                                                         osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
5135                                                                 class_itr->key, order_itr->key );
5136                                                         if( !field_def )
5137                                                                 continue;    // Field not defined in IDL?  Ignore it.
5138
5139                                                         char* field_str = NULL;
5140                                                         char* direction = NULL;
5141                                                         if( onode->type == JSON_HASH ) {
5142                                                                 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5143                                                                         field_str = searchFieldTransform(
5144                                                                                 class_itr->key, field_def, onode );
5145                                                                         if( ! field_str ) {
5146                                                                                 osrfAppSessionStatus(
5147                                                                                         ctx->session,
5148                                                                                         OSRF_STATUS_INTERNALSERVERERROR,
5149                                                                                         "osrfMethodException",
5150                                                                                         ctx->request,
5151                                                                                         "Severe query error in ORDER BY clause -- "
5152                                                                                         "see error log for more details"
5153                                                                                 );
5154                                                                                 jsonIteratorFree( order_itr );
5155                                                                                 jsonIteratorFree( class_itr );
5156                                                                                 buffer_free( order_buf );
5157                                                                                 buffer_free( sql_buf );
5158                                                                                 if( defaultselhash )
5159                                                                                         jsonObjectFree( defaultselhash );
5160                                                                                 clear_query_stack();
5161                                                                                 return NULL;
5162                                                                         }
5163                                                                 } else {
5164                                                                         growing_buffer* field_buf = buffer_init( 16 );
5165                                                                         buffer_fadd( field_buf, "\"%s\".%s",
5166                                                                                 class_itr->key, order_itr->key );
5167                                                                         field_str = buffer_release( field_buf );
5168                                                                 }
5169
5170                                                                 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5171                                                                         const char* dir = jsonObjectGetString( order_by );
5172                                                                         if(!strncasecmp( dir, "d", 1 )) {
5173                                                                                 direction = " DESC";
5174                                                                         }
5175                                                                 }
5176                                                         } else {
5177                                                                 field_str = strdup( order_itr->key );
5178                                                                 const char* dir = jsonObjectGetString( onode );
5179                                                                 if( !strncasecmp( dir, "d", 1 )) {
5180                                                                         direction = " DESC";
5181                                                                 } else {
5182                                                                         direction = " ASC";
5183                                                                 }
5184                                                         }
5185
5186                                                         if( first ) {
5187                                                                 first = 0;
5188                                                         } else {
5189                                                                 buffer_add( order_buf, ", " );
5190                                                         }
5191
5192                                                         buffer_add( order_buf, field_str );
5193                                                         free( field_str );
5194
5195                                                         if( direction ) {
5196                                                                 buffer_add( order_buf, direction );
5197                                                         }
5198                                                 } // end while; looping over ORDER BY expressions
5199
5200                                                 jsonIteratorFree( order_itr );
5201
5202                                         } else {
5203                                                 // The data for the current class is not a JSON_HASH, so we expect
5204                                                 // it to be a JSON_STRING with a single field name.
5205                                                 const char* str = jsonObjectGetString( snode );
5206                                                 buffer_add( order_buf, str );
5207                                                 break;
5208                                         }
5209
5210                                 } // end while; looping over order_by classes
5211
5212                                 jsonIteratorFree( class_itr );
5213                                 order_by_list = buffer_release( order_buf );
5214
5215                         } else {
5216                                 osrfLogWarning( OSRF_LOG_MARK,
5217                                         "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5218                                         "no ORDER BY generated" );
5219                         }
5220
5221                         if( order_by_list && *order_by_list ) {
5222                                 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5223                                 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5224                         }
5225
5226                         free( order_by_list );
5227                 }
5228
5229                 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5230                 if( limit ) {
5231                         const char* str = jsonObjectGetString( limit );
5232                         buffer_fadd(
5233                                 sql_buf,
5234                                 " LIMIT %d",
5235                                 atoi(str)
5236                         );
5237                 }
5238
5239                 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5240                 if( offset ) {
5241                         const char* str = jsonObjectGetString( offset );
5242                         buffer_fadd(
5243                                 sql_buf,
5244                                 " OFFSET %d",
5245                                 atoi( str )
5246                         );
5247                 }
5248         }
5249
5250         if( defaultselhash )
5251                 jsonObjectFree( defaultselhash );
5252         clear_query_stack();
5253
5254         OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5255         return buffer_release( sql_buf );
5256 }
5257
5258 int doJSONSearch ( osrfMethodContext* ctx ) {
5259         if(osrfMethodVerifyContext( ctx )) {
5260                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
5261                 return -1;
5262         }
5263
5264         osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5265
5266         int err = 0;
5267
5268         jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5269
5270         int flags = 0;
5271
5272         if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5273                 flags |= SELECT_DISTINCT;
5274
5275         if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5276                 flags |= DISABLE_I18N;
5277
5278         osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5279         clear_query_stack();       // a possibly needless precaution
5280         char* sql = buildQuery( ctx, hash, flags );
5281         clear_query_stack();
5282
5283         if( !sql ) {
5284                 err = -1;
5285                 return err;
5286         }
5287
5288         osrfLogDebug( OSRF_LOG_MARK, "%s SQL =  %s", modulename, sql );
5289
5290         // XXX for now...
5291         dbhandle = writehandle;
5292
5293         dbi_result result = dbi_conn_query( dbhandle, sql );
5294
5295         if( result ) {
5296                 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5297
5298                 if( dbi_result_first_row( result )) {
5299                         /* JSONify the result */
5300                         osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5301
5302                         do {
5303                                 jsonObject* return_val = oilsMakeJSONFromResult( result );
5304                                 osrfAppRespond( ctx, return_val );
5305                                 jsonObjectFree( return_val );
5306                         } while( dbi_result_next_row( result ));
5307
5308                 } else {
5309                         osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5310                 }
5311
5312                 osrfAppRespondComplete( ctx, NULL );
5313
5314                 /* clean up the query */
5315                 dbi_result_free( result );
5316
5317         } else {
5318                 err = -1;
5319                 const char* msg;
5320                 int errnum = dbi_conn_error( dbhandle, &msg );
5321                 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5322                         modulename, sql, errnum, msg ? msg : "(No description available)" );
5323                 osrfAppSessionStatus(
5324                         ctx->session,
5325                         OSRF_STATUS_INTERNALSERVERERROR,
5326                         "osrfMethodException",
5327                         ctx->request,
5328                         "Severe query error -- see error log for more details"
5329                 );
5330                 if( !oilsIsDBConnected( dbhandle ))
5331                         osrfAppSessionPanic( ctx->session );
5332         }
5333
5334         free( sql );
5335         return err;
5336 }
5337
5338 // The last parameter, err, is used to report an error condition by updating an int owned by
5339 // the calling code.
5340
5341 // In case of an error, we set *err to -1.  If there is no error, *err is left unchanged.
5342 // It is the responsibility of the calling code to initialize *err before the
5343 // call, so that it will be able to make sense of the result.
5344
5345 // Note also that we return NULL if and only if we set *err to -1.  So the err parameter is
5346 // redundant anyway.
5347 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5348                 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5349
5350         // XXX for now...
5351         dbhandle = writehandle;
5352
5353         char* core_class = osrfHashGet( class_meta, "classname" );
5354         char* pkey = osrfHashGet( class_meta, "primarykey" );
5355
5356         const jsonObject* _tmp;
5357
5358         char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5359         if( !sql ) {
5360                 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5361                 *err = -1;
5362                 return NULL;
5363         }
5364
5365         osrfLogDebug( OSRF_LOG_MARK, "%s SQL =  %s", modulename, sql );
5366
5367         dbi_result result = dbi_conn_query( dbhandle, sql );
5368         if( NULL == result ) {
5369                 const char* msg;
5370                 int errnum = dbi_conn_error( dbhandle, &msg );
5371                 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5372                         modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5373                         msg ? msg : "(No description available)" );
5374                 if( !oilsIsDBConnected( dbhandle ))
5375                         osrfAppSessionPanic( ctx->session );
5376                 osrfAppSessionStatus(
5377                         ctx->session,
5378                         OSRF_STATUS_INTERNALSERVERERROR,
5379                         "osrfMethodException",
5380                         ctx->request,
5381                         "Severe query error -- see error log for more details"
5382                 );
5383                 *err = -1;
5384                 free( sql );
5385                 return NULL;
5386
5387         } else {
5388                 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5389         }
5390
5391         jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5392         jsonObject* row_obj = NULL;
5393
5394         if( dbi_result_first_row( result )) {
5395
5396                 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5397                 // in a JSON_ARRAY of rows.  If two or more rows have the same key value, then
5398                 // eliminate the duplicates.
5399                 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5400                 osrfHash* dedup = osrfNewHash();
5401                 do {
5402                         row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5403                         char* pkey_val = oilsFMGetString( row_obj, pkey );
5404                         if( osrfHashGet( dedup, pkey_val ) ) {
5405                                 jsonObjectFree( row_obj );
5406                                 free( pkey_val );
5407                         } else {
5408                                 osrfHashSet( dedup, pkey_val, pkey_val );
5409                                 jsonObjectPush( res_list, row_obj );
5410                         }
5411                 } while( dbi_result_next_row( result ));
5412                 osrfHashFree( dedup );
5413
5414         } else {
5415                 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5416                         modulename, sql );
5417         }
5418
5419         /* clean up the query */
5420         dbi_result_free( result );
5421         free( sql );
5422
5423         // If we're asked to flesh, and there's anything to flesh, then flesh it
5424         // (but not for PCRUD, lest the user to bypass permissions by fleshing
5425         // something that he has no permission to look at).
5426         if( res_list->size && query_hash && ! enforce_pcrud ) {
5427                 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5428                 if( _tmp ) {
5429                         // Get the flesh depth
5430                         int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5431                         if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5432                                 flesh_depth = max_flesh_depth;
5433
5434                         // We need a non-zero flesh depth, and a list of fields to flesh
5435                         const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5436                         if( temp_blob && flesh_depth > 0 ) {
5437
5438                                 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5439                                 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5440
5441                                 osrfStringArray* link_fields = NULL;
5442                                 osrfHash* links = osrfHashGet( class_meta, "links" );
5443
5444                                 // Make an osrfStringArray of the names of fields to be fleshed
5445                                 if( flesh_fields ) {
5446                                         if( flesh_fields->size == 1 ) {
5447                                                 const char* _t = jsonObjectGetString(
5448                                                         jsonObjectGetIndex( flesh_fields, 0 ) );
5449                                                 if( !strcmp( _t, "*" ))
5450                                                         link_fields = osrfHashKeys( links );
5451                                         }
5452
5453                                         if( !link_fields ) {
5454                                                 jsonObject* _f;
5455                                                 link_fields = osrfNewStringArray( 1 );
5456                                                 jsonIterator* _i = jsonNewIterator( flesh_fields );
5457                                                 while ((_f = jsonIteratorNext( _i ))) {
5458                                                         osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5459                                                 }
5460                                                 jsonIteratorFree( _i );
5461                                         }
5462                                 }
5463
5464                                 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5465
5466                                 // Iterate over the JSON_ARRAY of rows
5467                                 jsonObject* cur;
5468                                 unsigned long res_idx = 0;
5469                                 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5470
5471                                         int i = 0;
5472                                         const char* link_field;
5473
5474                                         // Iterate over the list of fleshable fields
5475                                         while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5476
5477                                                 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5478
5479                                                 osrfHash* kid_link = osrfHashGet( links, link_field );
5480                                                 if( !kid_link )
5481                                                         continue;     // Not a link field; skip it
5482
5483                                                 osrfHash* field = osrfHashGet( fields, link_field );
5484                                                 if( !field )
5485                                                         continue;     // Not a field at all; skip it (IDL is ill-formed)
5486
5487                                                 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5488                                                         osrfHashGet( kid_link, "class" ));
5489                                                 if( !kid_idl )
5490                                                         continue;   // The class it links to doesn't exist; skip it
5491
5492                                                 const char* reltype = osrfHashGet( kid_link, "reltype" );
5493                                                 if( !reltype )
5494                                                         continue;   // No reltype; skip it (IDL is ill-formed)
5495
5496                                                 osrfHash* value_field = field;
5497
5498                                                 if(    !strcmp( reltype, "has_many" )
5499                                                         || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5500                                                         value_field = osrfHashGet(
5501                                                                 fields, osrfHashGet( class_meta, "primarykey" ) );
5502                                                 }
5503
5504                                                 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5505
5506                                                 if( link_map->size > 0 ) {
5507                                                         jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5508                                                         jsonObjectPush(
5509                                                                 _kid_key,
5510                                                                 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5511                                                         );
5512
5513                                                         jsonObjectSetKey(
5514                                                                 flesh_blob,
5515                                                                 osrfHashGet( kid_link, "class" ),
5516                                                                 _kid_key
5517                                                         );
5518                                                 };
5519
5520                                                 osrfLogDebug(
5521                                                         OSRF_LOG_MARK,
5522                                                         "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5523                                                         osrfHashGet( kid_link, "field" ),
5524                                                         osrfHashGet( kid_link, "class" ),
5525                                                         osrfHashGet( kid_link, "key" ),
5526                                                         osrfHashGet( kid_link, "reltype" )
5527                                                 );
5528
5529                                                 const char* search_key = jsonObjectGetString(
5530                                                         jsonObjectGetIndex( cur,
5531                                                                 atoi( osrfHashGet( value_field, "array_position" ) )
5532                                                         )
5533                                                 );
5534
5535                                                 if( !search_key ) {
5536                                                         osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5537                                                         continue;
5538                                                 }
5539
5540                                                 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5541
5542                                                 // construct WHERE clause
5543                                                 jsonObject* where_clause  = jsonNewObjectType( JSON_HASH );
5544                                                 jsonObjectSetKey(
5545                                                         where_clause,
5546                                                         osrfHashGet( kid_link, "key" ),
5547                                                         jsonNewObject( search_key )
5548                                                 );
5549
5550                                                 // construct the rest of the query, mostly
5551                                                 // by copying pieces of the previous level of query
5552                                                 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5553                                                 jsonObjectSetKey( rest_of_query, "flesh",
5554                                                         jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5555                                                 );
5556
5557                                                 if( flesh_blob )
5558                                                         jsonObjectSetKey( rest_of_query, "flesh_fields",
5559                                                                 jsonObjectClone( flesh_blob ));
5560
5561                                                 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5562                                                         jsonObjectSetKey( rest_of_query, "order_by",
5563                                                                 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5564                                                         );
5565                                                 }
5566
5567                                                 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5568                                                         jsonObjectSetKey( rest_of_query, "select",
5569                                                                 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5570                                                         );
5571                                                 }
5572
5573                                                 // do the query, recursively, to expand the fleshable field
5574                                                 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5575                                                         where_clause, rest_of_query, err );
5576
5577                                                 jsonObjectFree( where_clause );
5578                                                 jsonObjectFree( rest_of_query );
5579
5580                                                 if( *err ) {
5581                                                         osrfStringArrayFree( link_fields );
5582                                                         jsonObjectFree( res_list );
5583                                                         jsonObjectFree( flesh_blob );
5584                                                         return NULL;
5585                                                 }
5586
5587                                                 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5588                                                         osrfHashGet( kid_link, "class" ), kids->size );
5589
5590                                                 // Traverse the result set
5591                                                 jsonObject* X = NULL;
5592                                                 if( link_map->size > 0 && kids->size > 0 ) {
5593                                                         X = kids;
5594                                                         kids = jsonNewObjectType( JSON_ARRAY );
5595
5596                                                         jsonObject* _k_node;
5597                                                         unsigned long res_idx = 0;
5598                                                         while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5599                                                                 jsonObjectPush(
5600                                                                         kids,
5601                                                                         jsonObjectClone(
5602                                                                                 jsonObjectGetIndex(
5603                                                                                         _k_node,
5604                                                                                         (unsigned long) atoi(
5605                                                                                                 osrfHashGet(
5606                                                                                                         osrfHashGet(
5607                                                                                                                 osrfHashGet(
5608                                                                                                                         osrfHashGet(
5609                                                                                                                                 oilsIDL(),
5610                                                                                                                                 osrfHashGet( kid_link, "class" )
5611                                                                                                                         ),
5612                                                                                                                         "fields"
5613                                                                                                                 ),
5614                                                                                                                 osrfStringArrayGetString( link_map, 0 )
5615                                                                                                         ),
5616                                                                                                         "array_position"
5617                                                                                                 )
5618                                                                                         )
5619                                                                                 )
5620                                                                         )
5621                                                                 );
5622                                                         } // end while loop traversing X
5623                                                 }
5624
5625                                                 if(    !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5626                                                         || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5627                                                         osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5628                                                                 osrfHashGet( kid_link, "field" ));
5629                                                         jsonObjectSetIndex(
5630                                                                 cur,
5631                                                                 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5632                                                                 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5633                                                         );
5634                                                 }
5635
5636                                                 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5637                                                         // has_many
5638                                                         osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5639                                                                 osrfHashGet( kid_link, "field" ) );
5640                                                         jsonObjectSetIndex(
5641                                                                 cur,
5642                                                                 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5643                                                                 jsonObjectClone( kids )
5644                                                         );
5645                                                 }
5646
5647                                                 if( X ) {
5648                                                         jsonObjectFree( kids );
5649                                                         kids = X;
5650                                                 }
5651
5652                                                 jsonObjectFree( kids );
5653
5654                                                 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5655                                                         osrfHashGet( kid_link, "field" ) );
5656                                                 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5657
5658                                         } // end while loop traversing list of fleshable fields
5659                                 } // end while loop traversing res_list
5660                                 jsonObjectFree( flesh_blob );
5661                                 osrfStringArrayFree( link_fields );
5662                         }
5663                 }
5664         }
5665
5666         return res_list;
5667 }
5668
5669
5670 int doUpdate( osrfMethodContext* ctx ) {
5671         if( osrfMethodVerifyContext( ctx )) {
5672                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5673                 return -1;
5674         }
5675
5676         if( enforce_pcrud )
5677                 timeout_needs_resetting = 1;
5678
5679         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5680
5681         jsonObject* target = NULL;
5682         if( enforce_pcrud )
5683                 target = jsonObjectGetIndex( ctx->params, 1 );
5684         else
5685                 target = jsonObjectGetIndex( ctx->params, 0 );
5686
5687         if(!verifyObjectClass( ctx, target )) {
5688                 osrfAppRespondComplete( ctx, NULL );
5689                 return -1;
5690         }
5691
5692         if( getXactId( ctx ) == NULL ) {
5693                 osrfAppSessionStatus(
5694                         ctx->session,
5695                         OSRF_STATUS_BADREQUEST,
5696                         "osrfMethodException",
5697                         ctx->request,
5698                         "No active transaction -- required for UPDATE"
5699                 );
5700                 osrfAppRespondComplete( ctx, NULL );
5701                 return -1;
5702         }
5703
5704         // The following test is harmless but redundant.  If a class is
5705         // readonly, we don't register an update method for it.
5706         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5707                 osrfAppSessionStatus(
5708                         ctx->session,
5709                         OSRF_STATUS_BADREQUEST,
5710                         "osrfMethodException",
5711                         ctx->request,
5712                         "Cannot UPDATE readonly class"
5713                 );
5714                 osrfAppRespondComplete( ctx, NULL );
5715                 return -1;
5716         }
5717
5718         const char* trans_id = getXactId( ctx );
5719
5720         // Set the last_xact_id
5721         int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5722         if( index > -1 ) {
5723                 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5724                                 trans_id, target->classname, index );
5725                 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5726         }
5727
5728         char* pkey = osrfHashGet( meta, "primarykey" );
5729         osrfHash* fields = osrfHashGet( meta, "fields" );
5730
5731         char* id = oilsFMGetString( target, pkey );
5732
5733         osrfLogDebug(
5734                 OSRF_LOG_MARK,
5735                 "%s updating %s object with %s = %s",
5736                 modulename,
5737                 osrfHashGet( meta, "fieldmapper" ),
5738                 pkey,
5739                 id
5740         );
5741
5742         dbhandle = writehandle;
5743         growing_buffer* sql = buffer_init( 128 );
5744         buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5745
5746         int first = 1;
5747         osrfHash* field_def = NULL;
5748         osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5749         while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5750
5751                 // Skip virtual fields, and the primary key
5752                 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5753                         continue;
5754
5755                 const char* field_name = osrfHashIteratorKey( field_itr );
5756                 if( ! strcmp( field_name, pkey ) )
5757                         continue;
5758
5759                 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5760
5761                 int value_is_numeric = 0;    // boolean
5762                 char* value;
5763                 if( field_object && field_object->classname ) {
5764                         value = oilsFMGetString(
5765                                 field_object,
5766                                 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5767                         );
5768                 } else if( field_object && JSON_BOOL == field_object->type ) {
5769                         if( jsonBoolIsTrue( field_object ) )
5770                                 value = strdup( "t" );
5771                         else
5772                                 value = strdup( "f" );
5773                 } else {
5774                         value = jsonObjectToSimpleString( field_object );
5775                         if( field_object && JSON_NUMBER == field_object->type )
5776                                 value_is_numeric = 1;
5777                 }
5778
5779                 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5780                                 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5781
5782                 if( !field_object || field_object->type == JSON_NULL ) {
5783                         if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5784                                         && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5785                                 if( first )
5786                                         first = 0;
5787                                 else
5788                                         OSRF_BUFFER_ADD_CHAR( sql, ',' );
5789                                 buffer_fadd( sql, " %s = NULL", field_name );
5790                         }
5791
5792                 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5793                         if( first )
5794                                 first = 0;
5795                         else
5796                                 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5797
5798                         const char* numtype = get_datatype( field_def );
5799                         if( !strncmp( numtype, "INT", 3 ) ) {
5800                                 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5801                         } else if( !strcmp( numtype, "NUMERIC" ) ) {
5802                                 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5803                         } else {
5804                                 // Must really be intended as a string, so quote it
5805                                 if( dbi_conn_quote_string( dbhandle, &value )) {
5806                                         buffer_fadd( sql, " %s = %s", field_name, value );
5807                                 } else {
5808                                         osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5809                                                 modulename, value );
5810                                         osrfAppSessionStatus(
5811                                                 ctx->session,
5812                                                 OSRF_STATUS_INTERNALSERVERERROR,
5813                                                 "osrfMethodException",
5814                                                 ctx->request,
5815                                                 "Error quoting string -- please see the error log for more details"
5816                                         );
5817                                         free( value );
5818                                         free( id );
5819                                         osrfHashIteratorFree( field_itr );
5820                                         buffer_free( sql );
5821                                         osrfAppRespondComplete( ctx, NULL );
5822                                         return -1;
5823                                 }
5824                         }
5825
5826                         osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5827
5828                 } else {
5829                         if( dbi_conn_quote_string( dbhandle, &value ) ) {
5830                                 if( first )
5831                                         first = 0;
5832                                 else
5833                                         OSRF_BUFFER_ADD_CHAR( sql, ',' );
5834                                 buffer_fadd( sql, " %s = %s", field_name, value );
5835                         } else {
5836                                 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5837                                 osrfAppSessionStatus(
5838                                         ctx->session,
5839                                         OSRF_STATUS_INTERNALSERVERERROR,
5840                                         "osrfMethodException",
5841                                         ctx->request,
5842                                         "Error quoting string -- please see the error log for more details"
5843                                 );
5844                                 free( value );
5845                                 free( id );
5846                                 osrfHashIteratorFree( field_itr );
5847                                 buffer_free( sql );
5848                                 osrfAppRespondComplete( ctx, NULL );
5849                                 return -1;
5850                         }
5851                 }
5852
5853                 free( value );
5854
5855         } // end while
5856
5857         osrfHashIteratorFree( field_itr );
5858
5859         jsonObject* obj = jsonNewObject( id );
5860
5861         if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
5862                 dbi_conn_quote_string( dbhandle, &id );
5863
5864         buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5865
5866         char* query = buffer_release( sql );
5867         osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
5868
5869         dbi_result result = dbi_conn_query( dbhandle, query );
5870         free( query );
5871
5872         int rc = 0;
5873         if( !result ) {
5874                 jsonObjectFree( obj );
5875                 obj = jsonNewObject( NULL );
5876                 const char* msg;
5877                 int errnum = dbi_conn_error( dbhandle, &msg );
5878                 osrfLogError(
5879                         OSRF_LOG_MARK,
5880                         "%s ERROR updating %s object with %s = %s: %d %s",
5881                         modulename,
5882                         osrfHashGet( meta, "fieldmapper" ),
5883                         pkey,
5884                         id,
5885                         errnum,
5886                         msg ? msg : "(No description available)"
5887                 );
5888                 osrfAppSessionStatus(
5889                         ctx->session,
5890                         OSRF_STATUS_INTERNALSERVERERROR,
5891                         "osrfMethodException",
5892                         ctx->request,
5893                         "Error in updating a row -- please see the error log for more details"
5894                 );
5895                 if( !oilsIsDBConnected( dbhandle ))
5896                         osrfAppSessionPanic( ctx->session );
5897                 rc = -1;
5898         } else
5899                 dbi_result_free( result );
5900
5901         free( id );
5902         osrfAppRespondComplete( ctx, obj );
5903         jsonObjectFree( obj );
5904         return rc;
5905 }
5906
5907 int doDelete( osrfMethodContext* ctx ) {
5908         if( osrfMethodVerifyContext( ctx )) {
5909                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5910                 return -1;
5911         }
5912
5913         if( enforce_pcrud )
5914                 timeout_needs_resetting = 1;
5915
5916         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5917
5918         if( getXactId( ctx ) == NULL ) {
5919                 osrfAppSessionStatus(
5920                         ctx->session,
5921                         OSRF_STATUS_BADREQUEST,
5922                         "osrfMethodException",
5923                         ctx->request,
5924                         "No active transaction -- required for DELETE"
5925                 );
5926                 osrfAppRespondComplete( ctx, NULL );
5927                 return -1;
5928         }
5929
5930         // The following test is harmless but redundant.  If a class is
5931         // readonly, we don't register a delete method for it.
5932         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5933                 osrfAppSessionStatus(
5934                         ctx->session,
5935                         OSRF_STATUS_BADREQUEST,
5936                         "osrfMethodException",
5937                         ctx->request,
5938                         "Cannot DELETE readonly class"
5939                 );
5940                 osrfAppRespondComplete( ctx, NULL );
5941                 return -1;
5942         }
5943
5944         dbhandle = writehandle;
5945
5946         char* pkey = osrfHashGet( meta, "primarykey" );
5947
5948         int _obj_pos = 0;
5949         if( enforce_pcrud )
5950                 _obj_pos = 1;
5951
5952         char* id;
5953         if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
5954                 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5955                         osrfAppRespondComplete( ctx, NULL );
5956                         return -1;
5957                 }
5958
5959                 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5960         } else {
5961                 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5962                         osrfAppRespondComplete( ctx, NULL );
5963                         return -1;
5964                 }
5965                 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
5966         }
5967
5968         osrfLogDebug(
5969                 OSRF_LOG_MARK,
5970                 "%s deleting %s object with %s = %s",
5971                 modulename,
5972                 osrfHashGet( meta, "fieldmapper" ),
5973                 pkey,
5974                 id
5975         );
5976
5977         jsonObject* obj = jsonNewObject( id );
5978
5979         if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5980                 dbi_conn_quote_string( writehandle, &id );
5981
5982         dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
5983                 osrfHashGet( meta, "tablename" ), pkey, id );
5984
5985         int rc = 0;
5986         if( !result ) {
5987                 rc = -1;
5988                 jsonObjectFree( obj );
5989                 obj = jsonNewObject( NULL );
5990                 const char* msg;
5991                 int errnum = dbi_conn_error( writehandle, &msg );
5992                 osrfLogError(
5993                         OSRF_LOG_MARK,
5994                         "%s ERROR deleting %s object with %s = %s: %d %s",
5995                         modulename,
5996                         osrfHashGet( meta, "fieldmapper" ),
5997                         pkey,
5998                         id,
5999                         errnum,
6000                         msg ? msg : "(No description available)"
6001                 );
6002                 osrfAppSessionStatus(
6003                         ctx->session,
6004                         OSRF_STATUS_INTERNALSERVERERROR,
6005                         "osrfMethodException",
6006                         ctx->request,
6007                         "Error in deleting a row -- please see the error log for more details"
6008                 );
6009                 if( !oilsIsDBConnected( writehandle ))
6010                         osrfAppSessionPanic( ctx->session );
6011         } else
6012                 dbi_result_free( result );
6013
6014         free( id );
6015
6016         osrfAppRespondComplete( ctx, obj );
6017         jsonObjectFree( obj );
6018         return rc;
6019 }
6020
6021 /**
6022         @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6023         @param result An iterator for a result set; we only look at the current row.
6024         @param @meta Pointer to the class metadata for the core class.
6025         @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6026
6027         If a column is not defined in the IDL, or if it has no array_position defined for it in
6028         the IDL, or if it is defined as virtual, ignore it.
6029
6030         Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6031         or JSON_STRING.  Then insert this jsonObject into the JSON_ARRAY according to its
6032         array_position in the IDL.
6033
6034         A field defined in the IDL but not represented in the returned row will leave a hole
6035         in the JSON_ARRAY.  In effect it will be treated as a null value.
6036
6037         In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6038         regardless of their sequence in the SELECT statement.  The JSON_ARRAY is assigned the
6039         classname corresponding to the @a meta argument.
6040
6041         The calling code is responsible for freeing the the resulting jsonObject by calling
6042         jsonObjectFree().
6043 */
6044 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6045         if( !( result && meta )) return NULL;
6046
6047         jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6048         jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6049         osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6050
6051         osrfHash* fields = osrfHashGet( meta, "fields" );
6052
6053         int columnIndex = 1;
6054         const char* columnName;
6055
6056         /* cycle through the columns in the row returned from the database */
6057         while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6058
6059                 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6060
6061                 int fmIndex = -1;  // Will be set to the IDL's sequence number for this field
6062
6063                 /* determine the field type and storage attributes */
6064                 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6065                 int attr            = dbi_result_get_field_attribs_idx( result, columnIndex );
6066
6067                 // Fetch the IDL's sequence number for the field.  If the field isn't in the IDL,
6068                 // or if it has no sequence number there, or if it's virtual, skip it.
6069                 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6070                 if( _f ) {
6071
6072                         if( str_is_true( osrfHashGet( _f, "virtual" )))
6073                                 continue;   // skip this column: IDL says it's virtual
6074
6075                         const char* pos = (char*) osrfHashGet( _f, "array_position" );
6076                         if( !pos )      // IDL has no sequence number for it.  This shouldn't happen,
6077                                 continue;    // since we assign sequence numbers dynamically as we load the IDL.
6078
6079                         fmIndex = atoi( pos );
6080                         osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6081                 } else {
6082                         continue;     // This field is not defined in the IDL
6083                 }
6084
6085                 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6086                 // sequence number from the IDL (which is likely to be different from the sequence
6087                 // of columns in the SELECT clause).
6088                 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6089                         jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6090                 } else {
6091
6092                         switch( type ) {
6093
6094                                 case DBI_TYPE_INTEGER :
6095
6096                                         if( attr & DBI_INTEGER_SIZE8 )
6097                                                 jsonObjectSetIndex( object, fmIndex,
6098                                                         jsonNewNumberObject(
6099                                                                 dbi_result_get_longlong_idx( result, columnIndex )));
6100                                         else
6101                                                 jsonObjectSetIndex( object, fmIndex,
6102                                                         jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6103
6104                                         break;
6105
6106                                 case DBI_TYPE_DECIMAL :
6107                                         jsonObjectSetIndex( object, fmIndex,
6108                                                         jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6109                                         break;
6110
6111                                 case DBI_TYPE_STRING :
6112
6113                                         jsonObjectSetIndex(
6114                                                 object,
6115                                                 fmIndex,
6116                                                 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6117                                         );
6118
6119                                         break;
6120
6121                                 case DBI_TYPE_DATETIME : {
6122
6123                                         char dt_string[ 256 ] = "";
6124                                         struct tm gmdt;
6125
6126                                         // Fetch the date column as a time_t
6127                                         time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6128
6129                                         // Translate the time_t to a human-readable string
6130                                         if( !( attr & DBI_DATETIME_DATE )) {
6131                                                 gmtime_r( &_tmp_dt, &gmdt );
6132                                                 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6133                                         } else if( !( attr & DBI_DATETIME_TIME )) {
6134                                                 localtime_r( &_tmp_dt, &gmdt );
6135                                                 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6136                                         } else {
6137                                                 localtime_r( &_tmp_dt, &gmdt );
6138                                                 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6139                                         }
6140
6141                                         jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6142
6143                                         break;
6144                                 }
6145                                 case DBI_TYPE_BINARY :
6146                                         osrfLogError( OSRF_LOG_MARK,
6147                                                 "Can't do binary at column %s : index %d", columnName, columnIndex );
6148                         } // End switch
6149                 }
6150                 ++columnIndex;
6151         } // End while
6152
6153         return object;
6154 }
6155
6156 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6157         if( !result ) return NULL;
6158
6159         jsonObject* object = jsonNewObject( NULL );
6160
6161         time_t _tmp_dt;
6162         char dt_string[ 256 ];
6163         struct tm gmdt;
6164
6165         int fmIndex;
6166         int columnIndex = 1;
6167         int attr;
6168         unsigned short type;
6169         const char* columnName;
6170
6171         /* cycle through the column list */
6172         while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6173
6174                 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6175
6176                 fmIndex = -1; // reset the position
6177
6178                 /* determine the field type and storage attributes */
6179                 type = dbi_result_get_field_type_idx( result, columnIndex );
6180                 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6181
6182                 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6183                         jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6184                 } else {
6185
6186                         switch( type ) {
6187
6188                                 case DBI_TYPE_INTEGER :
6189
6190                                         if( attr & DBI_INTEGER_SIZE8 )
6191                                                 jsonObjectSetKey( object, columnName,
6192                                                                 jsonNewNumberObject( dbi_result_get_longlong_idx(
6193                                                                                 result, columnIndex )) );
6194                                         else
6195                                                 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6196                                                                 dbi_result_get_int_idx( result, columnIndex )) );
6197                                         break;
6198
6199                                 case DBI_TYPE_DECIMAL :
6200                                         jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6201                                                 dbi_result_get_double_idx( result, columnIndex )) );
6202                                         break;
6203
6204                                 case DBI_TYPE_STRING :
6205                                         jsonObjectSetKey( object, columnName,
6206                                                 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6207                                         break;
6208
6209                                 case DBI_TYPE_DATETIME :
6210
6211                                         memset( dt_string, '\0', sizeof( dt_string ));
6212                                         memset( &gmdt, '\0', sizeof( gmdt ));
6213
6214                                         _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6215
6216                                         if( !( attr & DBI_DATETIME_DATE )) {
6217                                                 gmtime_r( &_tmp_dt, &gmdt );
6218                                                 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6219                                         } else if( !( attr & DBI_DATETIME_TIME )) {
6220                                                 localtime_r( &_tmp_dt, &gmdt );
6221                                                 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6222                                         } else {
6223                                                 localtime_r( &_tmp_dt, &gmdt );
6224                                                 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6225                                         }
6226
6227                                         jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6228                                         break;
6229
6230                                 case DBI_TYPE_BINARY :
6231                                         osrfLogError( OSRF_LOG_MARK,
6232                                                 "Can't do binary at column %s : index %d", columnName, columnIndex );
6233                         }
6234                 }
6235                 ++columnIndex;
6236         } // end while loop traversing result
6237
6238         return object;
6239 }
6240
6241 // Interpret a string as true or false
6242 int str_is_true( const char* str ) {
6243         if( NULL == str || strcasecmp( str, "true" ) )
6244                 return 0;
6245         else
6246                 return 1;
6247 }
6248
6249 // Interpret a jsonObject as true or false
6250 static int obj_is_true( const jsonObject* obj ) {
6251         if( !obj )
6252                 return 0;
6253         else switch( obj->type )
6254         {
6255                 case JSON_BOOL :
6256                         if( obj->value.b )
6257                                 return 1;
6258                         else
6259                                 return 0;
6260                 case JSON_STRING :
6261                         if( strcasecmp( obj->value.s, "true" ) )
6262                                 return 0;
6263                         else
6264                                 return 1;
6265                 case JSON_NUMBER :          // Support 1/0 for perl's sake
6266                         if( jsonObjectGetNumber( obj ) == 1.0 )
6267                                 return 1;
6268                         else
6269                                 return 0;
6270                 default :
6271                         return 0;
6272         }
6273 }
6274
6275 // Translate a numeric code into a text string identifying a type of
6276 // jsonObject.  To be used for building error messages.
6277 static const char* json_type( int code ) {
6278         switch ( code )
6279         {
6280                 case 0 :
6281                         return "JSON_HASH";
6282                 case 1 :
6283                         return "JSON_ARRAY";
6284                 case 2 :
6285                         return "JSON_STRING";
6286                 case 3 :
6287                         return "JSON_NUMBER";
6288                 case 4 :
6289                         return "JSON_NULL";
6290                 case 5 :
6291                         return "JSON_BOOL";
6292                 default :
6293                         return "(unrecognized)";
6294         }
6295 }
6296
6297 // Extract the "primitive" attribute from an IDL field definition.
6298 // If we haven't initialized the app, then we must be running in
6299 // some kind of testbed.  In that case, default to "string".
6300 static const char* get_primitive( osrfHash* field ) {
6301         const char* s = osrfHashGet( field, "primitive" );
6302         if( !s ) {
6303                 if( child_initialized )
6304                         osrfLogError(
6305                                 OSRF_LOG_MARK,
6306                                 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6307                                 modulename,
6308                                 osrfHashGet( field, "name" )
6309                         );
6310
6311                 s = "string";
6312         }
6313         return s;
6314 }
6315
6316 // Extract the "datatype" attribute from an IDL field definition.
6317 // If we haven't initialized the app, then we must be running in
6318 // some kind of testbed.  In that case, default to to NUMERIC,
6319 // since we look at the datatype only for numbers.
6320 static const char* get_datatype( osrfHash* field ) {
6321         const char* s = osrfHashGet( field, "datatype" );
6322         if( !s ) {
6323                 if( child_initialized )
6324                         osrfLogError(
6325                                 OSRF_LOG_MARK,
6326                                 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6327                                 modulename,
6328                                 osrfHashGet( field, "name" )
6329                         );
6330                 else
6331                         s = "NUMERIC";
6332         }
6333         return s;
6334 }
6335
6336 /**
6337         @brief Determine whether a string is potentially a valid SQL identifier.
6338         @param s The identifier to be tested.
6339         @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6340
6341         Purpose: to prevent certain kinds of SQL injection.  To that end we don't necessarily
6342         need to follow all the rules exactly, such as requiring that the first character not
6343         be a digit.
6344
6345         We allow leading and trailing white space.  In between, we do not allow punctuation
6346         (except for underscores and dollar signs), control characters, or embedded white space.
6347
6348         More pedantically we should allow quoted identifiers containing arbitrary characters, but
6349         for the foreseeable future such quoted identifiers are not likely to be an issue.
6350 */
6351 int is_identifier( const char* s) {
6352         if( !s )
6353                 return 0;
6354
6355         // Skip leading white space
6356         while( isspace( (unsigned char) *s ) )
6357                 ++s;
6358
6359         if( !s )
6360                 return 0;   // Nothing but white space?  Not okay.
6361
6362         // Check each character until we reach white space or
6363         // end-of-string.  Letters, digits, underscores, and
6364         // dollar signs are okay. With the exception of periods
6365         // (as in schema.identifier), control characters and other
6366         // punctuation characters are not okay.  Anything else
6367         // is okay -- it could for example be part of a multibyte
6368         // UTF8 character such as a letter with diacritical marks,
6369         // and those are allowed.
6370         do {
6371                 if( isalnum( (unsigned char) *s )
6372                         || '.' == *s
6373                         || '_' == *s
6374                         || '$' == *s )
6375                         ;  // Fine; keep going
6376                 else if(   ispunct( (unsigned char) *s )
6377                                 || iscntrl( (unsigned char) *s ) )
6378                         return 0;
6379                         ++s;
6380         } while( *s && ! isspace( (unsigned char) *s ) );
6381
6382         // If we found any white space in the above loop,
6383         // the rest had better be all white space.
6384
6385         while( isspace( (unsigned char) *s ) )
6386                 ++s;
6387
6388         if( *s )
6389                 return 0;   // White space was embedded within non-white space
6390
6391         return 1;
6392 }
6393
6394 /**
6395         @brief Determine whether to accept a character string as a comparison operator.
6396         @param op The candidate comparison operator.
6397         @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6398
6399         We don't validate the operator for real.  We just make sure that it doesn't contain
6400         any semicolons or white space (with special exceptions for a few specific operators).
6401         The idea is to block certain kinds of SQL injection.  If it has no semicolons or white
6402         space but it's still not a valid operator, then the database will complain.
6403
6404         Another approach would be to compare the string against a short list of approved operators.
6405         We don't do that because we want to allow custom operators like ">100*", which at this
6406         writing would be difficult or impossible to express otherwise in a JSON query.
6407 */
6408 int is_good_operator( const char* op ) {
6409         if( !op ) return 0;   // Sanity check
6410
6411         const char* s = op;
6412         while( *s ) {
6413                 if( isspace( (unsigned char) *s ) ) {
6414                         // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6415                         // and IS NOT DISTINCT FROM.
6416                         if( !strcasecmp( op, "similar to" ) )
6417                                 return 1;
6418                         else if( !strcasecmp( op, "is distinct from" ) )
6419                                 return 1;
6420                         else if( !strcasecmp( op, "is not distinct from" ) )
6421                                 return 1;
6422                         else
6423                                 return 0;
6424                 }
6425                 else if( ';' == *s )
6426                         return 0;
6427                 ++s;
6428         }
6429         return 1;
6430 }
6431
6432 /**
6433         @name Query Frame Management
6434
6435         The following machinery supports a stack of query frames for use by SELECT().
6436
6437         A query frame caches information about one level of a SELECT query.  When we enter
6438         a subquery, we push another query frame onto the stack, and pop it off when we leave.
6439
6440         The query frame stores information about the core class, and about any joined classes
6441         in the FROM clause.
6442
6443         The main purpose is to map table aliases to classes and tables, so that a query can
6444         join to the same table more than once.  A secondary goal is to reduce the number of
6445         lookups in the IDL by caching the results.
6446 */
6447 /*@{*/
6448
6449 #define STATIC_CLASS_INFO_COUNT 3
6450
6451 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6452
6453 /**
6454         @brief Allocate a ClassInfo as raw memory.
6455         @return Pointer to the newly allocated ClassInfo.
6456
6457         Except for the in_use flag, which is used only by the allocation and deallocation
6458         logic, we don't initialize the ClassInfo here.
6459 */
6460 static ClassInfo* allocate_class_info( void ) {
6461         // In order to reduce the number of mallocs and frees, we return a static
6462         // instance of ClassInfo, if we can find one that we're not already using.
6463         // We rely on the fact that the compiler will implicitly initialize the
6464         // static instances so that in_use == 0.
6465
6466         int i;
6467         for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6468                 if( ! static_class_info[ i ].in_use ) {
6469                         static_class_info[ i ].in_use = 1;
6470                         return static_class_info + i;
6471                 }
6472         }
6473
6474         // The static ones are all in use.  Malloc one.
6475
6476         return safe_malloc( sizeof( ClassInfo ) );
6477 }
6478
6479 /**
6480         @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6481         @param info Pointer to the ClassInfo to be cleared.
6482 */
6483 static void clear_class_info( ClassInfo* info ) {
6484         // Sanity check
6485         if( ! info )
6486                 return;
6487
6488         // Free any malloc'd strings
6489
6490         if( info->alias != info->alias_store )
6491                 free( info->alias );
6492
6493         if( info->class_name != info->class_name_store )
6494                 free( info->class_name );
6495
6496         free( info->source_def );
6497
6498         info->alias = info->class_name = info->source_def = NULL;
6499         info->next = NULL;
6500 }
6501
6502 /**
6503         @brief Free a ClassInfo and everything it owns.
6504         @param info Pointer to the ClassInfo to be freed.
6505 */
6506 static void free_class_info( ClassInfo* info ) {
6507         // Sanity check
6508         if( ! info )
6509                 return;
6510
6511         clear_class_info( info );
6512
6513         // If it's one of the static instances, just mark it as not in use
6514
6515         int i;
6516         for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6517                 if( info == static_class_info + i ) {
6518                         static_class_info[ i ].in_use = 0;
6519                         return;
6520                 }
6521         }
6522
6523         // Otherwise it must have been malloc'd, so free it
6524
6525         free( info );
6526 }
6527
6528 /**
6529         @brief Populate an already-allocated ClassInfo.
6530         @param info Pointer to the ClassInfo to be populated.
6531         @param alias Alias for the class.  If it is NULL, or an empty string, use the class
6532         name for an alias.
6533         @param class Name of the class.
6534         @return Zero if successful, or 1 if not.
6535
6536         Populate the ClassInfo with copies of the alias and class name, and with pointers to
6537         the relevant portions of the IDL for the specified class.
6538 */
6539 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6540         // Sanity checks
6541         if( ! info ){
6542                 osrfLogError( OSRF_LOG_MARK,
6543                                           "%s ERROR: No ClassInfo available to populate", modulename );
6544                 info->alias = info->class_name = info->source_def = NULL;
6545                 info->class_def = info->fields = info->links = NULL;
6546                 return 1;
6547         }
6548
6549         if( ! class ) {
6550                 osrfLogError( OSRF_LOG_MARK,
6551                                           "%s ERROR: No class name provided for lookup", modulename );
6552                 info->alias = info->class_name = info->source_def = NULL;
6553                 info->class_def = info->fields = info->links = NULL;
6554                 return 1;
6555         }
6556
6557         // Alias defaults to class name if not supplied
6558         if( ! alias || ! alias[ 0 ] )
6559                 alias = class;
6560
6561         // Look up class info in the IDL
6562         osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6563         if( ! class_def ) {
6564                 osrfLogError( OSRF_LOG_MARK,
6565                                           "%s ERROR: Class %s not defined in IDL", modulename, class );
6566                 info->alias = info->class_name = info->source_def = NULL;
6567                 info->class_def = info->fields = info->links = NULL;
6568                 return 1;
6569         } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6570                 osrfLogError( OSRF_LOG_MARK,
6571                                           "%s ERROR: Class %s is defined as virtual", modulename, class );
6572                 info->alias = info->class_name = info->source_def = NULL;
6573                 info->class_def = info->fields = info->links = NULL;
6574                 return 1;
6575         }
6576
6577         osrfHash* links = osrfHashGet( class_def, "links" );
6578         if( ! links ) {
6579                 osrfLogError( OSRF_LOG_MARK,
6580                                           "%s ERROR: No links defined in IDL for class %s", modulename, class );
6581                 info->alias = info->class_name = info->source_def = NULL;
6582                 info->class_def = info->fields = info->links = NULL;
6583                 return 1;
6584         }
6585
6586         osrfHash* fields = osrfHashGet( class_def, "fields" );
6587         if( ! fields ) {
6588                 osrfLogError( OSRF_LOG_MARK,
6589                                           "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6590                 info->alias = info->class_name = info->source_def = NULL;
6591                 info->class_def = info->fields = info->links = NULL;
6592                 return 1;
6593         }
6594
6595         char* source_def = oilsGetRelation( class_def );
6596         if( ! source_def )
6597                 return 1;
6598
6599         // We got everything we need, so populate the ClassInfo
6600         if( strlen( alias ) > ALIAS_STORE_SIZE )
6601                 info->alias = strdup( alias );
6602         else {
6603                 strcpy( info->alias_store, alias );
6604                 info->alias = info->alias_store;
6605         }
6606
6607         if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6608                 info->class_name = strdup( class );
6609         else {
6610                 strcpy( info->class_name_store, class );
6611                 info->class_name = info->class_name_store;
6612         }
6613
6614         info->source_def = source_def;
6615
6616         info->class_def = class_def;
6617         info->links     = links;
6618         info->fields    = fields;
6619
6620         return 0;
6621 }
6622
6623 #define STATIC_FRAME_COUNT 3
6624
6625 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6626
6627 /**
6628         @brief Allocate a QueryFrame as raw memory.
6629         @return Pointer to the newly allocated QueryFrame.
6630
6631         Except for the in_use flag, which is used only by the allocation and deallocation
6632         logic, we don't initialize the QueryFrame here.
6633 */
6634 static QueryFrame* allocate_frame( void ) {
6635         // In order to reduce the number of mallocs and frees, we return a static
6636         // instance of QueryFrame, if we can find one that we're not already using.
6637         // We rely on the fact that the compiler will implicitly initialize the
6638         // static instances so that in_use == 0.
6639
6640         int i;
6641         for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6642                 if( ! static_frame[ i ].in_use ) {
6643                         static_frame[ i ].in_use = 1;
6644                         return static_frame + i;
6645                 }
6646         }
6647
6648         // The static ones are all in use.  Malloc one.
6649
6650         return safe_malloc( sizeof( QueryFrame ) );
6651 }
6652
6653 /**
6654         @brief Free a QueryFrame, and all the memory it owns.
6655         @param frame Pointer to the QueryFrame to be freed.
6656 */
6657 static void free_query_frame( QueryFrame* frame ) {
6658         // Sanity check
6659         if( ! frame )
6660                 return;
6661
6662         clear_class_info( &frame->core );
6663
6664         // Free the join list
6665         ClassInfo* temp;
6666         ClassInfo* info = frame->join_list;
6667         while( info ) {
6668                 temp = info->next;
6669                 free_class_info( info );
6670                 info = temp;
6671         }
6672
6673         frame->join_list = NULL;
6674         frame->next = NULL;
6675
6676         // If the frame is a static instance, just mark it as unused
6677         int i;
6678         for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6679                 if( frame == static_frame + i ) {
6680                         static_frame[ i ].in_use = 0;
6681                         return;
6682                 }
6683         }
6684
6685         // Otherwise it must have been malloc'd, so free it
6686
6687         free( frame );
6688 }
6689
6690 /**
6691         @brief Search a given QueryFrame for a specified alias.
6692         @param frame Pointer to the QueryFrame to be searched.
6693         @param target The alias for which to search.
6694         @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6695 */
6696 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6697         if( ! frame || ! target ) {
6698                 return NULL;
6699         }
6700
6701         ClassInfo* found_class = NULL;
6702
6703         if( !strcmp( target, frame->core.alias ) )
6704                 return &(frame->core);
6705         else {
6706                 ClassInfo* curr_class = frame->join_list;
6707                 while( curr_class ) {
6708                         if( strcmp( target, curr_class->alias ) )
6709                                 curr_class = curr_class->next;
6710                         else {
6711                                 found_class = curr_class;
6712                                 break;
6713                         }
6714                 }
6715         }
6716
6717         return found_class;
6718 }
6719
6720 /**
6721         @brief Push a new (blank) QueryFrame onto the stack.
6722 */
6723 static void push_query_frame( void ) {
6724         QueryFrame* frame = allocate_frame();
6725         frame->join_list = NULL;
6726         frame->next = curr_query;
6727
6728         // Initialize the ClassInfo for the core class
6729         ClassInfo* core = &frame->core;
6730         core->alias = core->class_name = core->source_def = NULL;
6731         core->class_def = core->fields = core->links = NULL;
6732
6733         curr_query = frame;
6734 }
6735
6736 /**
6737         @brief Pop a QueryFrame off the stack and destroy it.
6738 */
6739 static void pop_query_frame( void ) {
6740         // Sanity check
6741         if( ! curr_query )
6742                 return;
6743
6744         QueryFrame* popped = curr_query;
6745         curr_query = popped->next;
6746
6747         free_query_frame( popped );
6748 }
6749
6750 /**
6751         @brief Populate the ClassInfo for the core class.
6752         @param alias Alias for the core class.  If it is NULL or an empty string, we use the
6753         class name as an alias.
6754         @param class_name Name of the core class.
6755         @return Zero if successful, or 1 if not.
6756
6757         Populate the ClassInfo of the core class with copies of the alias and class name, and
6758         with pointers to the relevant portions of the IDL for the core class.
6759 */
6760 static int add_query_core( const char* alias, const char* class_name ) {
6761
6762         // Sanity checks
6763         if( ! curr_query ) {
6764                 osrfLogError( OSRF_LOG_MARK,
6765                                           "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6766                 return 1;
6767         } else if( curr_query->core.alias ) {
6768                 osrfLogError( OSRF_LOG_MARK,
6769                                           "%s ERROR: Core class %s already populated as %s",
6770                                           modulename, curr_query->core.class_name, curr_query->core.alias );
6771                 return 1;
6772         }
6773
6774         build_class_info( &curr_query->core, alias, class_name );
6775         if( curr_query->core.alias )
6776                 return 0;
6777         else {
6778                 osrfLogError( OSRF_LOG_MARK,
6779                                           "%s ERROR: Unable to look up core class %s", modulename, class_name );
6780                 return 1;
6781         }
6782 }
6783
6784 /**
6785         @brief Search the current QueryFrame for a specified alias.
6786         @param target The alias for which to search.
6787         @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6788 */
6789 static inline ClassInfo* search_alias( const char* target ) {
6790         return search_alias_in_frame( curr_query, target );
6791 }
6792
6793 /**
6794         @brief Search all levels of query for a specified alias, starting with the current query.
6795         @param target The alias for which to search.
6796         @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6797 */
6798 static ClassInfo* search_all_alias( const char* target ) {
6799         ClassInfo* found_class = NULL;
6800         QueryFrame* curr_frame = curr_query;
6801
6802         while( curr_frame ) {
6803                 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6804                         break;
6805                 else
6806                         curr_frame = curr_frame->next;
6807         }
6808
6809         return found_class;
6810 }
6811
6812 /**
6813         @brief Add a class to the list of classes joined to the current query.
6814         @param alias Alias of the class to be added.  If it is NULL or an empty string, we use
6815         the class name as an alias.
6816         @param classname The name of the class to be added.
6817         @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6818 */
6819 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6820
6821         if( ! classname || ! *classname ) {    // sanity check
6822                 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6823                 return NULL;
6824         }
6825
6826         if( ! alias )
6827                 alias = classname;
6828
6829         const ClassInfo* conflict = search_alias( alias );
6830         if( conflict ) {
6831                 osrfLogError( OSRF_LOG_MARK,
6832                                           "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6833                                           modulename, alias, conflict->class_name );
6834                 return NULL;
6835         }
6836
6837         ClassInfo* info = allocate_class_info();
6838
6839         if( build_class_info( info, alias, classname ) ) {
6840                 free_class_info( info );
6841                 return NULL;
6842         }
6843
6844         // Add the new ClassInfo to the join list of the current QueryFrame
6845         info->next = curr_query->join_list;
6846         curr_query->join_list = info;
6847
6848         return info;
6849 }
6850
6851 /**
6852         @brief Destroy all nodes on the query stack.
6853 */
6854 static void clear_query_stack( void ) {
6855         while( curr_query )
6856                 pop_query_frame();
6857 }
6858
6859 /*@}*/