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