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