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