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