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