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