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