]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/c-apps/oils_cstore.c
For create and update methods: accept boolean column values
[working/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 if( JSON_BOOL == field_object->type ) {
1501                         if( jsonBoolIsTrue( field_object ) )
1502                                 value = strdup( "t" );
1503                         else
1504                                 value = strdup( "f" );
1505                 } else {
1506                         value = jsonObjectToSimpleString( field_object );
1507                 }
1508
1509                 if (first) {
1510                         first = 0;
1511                 } else {
1512                         OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1513                         OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1514                 }
1515
1516                 buffer_add(col_buf, field_name);
1517
1518                 if (!field_object || field_object->type == JSON_NULL) {
1519                         buffer_add( val_buf, "DEFAULT" );
1520                         
1521                 } else if ( !strcmp(get_primitive( field ), "number") ) {
1522                         const char* numtype = get_datatype( field );
1523                         if ( !strcmp( numtype, "INT8") ) {
1524                                 buffer_fadd( val_buf, "%lld", atoll(value) );
1525                                 
1526                         } else if ( !strcmp( numtype, "INT") ) {
1527                                 buffer_fadd( val_buf, "%d", atoi(value) );
1528                                 
1529                         } else if ( !strcmp( numtype, "NUMERIC") ) {
1530                                 buffer_fadd( val_buf, "%f", atof(value) );
1531                         }
1532                 } else {
1533                         if ( dbi_conn_quote_string(writehandle, &value) ) {
1534                                 OSRF_BUFFER_ADD( val_buf, value );
1535
1536                         } else {
1537                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1538                                 osrfAppSessionStatus(
1539                                         ctx->session,
1540                                         OSRF_STATUS_INTERNALSERVERERROR,
1541                                         "osrfMethodException",
1542                                         ctx->request,
1543                                         "Error quoting string -- please see the error log for more details"
1544                                 );
1545                                 free(value);
1546                                 buffer_free(table_buf);
1547                                 buffer_free(col_buf);
1548                                 buffer_free(val_buf);
1549                                 *err = -1;
1550                                 return jsonNULL;
1551                         }
1552                 }
1553
1554                 free(value);
1555                 
1556         }
1557
1558         osrfHashIteratorFree( field_itr );
1559
1560         OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1561         OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1562
1563         char* table_str = buffer_release(table_buf);
1564         char* col_str   = buffer_release(col_buf);
1565         char* val_str   = buffer_release(val_buf);
1566         growing_buffer* sql = buffer_init(128);
1567         buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1568         free(table_str);
1569         free(col_str);
1570         free(val_str);
1571
1572         char* query = buffer_release(sql);
1573
1574         osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1575
1576         
1577         dbi_result result = dbi_conn_query(writehandle, query);
1578
1579         jsonObject* obj = NULL;
1580
1581         if (!result) {
1582                 obj = jsonNewObject(NULL);
1583                 osrfLogError(
1584                         OSRF_LOG_MARK,
1585                         "%s ERROR inserting %s object using query [%s]",
1586                         MODULENAME,
1587                         osrfHashGet(meta, "fieldmapper"),
1588                         query
1589                 );
1590                 osrfAppSessionStatus(
1591                         ctx->session,
1592                         OSRF_STATUS_INTERNALSERVERERROR,
1593                         "osrfMethodException",
1594                         ctx->request,
1595                         "INSERT error -- please see the error log for more details"
1596                 );
1597                 *err = -1;
1598         } else {
1599
1600                 char* id = oilsFMGetString(target, pkey);
1601                 if (!id) {
1602                         unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1603                         growing_buffer* _id = buffer_init(10);
1604                         buffer_fadd(_id, "%lld", new_id);
1605                         id = buffer_release(_id);
1606                 }
1607
1608                 // Find quietness specification, if present
1609                 const char* quiet_str = NULL;
1610                 if ( options ) {
1611                         const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1612                         if( quiet_obj )
1613                                 quiet_str = jsonObjectGetString( quiet_obj );
1614                 }
1615
1616                 if( str_is_true( quiet_str ) ) {  // if quietness is specified
1617                         obj = jsonNewObject(id);
1618                 }
1619                 else {
1620
1621                         jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1622                         jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1623
1624                         jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1625
1626                         jsonObjectFree( where_clause );
1627
1628                         if(*err) {
1629                                 obj = jsonNULL;
1630                         } else {
1631                                 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1632                         }
1633
1634                         jsonObjectFree( list );
1635                 }
1636
1637                 free(id);
1638         }
1639
1640         free(query);
1641
1642         return obj;
1643
1644 }
1645
1646 /*
1647  * Fetch one row from a specified table, using a specified value
1648  * for the primary key
1649 */
1650 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1651
1652     int id_pos = 0;
1653     int order_pos = 1;
1654
1655 #ifdef PCRUD
1656     id_pos = 1;
1657     order_pos = 2;
1658 #endif
1659
1660         osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1661
1662         const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos);  // key value
1663
1664         osrfLogDebug(
1665                 OSRF_LOG_MARK,
1666                 "%s retrieving %s object with primary key value of %s",
1667                 MODULENAME,
1668                 osrfHashGet( class_def, "fieldmapper" ),
1669                 jsonObjectGetString( id_obj )
1670         );
1671
1672         // Build a WHERE clause based on the key value
1673         jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1674         jsonObjectSetKey( 
1675                 where_clause,
1676                 osrfHashGet( class_def, "primarykey" ),
1677                 jsonObjectClone( id_obj )
1678         );
1679
1680         jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
1681
1682         jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
1683
1684         jsonObjectFree( where_clause );
1685         if(*err)
1686                 return jsonNULL;
1687
1688         jsonObject* obj = jsonObjectExtractIndex( list, 0 );
1689         jsonObjectFree( list );
1690
1691 #ifdef PCRUD
1692         if(!verifyObjectPCRUD(ctx, obj)) {
1693         jsonObjectFree(obj);
1694         *err = -1;
1695
1696         growing_buffer* msg = buffer_init(128);
1697                 OSRF_BUFFER_ADD( msg, MODULENAME );
1698                 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1699
1700         char* m = buffer_release(msg);
1701         osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1702
1703         free(m);
1704
1705                 return jsonNULL;
1706         }
1707 #endif
1708
1709         return obj;
1710 }
1711
1712 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1713         growing_buffer* val_buf = buffer_init(32);
1714         const char* numtype = get_datatype( field );
1715
1716         if ( !strncmp( numtype, "INT", 3 ) ) {
1717                 if (value->type == JSON_NUMBER)
1718                         //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1719                         buffer_fadd( val_buf, jsonObjectGetString( value ) );
1720                 else {
1721                         //const char* val_str = jsonObjectGetString( value );
1722                         //buffer_fadd( val_buf, "%ld", atol(val_str) );
1723                         buffer_fadd( val_buf, jsonObjectGetString( value ) );
1724                 }
1725
1726         } else if ( !strcmp( numtype, "NUMERIC" ) ) {
1727                 if (value->type == JSON_NUMBER)
1728                         //buffer_fadd( val_buf, "%f",  jsonObjectGetNumber(value) );
1729                         buffer_fadd( val_buf, jsonObjectGetString( value ) );
1730                 else {
1731                         //const char* val_str = jsonObjectGetString( value );
1732                         //buffer_fadd( val_buf, "%f", atof(val_str) );
1733                         buffer_fadd( val_buf, jsonObjectGetString( value ) );
1734                 }
1735
1736         } else {
1737                 // Presumably this was really intended ot be a string, so quote it
1738                 char* str = jsonObjectToSimpleString( value );
1739                 if ( dbi_conn_quote_string(dbhandle, &str) ) {
1740                         OSRF_BUFFER_ADD( val_buf, str );
1741                         free(str);
1742                 } else {
1743                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
1744                         free(str);
1745                         buffer_free(val_buf);
1746                         return NULL;
1747                 }
1748         }
1749
1750         return buffer_release(val_buf);
1751 }
1752
1753 static char* searchINPredicate (const char* class_alias, osrfHash* field,
1754                 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1755         growing_buffer* sql_buf = buffer_init(32);
1756         
1757         buffer_fadd(
1758                 sql_buf,
1759                 "\"%s\".%s ",
1760                 class_alias,
1761                 osrfHashGet(field, "name")
1762         );
1763
1764         if (!op) {
1765                 buffer_add(sql_buf, "IN (");
1766         } else if (!(strcasecmp(op,"not in"))) {
1767                 buffer_add(sql_buf, "NOT IN (");
1768         } else {
1769                 buffer_add(sql_buf, "IN (");
1770         }
1771
1772     if (node->type == JSON_HASH) {
1773         // subquery predicate
1774         char* subpred = SELECT(
1775             ctx,
1776             jsonObjectGetKey( node, "select" ),
1777             jsonObjectGetKey( node, "from" ),
1778             jsonObjectGetKey( node, "where" ),
1779             jsonObjectGetKey( node, "having" ),
1780             jsonObjectGetKey( node, "order_by" ),
1781             jsonObjectGetKey( node, "limit" ),
1782             jsonObjectGetKey( node, "offset" ),
1783             SUBSELECT
1784         );
1785                 pop_query_frame();
1786
1787                 if( subpred ) {
1788                         buffer_add(sql_buf, subpred);
1789                         free(subpred);
1790                 } else {
1791                         buffer_free( sql_buf );
1792                         return NULL;
1793                 }
1794
1795     } else if (node->type == JSON_ARRAY) {
1796         // literal value list
1797         int in_item_index = 0;
1798         int in_item_first = 1;
1799         const jsonObject* in_item;
1800         while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1801
1802                         if (in_item_first)
1803                                 in_item_first = 0;
1804                         else
1805                                 buffer_add(sql_buf, ", ");
1806
1807                         // Sanity check
1808                         if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1809                                 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1810                                                 MODULENAME, json_type( in_item->type ) );
1811                                 buffer_free(sql_buf);
1812                                 return NULL;
1813                         }
1814                         
1815                         // Append the literal value -- quoted if not a number
1816                         if ( JSON_NUMBER == in_item->type ) {
1817                                 char* val = jsonNumberToDBString( field, in_item );
1818                                 OSRF_BUFFER_ADD( sql_buf, val );
1819                                 free(val);
1820
1821                         } else if ( !strcmp( get_primitive( field ), "number") ) {
1822                                 char* val = jsonNumberToDBString( field, in_item );
1823                                 OSRF_BUFFER_ADD( sql_buf, val );
1824                                 free(val);
1825
1826                         } else {
1827                                 char* key_string = jsonObjectToSimpleString(in_item);
1828                                 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1829                                         OSRF_BUFFER_ADD( sql_buf, key_string );
1830                                         free(key_string);
1831                                 } else {
1832                                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1833                                         free(key_string);
1834                                         buffer_free(sql_buf);
1835                                         return NULL;
1836                                 }
1837                         }
1838                 }
1839
1840                 if( in_item_first ) {
1841                         osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1842                         buffer_free( sql_buf );
1843                         return NULL;
1844                 }
1845         } else {
1846                 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1847                         MODULENAME, json_type( node->type ) );
1848                 buffer_free(sql_buf);
1849                 return NULL;
1850         }
1851
1852         OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1853
1854         return buffer_release(sql_buf);
1855 }
1856
1857 // Receive a JSON_ARRAY representing a function call.  The first
1858 // entry in the array is the function name.  The rest are parameters.
1859 static char* searchValueTransform( const jsonObject* array ) {
1860         
1861         if( array->size < 1 ) {
1862                 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
1863                 return NULL;
1864         }
1865         
1866         // Get the function name
1867         jsonObject* func_item = jsonObjectGetIndex( array, 0 );
1868         if( func_item->type != JSON_STRING ) {
1869                 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
1870                         MODULENAME, json_type( func_item->type ) );
1871                 return NULL;
1872         }
1873         
1874         growing_buffer* sql_buf = buffer_init(32);
1875
1876         OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1877         OSRF_BUFFER_ADD( sql_buf, "( " );
1878         
1879         // Get the parameters
1880         int func_item_index = 1;   // We already grabbed the zeroth entry
1881         while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1882
1883                 // Add a separator comma, if we need one
1884                 if( func_item_index > 2 )
1885                         buffer_add( sql_buf, ", " );
1886
1887                 // Add the current parameter
1888                 if (func_item->type == JSON_NULL) {
1889                         buffer_add( sql_buf, "NULL" );
1890                 } else {
1891                         char* val = jsonObjectToSimpleString(func_item);
1892                         if ( dbi_conn_quote_string(dbhandle, &val) ) {
1893                                 OSRF_BUFFER_ADD( sql_buf, val );
1894                                 free(val);
1895                         } else {
1896                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1897                                 buffer_free(sql_buf);
1898                                 free(val);
1899                                 return NULL;
1900                         }
1901                 }
1902         }
1903
1904         buffer_add( sql_buf, " )" );
1905
1906         return buffer_release(sql_buf);
1907 }
1908
1909 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
1910                 const jsonObject* node, const char* op) {
1911
1912         if( ! is_good_operator( op ) ) {
1913                 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
1914                 return NULL;
1915         }
1916         
1917         char* val = searchValueTransform(node);
1918         if( !val )
1919                 return NULL;
1920         
1921         growing_buffer* sql_buf = buffer_init(32);
1922         buffer_fadd(
1923                 sql_buf,
1924                 "\"%s\".%s %s %s",
1925                 class_alias,
1926                 osrfHashGet(field, "name"),
1927                 op,
1928                 val
1929         );
1930
1931         free(val);
1932
1933         return buffer_release(sql_buf);
1934 }
1935
1936 // class_alias is a class name or other table alias
1937 // field is a field definition as stored in the IDL
1938 // node comes from the method parameter, and may represent an entry in the SELECT list
1939 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
1940         growing_buffer* sql_buf = buffer_init(32);
1941
1942         const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
1943         const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
1944
1945         if(transform_subcolumn) {
1946                 if( ! is_identifier( transform_subcolumn ) ) {
1947                         osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
1948                                         MODULENAME, transform_subcolumn );
1949                         buffer_free( sql_buf );
1950                         return NULL;
1951                 }
1952                 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' );    // enclose transform in parentheses
1953         }
1954
1955         if (field_transform) {
1956                 
1957                 if( ! is_identifier( field_transform ) ) {
1958                         osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
1959                                         MODULENAME, field_transform );
1960                         buffer_free( sql_buf );
1961                         return NULL;
1962                 }
1963                 
1964                 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class_alias, osrfHashGet(field, "name"));
1965                 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
1966
1967                 if (array) {
1968                         if( array->type != JSON_ARRAY ) {
1969                                 osrfLogError( OSRF_LOG_MARK,
1970                                         "%s: Expected JSON_ARRAY for function params; found %s",
1971                                         MODULENAME, json_type( array->type ) );
1972                                 buffer_free( sql_buf );
1973                                 return NULL;
1974                         }
1975                         int func_item_index = 0;
1976                         jsonObject* func_item;
1977                         while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1978
1979                                 char* val = jsonObjectToSimpleString(func_item);
1980
1981                                 if ( !val ) {
1982                                         buffer_add( sql_buf, ",NULL" );
1983                                 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
1984                                         OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
1985                                         OSRF_BUFFER_ADD( sql_buf, val );
1986                                 } else {
1987                                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1988                                         free(val);
1989                                         buffer_free(sql_buf);
1990                                         return NULL;
1991                         }
1992                                 free(val);
1993                         }
1994                 }
1995
1996                 buffer_add( sql_buf, " )" );
1997
1998         } else {
1999                 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
2000         }
2001
2002         if (transform_subcolumn)
2003                 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2004
2005         return buffer_release(sql_buf);
2006 }
2007
2008 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2009                 const jsonObject* node, const char* op ) {
2010
2011         if( ! is_good_operator( op ) ) {
2012                 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2013                 return NULL;
2014         }
2015
2016         char* field_transform = searchFieldTransform( class_info->alias, field, node );
2017         if( ! field_transform )
2018                 return NULL;
2019         char* value = NULL;
2020         int extra_parens = 0;   // boolean
2021
2022         const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2023         if ( ! value_obj ) {
2024                 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2025                 if( !value ) {
2026                         osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
2027                         free(field_transform);
2028                         return NULL;
2029                 }
2030                 extra_parens = 1;
2031         } else if ( value_obj->type == JSON_ARRAY ) {
2032                 value = searchValueTransform( value_obj );
2033                 if( !value ) {
2034                         osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
2035                         free( field_transform );
2036                         return NULL;
2037                 }
2038         } else if ( value_obj->type == JSON_HASH ) {
2039                 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2040                 if( !value ) {
2041                         osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
2042                         free(field_transform);
2043                         return NULL;
2044                 }
2045                 extra_parens = 1;
2046         } else if ( value_obj->type == JSON_NUMBER ) {
2047                 value = jsonNumberToDBString( field, value_obj );
2048         } else if ( value_obj->type == JSON_NULL ) {
2049                 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
2050                 free(field_transform);
2051                 return NULL;
2052         } else if ( value_obj->type == JSON_BOOL ) {
2053                 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
2054                 free(field_transform);
2055                 return NULL;
2056         } else {
2057                 if ( !strcmp( get_primitive( field ), "number") ) {
2058                         value = jsonNumberToDBString( field, value_obj );
2059                 } else {
2060                         value = jsonObjectToSimpleString( value_obj );
2061                         if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2062                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
2063                                 free(value);
2064                                 free(field_transform);
2065                                 return NULL;
2066                         }
2067                 }
2068         }
2069
2070         const char* left_parens  = "";
2071         const char* right_parens = "";
2072
2073         if( extra_parens ) {
2074                 left_parens  = "(";
2075                 right_parens = ")";
2076         }
2077
2078         growing_buffer* sql_buf = buffer_init(32);
2079
2080         buffer_fadd(
2081                 sql_buf,
2082                 "%s%s %s %s %s %s%s",
2083                 left_parens,
2084                 field_transform,
2085                 op,
2086                 left_parens,
2087                 value,
2088                 right_parens,
2089                 right_parens
2090         );
2091
2092         free(value);
2093         free(field_transform);
2094
2095         return buffer_release(sql_buf);
2096 }
2097
2098 static char* searchSimplePredicate (const char* op, const char* class_alias,
2099                 osrfHash* field, const jsonObject* node) {
2100
2101         if( ! is_good_operator( op ) ) {
2102                 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2103                 return NULL;
2104         }
2105
2106         char* val = NULL;
2107
2108         // Get the value to which we are comparing the specified column
2109         if (node->type != JSON_NULL) {
2110                 if ( node->type == JSON_NUMBER ) {
2111                         val = jsonNumberToDBString( field, node );
2112                 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2113                         val = jsonNumberToDBString( field, node );
2114                 } else {
2115                         val = jsonObjectToSimpleString(node);
2116                 }
2117         }
2118
2119         if( val ) {
2120                 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2121                         // Value is not numeric; enclose it in quotes
2122                         if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2123                                 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2124                                 free( val );
2125                                 return NULL;
2126                         }
2127                 }
2128         } else {
2129                 // Compare to a null value
2130                 val = strdup( "NULL" );
2131                 if (strcmp( op, "=" ))
2132                         op = "IS NOT";
2133                 else
2134                         op = "IS";
2135         }
2136
2137         growing_buffer* sql_buf = buffer_init(32);
2138         buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2139         char* pred = buffer_release( sql_buf );
2140
2141         free(val);
2142
2143         return pred;
2144 }
2145
2146 static char* searchBETWEENPredicate (const char* class_alias,
2147                 osrfHash* field, const jsonObject* node) {
2148
2149         const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2150         const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2151         
2152         if( NULL == y_node ) {
2153                 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2154                 return NULL;
2155         }
2156         else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2157                 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2158                 return NULL;
2159         }
2160         
2161         char* x_string;
2162         char* y_string;
2163
2164         if ( !strcmp( get_primitive( field ), "number") ) {
2165                 x_string = jsonNumberToDBString(field, x_node);
2166                 y_string = jsonNumberToDBString(field, y_node);
2167
2168         } else {
2169                 x_string = jsonObjectToSimpleString(x_node);
2170                 y_string = jsonObjectToSimpleString(y_node);
2171                 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2172                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2173                                         MODULENAME, x_string, y_string);
2174                         free(x_string);
2175                         free(y_string);
2176                         return NULL;
2177                 }
2178         }
2179
2180         growing_buffer* sql_buf = buffer_init(32);
2181         buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s", 
2182                         class_alias, osrfHashGet(field, "name"), x_string, y_string );
2183         free(x_string);
2184         free(y_string);
2185
2186         return buffer_release(sql_buf);
2187 }
2188
2189 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2190                                                            jsonObject* node, osrfMethodContext* ctx ) {
2191
2192         char* pred = NULL;
2193         if (node->type == JSON_ARRAY) { // equality IN search
2194                 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2195         } else if (node->type == JSON_HASH) { // other search
2196                 jsonIterator* pred_itr = jsonNewIterator( node );
2197                 if( !jsonIteratorHasNext( pred_itr ) ) {
2198                         osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"", 
2199                                         MODULENAME, osrfHashGet(field, "name") );
2200                 } else {
2201                         jsonObject* pred_node = jsonIteratorNext( pred_itr );
2202
2203                         // Verify that there are no additional predicates
2204                         if( jsonIteratorHasNext( pred_itr ) ) {
2205                                 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"", 
2206                                                 MODULENAME, osrfHashGet(field, "name") );
2207                         } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2208                                 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2209                         else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2210                                 pred = searchINPredicate( class_info->alias, field, pred_node, pred_itr->key, ctx );
2211                         else if ( pred_node->type == JSON_ARRAY )
2212                                 pred = searchFunctionPredicate( class_info->alias, field, pred_node, pred_itr->key );
2213                         else if ( pred_node->type == JSON_HASH )
2214                                 pred = searchFieldTransformPredicate( class_info, field, pred_node, pred_itr->key );
2215                         else
2216                                 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2217                 }
2218                 jsonIteratorFree(pred_itr);
2219
2220         } else if (node->type == JSON_NULL) { // IS NULL search
2221                 growing_buffer* _p = buffer_init(64);
2222                 buffer_fadd(
2223                         _p,
2224                         "\"%s\".%s IS NULL",
2225                         class_info->class_name,
2226                         osrfHashGet(field, "name")
2227                 );
2228                 pred = buffer_release(_p);
2229         } else { // equality search
2230                 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2231         }
2232
2233         return pred;
2234
2235 }
2236
2237
2238 /*
2239
2240 join : {
2241         acn : {
2242                 field : record,
2243                 fkey : id
2244                 type : left
2245                 filter_op : or
2246                 filter : { ... },
2247                 join : {
2248                         acp : {
2249                                 field : call_number,
2250                                 fkey : id,
2251                                 filter : { ... },
2252                         },
2253                 },
2254         },
2255         mrd : {
2256                 field : record,
2257                 type : inner
2258                 fkey : id,
2259                 filter : { ... },
2260         }
2261 }
2262
2263 */
2264
2265 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2266
2267         const jsonObject* working_hash;
2268         jsonObject* freeable_hash = NULL;
2269
2270         if (join_hash->type == JSON_HASH) {
2271                 working_hash = join_hash;
2272         } else if (join_hash->type == JSON_STRING) {
2273                 // turn it into a JSON_HASH by creating a wrapper
2274                 // around a copy of the original
2275                 const char* _tmp = jsonObjectGetString( join_hash );
2276                 freeable_hash = jsonNewObjectType(JSON_HASH);
2277                 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2278                 working_hash = freeable_hash;
2279         } else {
2280                 osrfLogError(
2281                         OSRF_LOG_MARK,
2282                         "%s: JOIN failed; expected JSON object type not found",
2283                         MODULENAME
2284                 );
2285                 return NULL;
2286         }
2287
2288         growing_buffer* join_buf = buffer_init(128);
2289         const char* leftclass = left_info->class_name;
2290
2291         jsonObject* snode = NULL;
2292         jsonIterator* search_itr = jsonNewIterator( working_hash );
2293
2294         while ( (snode = jsonIteratorNext( search_itr )) ) {
2295                 const char* right_alias = search_itr->key;
2296                 const char* class = 
2297                                 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2298                 if( ! class )
2299                         class = right_alias;
2300
2301                 const ClassInfo* right_info = add_joined_class( right_alias, class );
2302                 if( !right_info ) {
2303                         osrfLogError(
2304                                 OSRF_LOG_MARK,
2305                                 "%s: JOIN failed.  Class \"%s\" not resolved in IDL",
2306                                 MODULENAME,
2307                                 search_itr->key
2308                         );
2309                         jsonIteratorFree( search_itr );
2310                         buffer_free( join_buf );
2311                         if( freeable_hash )
2312                                 jsonObjectFree( freeable_hash );
2313                         return NULL;
2314                 }
2315                 osrfHash* links    = right_info->links;
2316                 const char* table  = right_info->source_def;
2317
2318                 const char* fkey  = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2319                 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2320
2321                 if (field && !fkey) {
2322                         // Look up the corresponding join column in the IDL.
2323                         // The link must be defined in the child table,
2324                         // and point to the right parent table.
2325                         osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2326                         const char* reltype = NULL;
2327                         const char* other_class = NULL;
2328                         reltype = osrfHashGet( idl_link, "reltype" );
2329                         if( reltype && strcmp( reltype, "has_many" ) )
2330                                 other_class = osrfHashGet( idl_link, "class" );
2331                         if( other_class && !strcmp( other_class, leftclass ) )
2332                                 fkey = osrfHashGet( idl_link, "key" );
2333                         if (!fkey) {
2334                                 osrfLogError(
2335                                         OSRF_LOG_MARK,
2336                                         "%s: JOIN failed.  No link defined from %s.%s to %s",
2337                                         MODULENAME,
2338                                         class,
2339                                         field,
2340                                         leftclass
2341                                 );
2342                                 buffer_free(join_buf);
2343                                 if(freeable_hash)
2344                                         jsonObjectFree(freeable_hash);
2345                                 jsonIteratorFree(search_itr);
2346                                 return NULL;
2347                         }
2348
2349                 } else if (!field && fkey) {
2350                         // Look up the corresponding join column in the IDL.
2351                         // The link must be defined in the child table,
2352                         // and point to the right parent table.
2353                         osrfHash* left_links = left_info->links;
2354                         osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2355                         const char* reltype = NULL;
2356                         const char* other_class = NULL;
2357                         reltype = osrfHashGet( idl_link, "reltype" );
2358                         if( reltype && strcmp( reltype, "has_many" ) )
2359                                 other_class = osrfHashGet( idl_link, "class" );
2360                         if( other_class && !strcmp( other_class, class ) )
2361                                 field = osrfHashGet( idl_link, "key" );
2362                         if (!field) {
2363                                 osrfLogError(
2364                                         OSRF_LOG_MARK,
2365                                         "%s: JOIN failed.  No link defined from %s.%s to %s",
2366                                         MODULENAME,
2367                                         leftclass,
2368                                         fkey,
2369                                         class
2370                                 );
2371                                 buffer_free(join_buf);
2372                                 if(freeable_hash)
2373                                         jsonObjectFree(freeable_hash);
2374                                 jsonIteratorFree(search_itr);
2375                                 return NULL;
2376                         }
2377
2378                 } else if (!field && !fkey) {
2379                         osrfHash* left_links = left_info->links;
2380
2381                         // For each link defined for the left class:
2382                         // see if the link references the joined class
2383                         osrfHashIterator* itr = osrfNewHashIterator( left_links );
2384                         osrfHash* curr_link = NULL;
2385                         while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2386                                 const char* other_class = osrfHashGet( curr_link, "class" );
2387                                 if( other_class && !strcmp( other_class, class ) ) {
2388
2389                                         // In the IDL, the parent class doesn't know then names of the child
2390                                         // columns that are pointing to it, so don't use that end of the link
2391                                         const char* reltype = osrfHashGet( curr_link, "reltype" );
2392                                         if( reltype && strcmp( reltype, "has_many" ) ) {
2393                                                 // Found a link between the classes
2394                                                 fkey = osrfHashIteratorKey( itr );
2395                                                 field = osrfHashGet( curr_link, "key" );
2396                                                 break;
2397                                         }
2398                                 }
2399                         }
2400                         osrfHashIteratorFree( itr );
2401
2402                         if (!field || !fkey) {
2403                                 // Do another such search, with the classes reversed
2404
2405                                 // For each link defined for the joined class:
2406                                 // see if the link references the left class
2407                                 osrfHashIterator* itr = osrfNewHashIterator( links );
2408                                 osrfHash* curr_link = NULL;
2409                                 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2410                                         const char* other_class = osrfHashGet( curr_link, "class" );
2411                                         if( other_class && !strcmp( other_class, leftclass ) ) {
2412
2413                                                 // In the IDL, the parent class doesn't know then names of the child
2414                                                 // columns that are pointing to it, so don't use that end of the link
2415                                                 const char* reltype = osrfHashGet( curr_link, "reltype" );
2416                                                 if( reltype && strcmp( reltype, "has_many" ) ) {
2417                                                         // Found a link between the classes
2418                                                         field = osrfHashIteratorKey( itr );
2419                                                         fkey = osrfHashGet( curr_link, "key" );
2420                                                         break;
2421                                                 }
2422                                         }
2423                                 }
2424                                 osrfHashIteratorFree( itr );
2425                         }
2426
2427                         if (!field || !fkey) {
2428                                 osrfLogError(
2429                                         OSRF_LOG_MARK,
2430                                         "%s: JOIN failed.  No link defined between %s and %s",
2431                                         MODULENAME,
2432                                         leftclass,
2433                                         class
2434                                 );
2435                                 buffer_free(join_buf);
2436                                 if(freeable_hash)
2437                                         jsonObjectFree(freeable_hash);
2438                                 jsonIteratorFree(search_itr);
2439                                 return NULL;
2440                         }
2441
2442                 }
2443
2444                 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2445                 if (type) {
2446                         if ( !strcasecmp(type,"left") ) {
2447                                 buffer_add(join_buf, " LEFT JOIN");
2448                         } else if ( !strcasecmp(type,"right") ) {
2449                                 buffer_add(join_buf, " RIGHT JOIN");
2450                         } else if ( !strcasecmp(type,"full") ) {
2451                                 buffer_add(join_buf, " FULL JOIN");
2452                         } else {
2453                                 buffer_add(join_buf, " INNER JOIN");
2454                         }
2455                 } else {
2456                         buffer_add(join_buf, " INNER JOIN");
2457                 }
2458
2459                 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2460                                         table, right_alias, right_alias, field, left_info->alias, fkey);
2461
2462                 // Add any other join conditions as specified by "filter"
2463                 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2464                 if (filter) {
2465                         const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2466                         if ( filter_op && !strcasecmp("or",filter_op) ) {
2467                                 buffer_add( join_buf, " OR " );
2468                         } else {
2469                                 buffer_add( join_buf, " AND " );
2470                         }
2471
2472                         char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2473                         if( jpred ) {
2474                                 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2475                                 OSRF_BUFFER_ADD( join_buf, jpred );
2476                                 free(jpred);
2477                         } else {
2478                                 osrfLogError(
2479                                         OSRF_LOG_MARK,
2480                                         "%s: JOIN failed.  Invalid conditional expression.",
2481                                         MODULENAME
2482                                 );
2483                                 jsonIteratorFree( search_itr );
2484                                 buffer_free( join_buf );
2485                                 if( freeable_hash )
2486                                         jsonObjectFree( freeable_hash );
2487                                 return NULL;
2488                         }
2489                 }
2490
2491                 buffer_add(join_buf, " ) ");
2492
2493                 // Recursively add a nested join, if one is present
2494                 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2495                 if (join_filter) {
2496                         char* jpred = searchJOIN( join_filter, right_info );
2497                         if( jpred ) {
2498                                 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2499                                 OSRF_BUFFER_ADD( join_buf, jpred );
2500                                 free(jpred);
2501                         } else {
2502                                 osrfLogError(  OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2503                                 jsonIteratorFree( search_itr );
2504                                 buffer_free( join_buf );
2505                                 if( freeable_hash )
2506                                         jsonObjectFree( freeable_hash );
2507                                 return NULL;
2508                         }
2509                 }
2510         }
2511
2512         if(freeable_hash)
2513                 jsonObjectFree(freeable_hash);
2514         jsonIteratorFree(search_itr);
2515
2516         return buffer_release(join_buf);
2517 }
2518
2519 /*
2520
2521 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2522 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2523 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2524
2525 Generate code to express a set of conditions, as for a WHERE clause.  Parameters:
2526
2527 search_hash is the JSON expression of the conditions.
2528 meta is the class definition from the IDL, for the relevant table.
2529 opjoin_type indicates whether multiple conditions, if present, should be
2530         connected by AND or OR.
2531 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2532         to pass it to other functions -- and all they do with it is to use the session
2533         and request members to send error messages back to the client.
2534
2535 */
2536
2537 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
2538                 int opjoin_type, osrfMethodContext* ctx ) {
2539
2540         osrfLogDebug(
2541                 OSRF_LOG_MARK,
2542                 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2543                 MODULENAME,
2544                 search_hash,
2545                 class_info->class_def,
2546                 opjoin_type,
2547                 ctx
2548         );
2549
2550         growing_buffer* sql_buf = buffer_init(128);
2551
2552         jsonObject* node = NULL;
2553
2554         int first = 1;
2555         if ( search_hash->type == JSON_ARRAY ) {
2556                 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2557                 if( 0 == search_hash->size ) {
2558                         osrfLogError(
2559                                 OSRF_LOG_MARK,
2560                                 "%s: Invalid predicate structure: empty JSON array",
2561                                 MODULENAME
2562                         );
2563                         buffer_free( sql_buf );
2564                         return NULL;
2565                 }
2566
2567                 unsigned long i = 0;
2568                 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
2569                         if (first) {
2570                                 first = 0;
2571                         } else {
2572                                 if (opjoin_type == OR_OP_JOIN)
2573                                         buffer_add(sql_buf, " OR ");
2574                                 else
2575                                         buffer_add(sql_buf, " AND ");
2576                         }
2577
2578                         char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2579                         if( ! subpred ) {
2580                                 buffer_free( sql_buf );
2581                                 return NULL;
2582                         }
2583
2584                         buffer_fadd(sql_buf, "( %s )", subpred);
2585                         free(subpred);
2586                 }
2587
2588         } else if ( search_hash->type == JSON_HASH ) {
2589                 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2590                 jsonIterator* search_itr = jsonNewIterator( search_hash );
2591                 if( !jsonIteratorHasNext( search_itr ) ) {
2592                         osrfLogError(
2593                                 OSRF_LOG_MARK,
2594                                 "%s: Invalid predicate structure: empty JSON object",
2595                                 MODULENAME
2596                         );
2597                         jsonIteratorFree( search_itr );
2598                         buffer_free( sql_buf );
2599                         return NULL;
2600                 }
2601
2602                 while ( (node = jsonIteratorNext( search_itr )) ) {
2603
2604                         if (first) {
2605                                 first = 0;
2606                         } else {
2607                                 if (opjoin_type == OR_OP_JOIN)
2608                                         buffer_add(sql_buf, " OR ");
2609                                 else
2610                                         buffer_add(sql_buf, " AND ");
2611                         }
2612
2613                         if ( '+' == search_itr->key[ 0 ] ) {
2614
2615                                 // This plus sign prefixes a class name or other table alias;
2616                                 // make sure the table alias is in scope
2617                                 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
2618                                 if( ! alias_info ) {
2619                                         osrfLogError(
2620                                                          OSRF_LOG_MARK,
2621                                                         "%s: Invalid table alias \"%s\" in WHERE clause",
2622                                                         MODULENAME,
2623                                                         search_itr->key + 1
2624                                         );
2625                                         jsonIteratorFree( search_itr );
2626                                         buffer_free( sql_buf );
2627                                         return NULL;
2628                                 }
2629
2630                                 if ( node->type == JSON_STRING ) {
2631                                         // It's the name of a column; make sure it belongs to the class
2632                                         const char* fieldname = jsonObjectGetString( node );
2633                                         if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
2634                                                 osrfLogError(
2635                                                         OSRF_LOG_MARK,
2636                                                         "%s: Invalid column name \"%s\" in WHERE clause for table alias \"%s\"",
2637                                                         MODULENAME,
2638                                                         fieldname,
2639                                                         alias_info->alias
2640                                                 );
2641                                                 jsonIteratorFree( search_itr );
2642                                                 buffer_free( sql_buf );
2643                                                 return NULL;
2644                                         }
2645
2646                                         buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
2647                                 } else {
2648                                         // It's something more complicated
2649                                         char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
2650                                         if( ! subpred ) {
2651                                                 jsonIteratorFree( search_itr );
2652                                                 buffer_free( sql_buf );
2653                                                 return NULL;
2654                                         }
2655
2656                                         buffer_fadd(sql_buf, "( %s )", subpred);
2657                                         free(subpred);
2658                                 }
2659                         } else if ( '-' == search_itr->key[ 0 ] ) {
2660                                 if ( !strcasecmp("-or",search_itr->key) ) {
2661                                         char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
2662                                         if( ! subpred ) {
2663                                                 jsonIteratorFree( search_itr );
2664                                                 buffer_free( sql_buf );
2665                                                 return NULL;
2666                                         }
2667
2668                                         buffer_fadd(sql_buf, "( %s )", subpred);
2669                                         free( subpred );
2670                                 } else if ( !strcasecmp("-and",search_itr->key) ) {
2671                                         char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2672                                         if( ! subpred ) {
2673                                                 jsonIteratorFree( search_itr );
2674                                                 buffer_free( sql_buf );
2675                                                 return NULL;
2676                                         }
2677
2678                                         buffer_fadd(sql_buf, "( %s )", subpred);
2679                                         free( subpred );
2680                                 } else if ( !strcasecmp("-not",search_itr->key) ) {
2681                                         char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2682                                         if( ! subpred ) {
2683                                                 jsonIteratorFree( search_itr );
2684                                                 buffer_free( sql_buf );
2685                                                 return NULL;
2686                                         }
2687
2688                                         buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2689                                         free( subpred );
2690                                 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2691                                         char* subpred = SELECT(
2692                                                 ctx,
2693                                                 jsonObjectGetKey( node, "select" ),
2694                                                 jsonObjectGetKey( node, "from" ),
2695                                                 jsonObjectGetKey( node, "where" ),
2696                                                 jsonObjectGetKey( node, "having" ),
2697                                                 jsonObjectGetKey( node, "order_by" ),
2698                                                 jsonObjectGetKey( node, "limit" ),
2699                                                 jsonObjectGetKey( node, "offset" ),
2700                                                 SUBSELECT
2701                                         );
2702                                         pop_query_frame();
2703
2704                                         if( ! subpred ) {
2705                                                 jsonIteratorFree( search_itr );
2706                                                 buffer_free( sql_buf );
2707                                                 return NULL;
2708                                         }
2709
2710                                         buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2711                                         free(subpred);
2712                                 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2713                                         char* subpred = SELECT(
2714                                                 ctx,
2715                                                 jsonObjectGetKey( node, "select" ),
2716                                                 jsonObjectGetKey( node, "from" ),
2717                                                 jsonObjectGetKey( node, "where" ),
2718                                                 jsonObjectGetKey( node, "having" ),
2719                                                 jsonObjectGetKey( node, "order_by" ),
2720                                                 jsonObjectGetKey( node, "limit" ),
2721                                                 jsonObjectGetKey( node, "offset" ),
2722                                                 SUBSELECT
2723                                         );
2724                                         pop_query_frame();
2725
2726                                         if( ! subpred ) {
2727                                                 jsonIteratorFree( search_itr );
2728                                                 buffer_free( sql_buf );
2729                                                 return NULL;
2730                                         }
2731
2732                                         buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2733                                         free(subpred);
2734                                 } else {     // Invalid "minus" operator
2735                                         osrfLogError(
2736                                                          OSRF_LOG_MARK,
2737                                                         "%s: Invalid operator \"%s\" in WHERE clause",
2738                                                         MODULENAME,
2739                                                         search_itr->key
2740                                         );
2741                                         jsonIteratorFree( search_itr );
2742                                         buffer_free( sql_buf );
2743                                         return NULL;
2744                                 }
2745
2746                         } else {
2747
2748                                 const char* class = class_info->class_name;
2749                                 osrfHash* fields = class_info->fields;
2750                                 osrfHash* field = osrfHashGet( fields, search_itr->key );
2751
2752                                 if (!field) {
2753                                         const char* table = class_info->source_def;
2754                                         osrfLogError(
2755                                                 OSRF_LOG_MARK,
2756                                                 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2757                                                 MODULENAME,
2758                                                 search_itr->key,
2759                                                 table ? table : "?",
2760                                                 class ? class : "?"
2761                                         );
2762                                         jsonIteratorFree(search_itr);
2763                                         buffer_free(sql_buf);
2764                                         return NULL;
2765                                 }
2766
2767                                 char* subpred = searchPredicate( class_info, field, node, ctx );
2768                                 if( ! subpred ) {
2769                                         buffer_free(sql_buf);
2770                                         jsonIteratorFree(search_itr);
2771                                         return NULL;
2772                                 }
2773
2774                                 buffer_add( sql_buf, subpred );
2775                                 free(subpred);
2776                         }
2777                 }
2778                 jsonIteratorFree(search_itr);
2779
2780     } else {
2781         // ERROR ... only hash and array allowed at this level
2782         char* predicate_string = jsonObjectToJSON( search_hash );
2783         osrfLogError(
2784             OSRF_LOG_MARK,
2785             "%s: Invalid predicate structure: %s",
2786             MODULENAME,
2787             predicate_string
2788         );
2789         buffer_free(sql_buf);
2790         free(predicate_string);
2791         return NULL;
2792     }
2793
2794         return buffer_release(sql_buf);
2795 }
2796
2797 /* Build a JSON_ARRAY of field names for a given table alias
2798  */
2799 static jsonObject* defaultSelectList( const char* table_alias ) {
2800
2801         if( ! table_alias )
2802                 table_alias = "";
2803
2804         ClassInfo* class_info = search_all_alias( table_alias );
2805         if( ! class_info ) {
2806                 osrfLogError(  
2807                         OSRF_LOG_MARK,
2808                         "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
2809                         MODULENAME,
2810                         table_alias
2811                 );
2812                 return NULL;
2813         }
2814
2815         jsonObject* array = jsonNewObjectType( JSON_ARRAY );
2816         osrfHash* field_def = NULL;
2817         osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
2818         while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2819                 const char* field_name = osrfHashIteratorKey( field_itr );
2820                 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2821                         jsonObjectPush( array, jsonNewObject( field_name ) );
2822                 }
2823         }
2824         osrfHashIteratorFree( field_itr );
2825
2826         return array;
2827 }
2828
2829 char* SELECT (
2830                 /* method context */ osrfMethodContext* ctx,
2831                 
2832                 /* SELECT   */ jsonObject* selhash,
2833                 /* FROM     */ jsonObject* join_hash,
2834                 /* WHERE    */ jsonObject* search_hash,
2835                 /* HAVING   */ jsonObject* having_hash,
2836                 /* ORDER BY */ jsonObject* order_hash,
2837                 /* LIMIT    */ jsonObject* limit,
2838                 /* OFFSET   */ jsonObject* offset,
2839                 /* flags    */ int flags
2840 ) {
2841         const char* locale = osrf_message_get_last_locale();
2842
2843         // general tmp objects
2844         const jsonObject* tmp_const;
2845         jsonObject* selclass = NULL;
2846         jsonObject* snode = NULL;
2847         jsonObject* onode = NULL;
2848
2849         char* string = NULL;
2850         int from_function = 0;
2851         int first = 1;
2852         int gfirst = 1;
2853         //int hfirst = 1;
2854
2855         osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale);
2856
2857         // punt if there's no FROM clause
2858         if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2859                 osrfLogError(
2860                         OSRF_LOG_MARK,
2861                         "%s: FROM clause is missing or empty",
2862                         MODULENAME
2863                 );
2864                 if( ctx )
2865                         osrfAppSessionStatus(
2866                                 ctx->session,
2867                                 OSRF_STATUS_INTERNALSERVERERROR,
2868                                 "osrfMethodException",
2869                                 ctx->request,
2870                                 "FROM clause is missing or empty in JSON query"
2871                         );
2872                 return NULL;
2873         }
2874
2875         // Push a node onto the stack for the current query.  Every level of
2876         // subquery gets its own QueryFrame on the Stack.
2877         push_query_frame();
2878
2879         // the core search class
2880         const char* core_class = NULL;
2881
2882         // get the core class -- the only key of the top level FROM clause, or a string
2883         if (join_hash->type == JSON_HASH) {
2884                 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2885                 snode = jsonIteratorNext( tmp_itr );
2886
2887                 // Populate the current QueryFrame with information
2888                 // about the core class
2889                 if( add_query_core( NULL, tmp_itr->key ) ) {
2890                         if( ctx )
2891                                 osrfAppSessionStatus(
2892                                         ctx->session,
2893                                         OSRF_STATUS_INTERNALSERVERERROR,
2894                                         "osrfMethodException",
2895                                         ctx->request,
2896                                         "Unable to look up core class"
2897                                 );
2898                         return NULL;
2899                 }
2900                 core_class = curr_query->core.class_name;
2901                 join_hash = snode;
2902
2903                 jsonObject* extra = jsonIteratorNext( tmp_itr );
2904
2905                 jsonIteratorFree( tmp_itr );
2906                 snode = NULL;
2907
2908                 // There shouldn't be more than one entry in join_hash
2909                 if( extra ) {
2910                         osrfLogError(
2911                                 OSRF_LOG_MARK,
2912                                 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2913                                 MODULENAME
2914                         );
2915                         if( ctx )
2916                                 osrfAppSessionStatus(
2917                                         ctx->session,
2918                                         OSRF_STATUS_INTERNALSERVERERROR,
2919                                         "osrfMethodException",
2920                                         ctx->request,
2921                                         "Malformed FROM clause in JSON query"
2922                                 );
2923                         return NULL;    // Malformed join_hash; extra entry
2924                 }
2925         } else if (join_hash->type == JSON_ARRAY) {
2926                 // We're selecting from a function, not from a table
2927                 from_function = 1;
2928                 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
2929                 selhash = NULL;
2930
2931         } else if (join_hash->type == JSON_STRING) {
2932                 // Populate the current QueryFrame with information
2933                 // about the core class
2934                 core_class = jsonObjectGetString( join_hash );
2935                 join_hash = NULL;
2936                 if( add_query_core( NULL, core_class ) ) {
2937                         if( ctx )
2938                                 osrfAppSessionStatus(
2939                                         ctx->session,
2940                                         OSRF_STATUS_INTERNALSERVERERROR,
2941                                         "osrfMethodException",
2942                                         ctx->request,
2943                                         "Unable to look up core class"
2944                                 );
2945                         return NULL;
2946                 }
2947         }
2948         else {
2949                 osrfLogError(
2950                         OSRF_LOG_MARK,
2951                         "%s: FROM clause is unexpected JSON type: %s",
2952                         MODULENAME,
2953                         json_type( join_hash->type )
2954                 );
2955                 if( ctx )
2956                         osrfAppSessionStatus(
2957                                 ctx->session,
2958                                 OSRF_STATUS_INTERNALSERVERERROR,
2959                                 "osrfMethodException",
2960                                 ctx->request,
2961                                 "Ill-formed FROM clause in JSON query"
2962                         );
2963                 return NULL;
2964         }
2965
2966         // Build the join clause, if any, while filling out the list
2967         // of joined classes in the current QueryFrame.
2968         char* join_clause = NULL;
2969         if( join_hash && ! from_function ) {
2970
2971                 join_clause = searchJOIN( join_hash, &curr_query->core );
2972                 if( ! join_clause ) {
2973                         if (ctx)
2974                                 osrfAppSessionStatus(
2975                                         ctx->session,
2976                                         OSRF_STATUS_INTERNALSERVERERROR,
2977                                         "osrfMethodException",
2978                                         ctx->request,
2979                                         "Unable to construct JOIN clause(s)"
2980                                 );
2981                         return NULL;
2982                 }
2983         }
2984
2985         // For in case we don't get a select list
2986         jsonObject* defaultselhash = NULL;
2987
2988         // if there is no select list, build a default select list ...
2989         if (!selhash && !from_function) {
2990                 jsonObject* default_list = defaultSelectList( core_class );
2991                 if( ! default_list ) {
2992                         if (ctx) {
2993                                 osrfAppSessionStatus(
2994                                         ctx->session,
2995                                         OSRF_STATUS_INTERNALSERVERERROR,
2996                                         "osrfMethodException",
2997                                         ctx->request,
2998                                         "Unable to build default SELECT clause in JSON query"
2999                                 );
3000                                 free( join_clause );
3001                                 return NULL;
3002                         }
3003                 }
3004
3005                 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
3006                 jsonObjectSetKey( selhash, core_class, default_list );
3007         } 
3008
3009         // The SELECT clause can be encoded only by a hash
3010         if( !from_function && selhash->type != JSON_HASH ) {
3011                 osrfLogError(
3012                         OSRF_LOG_MARK,
3013                         "%s: Expected JSON_HASH for SELECT clause; found %s",
3014                         MODULENAME,
3015                         json_type( selhash->type )
3016                 );
3017
3018                 if (ctx)
3019                         osrfAppSessionStatus(
3020                                 ctx->session,
3021                                 OSRF_STATUS_INTERNALSERVERERROR,
3022                                 "osrfMethodException",
3023                                 ctx->request,
3024                                 "Malformed SELECT clause in JSON query"
3025                         );
3026                 free( join_clause );
3027                 return NULL;
3028         }
3029
3030         // If you see a null or wild card specifier for the core class, or an
3031         // empty array, replace it with a default SELECT list
3032         tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3033         if ( tmp_const ) {
3034                 int default_needed = 0;   // boolean
3035                 if( JSON_STRING == tmp_const->type
3036                         && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3037                                 default_needed = 1;
3038                 else if( JSON_NULL == tmp_const->type )
3039                         default_needed = 1;
3040
3041                 if( default_needed ) {
3042                         // Build a default SELECT list
3043                         jsonObject* default_list = defaultSelectList( core_class );
3044                         if( ! default_list ) {
3045                                 if (ctx) {
3046                                         osrfAppSessionStatus(
3047                                                 ctx->session,
3048                                                 OSRF_STATUS_INTERNALSERVERERROR,
3049                                                 "osrfMethodException",
3050                                                 ctx->request,
3051                                                 "Can't build default SELECT clause in JSON query"
3052                                         );
3053                                         free( join_clause );
3054                                         return NULL;
3055                                 }
3056                         }
3057
3058                         jsonObjectSetKey( selhash, core_class, default_list );
3059                 }
3060         }
3061
3062         // temp buffers for the SELECT list and GROUP BY clause
3063         growing_buffer* select_buf = buffer_init(128);
3064         growing_buffer* group_buf = buffer_init(128);
3065
3066         int aggregate_found = 0;     // boolean
3067
3068         // Build a select list
3069         if(from_function)   // From a function we select everything
3070                 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3071         else {
3072
3073                 // Build the SELECT list as SQL
3074             int sel_pos = 1;
3075             first = 1;
3076             gfirst = 1;
3077             jsonIterator* selclass_itr = jsonNewIterator( selhash );
3078             while ( (selclass = jsonIteratorNext( selclass_itr )) ) {    // For each class
3079
3080                         const char* cname = selclass_itr->key;
3081
3082                         // Make sure the target relation is in the FROM clause.
3083                         
3084                         // At this point join_hash is a step down from the join_hash we
3085                         // received as a parameter.  If the original was a JSON_STRING,
3086                         // then json_hash is now NULL.  If the original was a JSON_HASH,
3087                         // then json_hash is now the first (and only) entry in it,
3088                         // denoting the core class.  We've already excluded the
3089                         // possibility that the original was a JSON_ARRAY, because in
3090                         // that case from_function would be non-NULL, and we wouldn't
3091                         // be here.
3092
3093                         // If the current table alias isn't in scope, 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                         unsigned long field_idx = 0;
3159                         jsonObject* selfield = NULL;
3160                         while((selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3161
3162                                 // If we need a separator comma, add one
3163                                 if (first) {
3164                                         first = 0;
3165                                 } else {
3166                                         OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3167                                 }
3168
3169                                 // if the field specification is a string, add it to the list
3170                                 if (selfield->type == JSON_STRING) {
3171
3172                                         // Look up the field in the IDL
3173                                         const char* col_name = jsonObjectGetString( selfield );
3174                                         osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3175                                         if ( !field_def ) {
3176                                                 // No such field in current class
3177                                                 osrfLogError(
3178                                                         OSRF_LOG_MARK,
3179                                                         "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3180                                                         MODULENAME,
3181                                                         col_name,
3182                                                         cname
3183                                                 );
3184                                                 if( ctx )
3185                                                         osrfAppSessionStatus(
3186                                                                 ctx->session,
3187                                                                 OSRF_STATUS_INTERNALSERVERERROR,
3188                                                                 "osrfMethodException",
3189                                                                 ctx->request,
3190                                                                 "Selected column not defined in JSON query"
3191                                                         );
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( selclass_itr );
3216                                                 buffer_free( select_buf );
3217                                                 buffer_free( group_buf );
3218                                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3219                                                 free( join_clause );
3220                                                 return NULL;
3221                                         }
3222
3223                                         if (locale) {
3224                                                 const char* i18n;
3225                                                 if (flags & DISABLE_I18N)
3226                                                         i18n = NULL;
3227                                                 else
3228                                                         i18n = osrfHashGet(field_def, "i18n");
3229
3230                                                 if( str_is_true( i18n ) ) {
3231                             buffer_fadd( select_buf,
3232                                                                 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3233                                                                 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3234                         } else {
3235                                             buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3236                         }
3237                     } else {
3238                                         buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3239                     }
3240                                         
3241                                 // ... but it could be an object, in which case we check for a Field Transform
3242                                 } else if (selfield->type == JSON_HASH) {
3243
3244                                         const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3245
3246                                         // Get the field definition from the IDL
3247                                         osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3248                                         if ( !field_def ) {
3249                                                 // No such field in current class
3250                                                 osrfLogError(
3251                                                         OSRF_LOG_MARK,
3252                                                         "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3253                                                         MODULENAME,
3254                                                         col_name,
3255                                                         cname
3256                                                 );
3257                                                 if( ctx )
3258                                                         osrfAppSessionStatus(
3259                                                                 ctx->session,
3260                                                                 OSRF_STATUS_INTERNALSERVERERROR,
3261                                                                 "osrfMethodException",
3262                                                                 ctx->request,
3263                                                                 "Selected column is not defined in JSON query"
3264                                                         );
3265                                                 jsonIteratorFree( selclass_itr );
3266                                                 buffer_free( select_buf );
3267                                                 buffer_free( group_buf );
3268                                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3269                                                 free( join_clause );
3270                                                 return NULL;
3271                                         } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3272                                                 // No such field in current class
3273                                                 osrfLogError(
3274                                                         OSRF_LOG_MARK,
3275                                                         "%s: Selected column \"%s\" is virtual for class \"%s\"",
3276                                                         MODULENAME,
3277                                                         col_name,
3278                                                         cname
3279                                                 );
3280                                                 if( ctx )
3281                                                         osrfAppSessionStatus(
3282                                                                 ctx->session,
3283                                                                 OSRF_STATUS_INTERNALSERVERERROR,
3284                                                                 "osrfMethodException",
3285                                                                 ctx->request,
3286                                                                 "Selected column is virtual in JSON query"
3287                                                         );
3288                                                 jsonIteratorFree( selclass_itr );
3289                                                 buffer_free( select_buf );
3290                                                 buffer_free( group_buf );
3291                                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3292                                                 free( join_clause );
3293                                                 return NULL;
3294                                         }
3295
3296                                         // Decide what to use as a column alias
3297                                         const char* _alias;
3298                                         if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3299                                                 _alias = jsonObjectGetString( tmp_const );
3300                                         } else {         // Use field name as the alias
3301                                                 _alias = col_name;
3302                                         }
3303
3304                                         if (jsonObjectGetKeyConst( selfield, "transform" )) {
3305                                                 char* transform_str = searchFieldTransform(class_info->alias, field_def, selfield);
3306                                                 if( transform_str ) {
3307                                                         buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3308                                                         free(transform_str);
3309                                                 } else {
3310                                                         if( ctx )
3311                                                                 osrfAppSessionStatus(
3312                                                                         ctx->session,
3313                                                                         OSRF_STATUS_INTERNALSERVERERROR,
3314                                                                         "osrfMethodException",
3315                                                                         ctx->request,
3316                                                                         "Unable to generate transform function in JSON query"
3317                                                                 );
3318                                                         jsonIteratorFree( selclass_itr );
3319                                                         buffer_free( select_buf );
3320                                                         buffer_free( group_buf );
3321                                                         if( defaultselhash ) jsonObjectFree( defaultselhash );
3322                                                         free( join_clause );
3323                                                         return NULL;
3324                                                 }
3325                                         } else {
3326
3327                                                 if (locale) {
3328                                                         const char* i18n;
3329                                                         if (flags & DISABLE_I18N)
3330                                                                 i18n = NULL;
3331                                                         else
3332                                                                 i18n = osrfHashGet(field_def, "i18n");
3333
3334                                                         if( str_is_true( i18n ) ) {
3335                                                                 buffer_fadd( select_buf,
3336                                                                         " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3337                                                                         class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3338                                                         } else {
3339                                                                 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3340                                                         }
3341                                                 } else {
3342                                                         buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3343                                                 }
3344                                         }
3345                                 }
3346                                 else {
3347                                         osrfLogError(
3348                                                 OSRF_LOG_MARK,
3349                                                 "%s: Selected item is unexpected JSON type: %s",
3350                                                 MODULENAME,
3351                                                 json_type( selfield->type )
3352                                         );
3353                                         if( ctx )
3354                                                 osrfAppSessionStatus(
3355                                                         ctx->session,
3356                                                         OSRF_STATUS_INTERNALSERVERERROR,
3357                                                         "osrfMethodException",
3358                                                         ctx->request,
3359                                                         "Ill-formed SELECT item in JSON query"
3360                                                 );
3361                                         jsonIteratorFree( selclass_itr );
3362                                         buffer_free( select_buf );
3363                                         buffer_free( group_buf );
3364                                         if( defaultselhash ) jsonObjectFree( defaultselhash );
3365                                         free( join_clause );
3366                                         return NULL;
3367                                 }
3368
3369                                 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3370                                 if( obj_is_true( agg_obj ) )
3371                                         aggregate_found = 1;
3372                                 else {
3373                                         // Append a comma (except for the first one)
3374                                         // and add the column to a GROUP BY clause
3375                                         if (gfirst)
3376                                                 gfirst = 0;
3377                                         else
3378                                                 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3379
3380                                         buffer_fadd(group_buf, " %d", sel_pos);
3381                                 }
3382
3383 #if 0
3384                             if (is_agg->size || (flags & SELECT_DISTINCT)) {
3385
3386                                         const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3387                                     if ( ! obj_is_true( aggregate_obj ) ) {
3388                                             if (gfirst) {
3389                                                     gfirst = 0;
3390                                             } else {
3391                                                         OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3392                                             }
3393
3394                                             buffer_fadd(group_buf, " %d", sel_pos);
3395
3396                                         /*
3397                                     } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3398                                             if (gfirst) {
3399                                                     gfirst = 0;
3400                                             } else {
3401                                                         OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3402                                             }
3403
3404                                             _column = searchFieldTransform(class_info->alias, field, selfield);
3405                                                 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3406                                                 OSRF_BUFFER_ADD(group_buf, _column);
3407                                             _column = searchFieldTransform(class_info->alias, field, selfield);
3408                                         */
3409                                     }
3410                             }
3411 #endif
3412
3413                                 sel_pos++;
3414                         } // end while -- iterating across SELECT columns
3415
3416                 } // end while -- iterating across classes
3417
3418                 jsonIteratorFree(selclass_itr);
3419         }
3420
3421
3422         char* col_list = buffer_release(select_buf);
3423
3424         // Make sure the SELECT list isn't empty.  This can happen, for example,
3425         // if we try to build a default SELECT clause from a non-core table.
3426
3427         if( ! *col_list ) {
3428                 osrfLogError(OSRF_LOG_MARK, "%s: SELECT clause is empty", MODULENAME );
3429                 if (ctx)
3430                         osrfAppSessionStatus(
3431                                 ctx->session,
3432                                 OSRF_STATUS_INTERNALSERVERERROR,
3433                                 "osrfMethodException",
3434                                 ctx->request,
3435                                 "SELECT list is empty"
3436                 );
3437                 free( col_list );
3438                 buffer_free( group_buf );
3439                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3440                 free( join_clause );
3441                 return NULL;    
3442         }
3443
3444         char* table = NULL;
3445         if (from_function) table = searchValueTransform(join_hash);
3446         else table = strdup( curr_query->core.source_def );
3447
3448         if( !table ) {
3449                 if (ctx)
3450                         osrfAppSessionStatus(
3451                                 ctx->session,
3452                                 OSRF_STATUS_INTERNALSERVERERROR,
3453                                 "osrfMethodException",
3454                                 ctx->request,
3455                                 "Unable to identify table for core class"
3456                         );
3457                 free( col_list );
3458                 buffer_free( group_buf );
3459                 if( defaultselhash ) jsonObjectFree( defaultselhash );
3460                 free( join_clause );
3461                 return NULL;    
3462         }
3463
3464         // Put it all together
3465         growing_buffer* sql_buf = buffer_init(128);
3466         buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3467         free(col_list);
3468         free(table);
3469
3470         // Append the join clause, if any
3471         if( join_clause ) {
3472                 buffer_add(sql_buf, join_clause);
3473                 free(join_clause);
3474         }
3475
3476         char* order_by_list = NULL;
3477         char* having_buf = NULL;
3478
3479         if (!from_function) {
3480
3481                 // Build a WHERE clause, if there is one
3482                 if ( search_hash ) {
3483                         buffer_add(sql_buf, " WHERE ");
3484
3485                         // and it's on the WHERE clause
3486                         char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
3487                         if ( ! pred ) {
3488                                 if (ctx) {
3489                                         osrfAppSessionStatus(
3490                                                 ctx->session,
3491                                                 OSRF_STATUS_INTERNALSERVERERROR,
3492                                                 "osrfMethodException",
3493                                                 ctx->request,
3494                                                 "Severe query error in WHERE predicate -- see error log for more details"
3495                                         );
3496                                 }
3497                                 buffer_free(group_buf);
3498                                 buffer_free(sql_buf);
3499                                 if (defaultselhash) jsonObjectFree(defaultselhash);
3500                                 return NULL;
3501                         }
3502
3503                         buffer_add(sql_buf, pred);
3504                         free(pred);
3505                 }
3506
3507                 // Build a HAVING clause, if there is one
3508                 if ( having_hash ) {
3509
3510                         // and it's on the the WHERE clause
3511                         having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
3512
3513                         if( ! having_buf ) {
3514                                 if (ctx) {
3515                                                 osrfAppSessionStatus(
3516                                                 ctx->session,
3517                                                 OSRF_STATUS_INTERNALSERVERERROR,
3518                                                 "osrfMethodException",
3519                                                 ctx->request,
3520                                                 "Severe query error in HAVING predicate -- see error log for more details"
3521                                         );
3522                                 }
3523                                 buffer_free(group_buf);
3524                                 buffer_free(sql_buf);
3525                                 if (defaultselhash) jsonObjectFree(defaultselhash);
3526                                 return NULL;
3527                         }
3528                 }
3529
3530                 growing_buffer* order_buf = NULL;  // to collect ORDER BY list
3531
3532                 // Build an ORDER BY clause, if there is one
3533                 if( NULL == order_hash )
3534                         ;  // No ORDER BY? do nothing
3535                 else if( JSON_ARRAY == order_hash->type ) {
3536                         // Array of field specifications, each specification being a
3537                         // hash to define the class, field, and other details
3538                         int order_idx = 0;
3539                         jsonObject* order_spec;
3540                         while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
3541
3542                                 if( JSON_HASH != order_spec->type ) {
3543                                         osrfLogError(OSRF_LOG_MARK,
3544                                                  "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
3545                                                 MODULENAME, json_type( order_spec->type ) );
3546                                         if( ctx )
3547                                                 osrfAppSessionStatus(
3548                                                          ctx->session,
3549                                                         OSRF_STATUS_INTERNALSERVERERROR,
3550                                                         "osrfMethodException",
3551                                                         ctx->request,
3552                                                         "Malformed ORDER BY clause -- see error log for more details"
3553                                                 );
3554                                         buffer_free( order_buf );
3555                                         free(having_buf);
3556                                         buffer_free(group_buf);
3557                                         buffer_free(sql_buf);
3558                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3559                                         return NULL;
3560                                 }
3561
3562                                 const char* class_alias =
3563                                                 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
3564                                 const char* field =
3565                                                 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
3566
3567                                 if ( order_buf )
3568                                         OSRF_BUFFER_ADD(order_buf, ", ");
3569                                 else
3570                                         order_buf = buffer_init(128);
3571
3572                                 if( !field || !class_alias ) {
3573                                         osrfLogError(OSRF_LOG_MARK,
3574                                                 "%s: Missing class or field name in field specification of ORDER BY clause",
3575                                                  MODULENAME );
3576                                         if( ctx )
3577                                                 osrfAppSessionStatus(
3578                                                         ctx->session,
3579                                                         OSRF_STATUS_INTERNALSERVERERROR,
3580                                                         "osrfMethodException",
3581                                                         ctx->request,
3582                                                         "Malformed ORDER BY clause -- see error log for more details"
3583                                                 );
3584                                         buffer_free( order_buf );
3585                                         free(having_buf);
3586                                         buffer_free(group_buf);
3587                                         buffer_free(sql_buf);
3588                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3589                                         return NULL;
3590                                 }
3591
3592                                 ClassInfo* order_class_info = search_alias( class_alias );
3593                                 if( ! order_class_info ) {
3594                                         osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
3595                                                         "not in FROM clause", MODULENAME, class_alias );
3596                                         if( ctx )
3597                                                 osrfAppSessionStatus(
3598                                                         ctx->session,
3599                                                         OSRF_STATUS_INTERNALSERVERERROR,
3600                                                         "osrfMethodException",
3601                                                         ctx->request,
3602                                                         "Invalid class referenced in ORDER BY clause -- see error log for more details"
3603                                                 );
3604                                         free(having_buf);
3605                                         buffer_free(group_buf);
3606                                         buffer_free(sql_buf);
3607                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3608                                         return NULL;
3609                                 }
3610
3611                                 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
3612                                 if( !field_def ) {
3613                                         osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
3614                                                  MODULENAME, class_alias, field );
3615                                         if( ctx )
3616                                                 osrfAppSessionStatus(
3617                                                         ctx->session,
3618                                                         OSRF_STATUS_INTERNALSERVERERROR,
3619                                                         "osrfMethodException",
3620                                                         ctx->request,
3621                                                         "Invalid field referenced in ORDER BY clause -- see error log for more details"
3622                                                 );
3623                                         free(having_buf);
3624                                         buffer_free(group_buf);
3625                                         buffer_free(sql_buf);
3626                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3627                                         return NULL;
3628                                 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3629                                         osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3630                                                                  MODULENAME, field );
3631                                         if( ctx )
3632                                                 osrfAppSessionStatus(
3633                                                         ctx->session,
3634                                                         OSRF_STATUS_INTERNALSERVERERROR,
3635                                                         "osrfMethodException",
3636                                                         ctx->request,
3637                                                         "Virtual field in ORDER BY clause -- see error log for more details"
3638                                                 );
3639                                         buffer_free( order_buf );
3640                                         free(having_buf);
3641                                         buffer_free(group_buf);
3642                                         buffer_free(sql_buf);
3643                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3644                                         return NULL;
3645                                 }
3646
3647                                 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
3648                                         char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
3649                                         if( ! transform_str ) {
3650                                                 if( ctx )
3651                                                         osrfAppSessionStatus(
3652                                                                 ctx->session,
3653                                                                 OSRF_STATUS_INTERNALSERVERERROR,
3654                                                                 "osrfMethodException",
3655                                                                 ctx->request,
3656                                                                 "Severe query error in ORDER BY clause -- see error log for more details"
3657                                                         );
3658                                                 buffer_free( order_buf );
3659                                                 free(having_buf);
3660                                                 buffer_free(group_buf);
3661                                                 buffer_free(sql_buf);
3662                                                 if (defaultselhash) jsonObjectFree(defaultselhash);
3663                                                 return NULL;
3664                                         }
3665                                         
3666                                         OSRF_BUFFER_ADD( order_buf, transform_str );
3667                                         free( transform_str );
3668                                 }
3669                                 else
3670                                         buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
3671
3672                                 const char* direction =
3673                                                 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
3674                                 if( direction ) {
3675                                         if( direction[ 0 ] || 'D' == direction[ 0 ] )
3676                                                 OSRF_BUFFER_ADD( order_buf, " DESC" );
3677                                         else
3678                                                 OSRF_BUFFER_ADD( order_buf, " ASC" );
3679                                 }
3680                         }
3681                 } else if( JSON_HASH == order_hash->type ) {
3682                         // This hash is keyed on class alias.  Each class has either
3683                         // an array of field names or a hash keyed on field name.
3684                         jsonIterator* class_itr = jsonNewIterator( order_hash );
3685                         while ( (snode = jsonIteratorNext( class_itr )) ) {
3686
3687                                 ClassInfo* order_class_info = search_alias( class_itr->key );
3688                                 if( ! order_class_info ) {
3689                                         osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
3690                                                                  MODULENAME, class_itr->key );
3691                                         if( ctx )
3692                                                 osrfAppSessionStatus(
3693                                                         ctx->session,
3694                                                         OSRF_STATUS_INTERNALSERVERERROR,
3695                                                         "osrfMethodException",
3696                                                         ctx->request,
3697                                                         "Invalid class referenced in ORDER BY clause -- see error log for more details"
3698                                                 );
3699                                         jsonIteratorFree( class_itr );
3700                                         buffer_free( order_buf );
3701                                         free(having_buf);
3702                                         buffer_free(group_buf);
3703                                         buffer_free(sql_buf);
3704                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3705                                         return NULL;
3706                                 }
3707
3708                                 osrfHash* field_list_def = order_class_info->fields;
3709
3710                                 if ( snode->type == JSON_HASH ) {
3711
3712                                         // Hash is keyed on field names from the current class.  For each field
3713                                         // there is another layer of hash to define the sorting details, if any,
3714                                         // or a string to indicate direction of sorting.
3715                                         jsonIterator* order_itr = jsonNewIterator( snode );
3716                                         while ( (onode = jsonIteratorNext( order_itr )) ) {
3717
3718                                                 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
3719                                                 if( !field_def ) {
3720                                                         osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3721                                                                         MODULENAME, order_itr->key );
3722                                                         if( ctx )
3723                                                                 osrfAppSessionStatus(
3724                                                                         ctx->session,
3725                                                                         OSRF_STATUS_INTERNALSERVERERROR,
3726                                                                         "osrfMethodException",
3727                                                                         ctx->request,
3728                                                                         "Invalid field in ORDER BY clause -- see error log for more details"
3729                                                                 );
3730                                                         jsonIteratorFree( order_itr );
3731                                                         jsonIteratorFree( class_itr );
3732                                                         buffer_free( order_buf );
3733                                                         free(having_buf);
3734                                                         buffer_free(group_buf);
3735                                                         buffer_free(sql_buf);
3736                                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3737                                                         return NULL;
3738                                                 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3739                                                         osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3740                                                                  MODULENAME, order_itr->key );
3741                                                         if( ctx )
3742                                                                 osrfAppSessionStatus(
3743                                                                         ctx->session,
3744                                                                         OSRF_STATUS_INTERNALSERVERERROR,
3745                                                                         "osrfMethodException",
3746                                                                         ctx->request,
3747                                                                         "Virtual field in ORDER BY clause -- see error log for more details"
3748                                                         );
3749                                                         jsonIteratorFree( order_itr );
3750                                                         jsonIteratorFree( class_itr );
3751                                                         buffer_free( order_buf );
3752                                                         free(having_buf);
3753                                                         buffer_free(group_buf);
3754                                                         buffer_free(sql_buf);
3755                                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3756                                                         return NULL;
3757                                                 }
3758
3759                                                 const char* direction = NULL;
3760                                                 if ( onode->type == JSON_HASH ) {
3761                                                         if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3762                                                                 string = searchFieldTransform(
3763                                                                         class_itr->key,
3764                                                                         osrfHashGet( field_list_def, order_itr->key ),
3765                                                                         onode
3766                                                                 );
3767                                                                 if( ! string ) {
3768                                                                         if( ctx ) osrfAppSessionStatus(
3769                                                                                 ctx->session,
3770                                                                                 OSRF_STATUS_INTERNALSERVERERROR,
3771                                                                                 "osrfMethodException",
3772                                                                                 ctx->request,
3773                                                                                 "Severe query error in ORDER BY clause -- see error log for more details"
3774                                                                         );
3775                                                                         jsonIteratorFree( order_itr );
3776                                                                         jsonIteratorFree( class_itr );
3777                                                                         free(having_buf);
3778                                                                         buffer_free(group_buf);
3779                                                                         buffer_free(order_buf);
3780                                                                         buffer_free(sql_buf);
3781                                                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3782                                                                         return NULL;
3783                                                                 }
3784                                                         } else {
3785                                                                 growing_buffer* field_buf = buffer_init(16);
3786                                                                 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3787                                                                 string = buffer_release(field_buf);
3788                                                         }
3789
3790                                                         if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3791                                                                 const char* dir = jsonObjectGetString(tmp_const);
3792                                                                 if (!strncasecmp(dir, "d", 1)) {
3793                                                                         direction = " DESC";
3794                                                                 } else {
3795                                                                         direction = " ASC";
3796                                                                 }
3797                                                         }
3798
3799                                                 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
3800                                                         osrfLogError( OSRF_LOG_MARK,
3801                                                                 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
3802                                                                 MODULENAME, json_type( onode->type ) );
3803                                                         if( ctx )
3804                                                                 osrfAppSessionStatus(
3805                                                                         ctx->session,
3806                                                                         OSRF_STATUS_INTERNALSERVERERROR,
3807                                                                         "osrfMethodException",
3808                                                                         ctx->request,
3809                                                                         "Malformed ORDER BY clause -- see error log for more details"
3810                                                                 );
3811                                                         jsonIteratorFree( order_itr );
3812                                                         jsonIteratorFree( class_itr );
3813                                                         free(having_buf);
3814                                                         buffer_free(group_buf);
3815                                                         buffer_free(order_buf);
3816                                                         buffer_free(sql_buf);
3817                                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3818                                                         return NULL;
3819
3820                                                 } else {
3821                                                         string = strdup(order_itr->key);
3822                                                         const char* dir = jsonObjectGetString(onode);
3823                                                         if (!strncasecmp(dir, "d", 1)) {
3824                                                                 direction = " DESC";
3825                                                         } else {
3826                                                                 direction = " ASC";
3827                                                         }
3828                                                 }
3829
3830                                                 if ( order_buf )
3831                                                         OSRF_BUFFER_ADD(order_buf, ", ");
3832                                                 else
3833                                                         order_buf = buffer_init(128);
3834
3835                                                 OSRF_BUFFER_ADD(order_buf, string);
3836                                                 free(string);
3837
3838                                                 if (direction) {
3839                                                          OSRF_BUFFER_ADD(order_buf, direction);
3840                                                 }
3841
3842                                         } // end while
3843                                         jsonIteratorFree(order_itr);
3844
3845                                 } else if ( snode->type == JSON_ARRAY ) {
3846
3847                                         // Array is a list of fields from the current class
3848                                         unsigned long order_idx = 0;
3849                                         while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
3850
3851                                                 const char* _f = jsonObjectGetString( onode );
3852
3853                                                 osrfHash* field_def = osrfHashGet( field_list_def, _f );
3854                                                 if( !field_def ) {
3855                                                         osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
3856                                                                         MODULENAME, _f );
3857                                                         if( ctx )
3858                                                                 osrfAppSessionStatus(
3859                                                                         ctx->session,
3860                                                                         OSRF_STATUS_INTERNALSERVERERROR,
3861                                                                         "osrfMethodException",
3862                                                                         ctx->request,
3863                                                                         "Invalid field in ORDER BY clause -- see error log for more details"
3864                                                                 );
3865                                                         jsonIteratorFree( class_itr );
3866                                                         buffer_free( order_buf );
3867                                                         free(having_buf);
3868                                                         buffer_free(group_buf);
3869                                                         buffer_free(sql_buf);
3870                                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3871                                                         return NULL;
3872                                                 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3873                                                         osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
3874                                                                         MODULENAME, _f );
3875                                                         if( ctx )
3876                                                                 osrfAppSessionStatus(
3877                                                                         ctx->session,
3878                                                                         OSRF_STATUS_INTERNALSERVERERROR,
3879                                                                         "osrfMethodException",
3880                                                                         ctx->request,
3881                                                                         "Virtual field in ORDER BY clause -- see error log for more details"
3882                                                                 );
3883                                                         jsonIteratorFree( class_itr );
3884                                                         buffer_free( order_buf );
3885                                                         free(having_buf);
3886                                                         buffer_free(group_buf);
3887                                                         buffer_free(sql_buf);
3888                                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3889                                                         return NULL;
3890                                                 }
3891
3892                                                 if ( order_buf )
3893                                                         OSRF_BUFFER_ADD(order_buf, ", ");
3894                                                 else
3895                                                         order_buf = buffer_init(128);
3896
3897                                                 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
3898
3899                                         } // end while
3900
3901                                 // IT'S THE OOOOOOOOOOOLD STYLE!
3902                                 } else {
3903                                         osrfLogError(OSRF_LOG_MARK, 
3904                                                         "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3905                                         if (ctx) {
3906                                                 osrfAppSessionStatus(
3907                                                         ctx->session,
3908                                                         OSRF_STATUS_INTERNALSERVERERROR,
3909                                                         "osrfMethodException",
3910                                                         ctx->request,
3911                                                         "Severe query error -- see error log for more details"
3912                                                 );
3913                                         }
3914
3915                                         free(having_buf);
3916                                         buffer_free(group_buf);
3917                                         buffer_free(order_buf);
3918                                         buffer_free(sql_buf);
3919                                         if (defaultselhash) jsonObjectFree(defaultselhash);
3920                                         jsonIteratorFree(class_itr);
3921                                         return NULL;
3922                                 }
3923                         } // end while
3924                         jsonIteratorFree( class_itr );
3925                 } else {
3926                         osrfLogError(OSRF_LOG_MARK,
3927                                 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
3928                                 MODULENAME, json_type( order_hash->type ) );
3929                         if( ctx )
3930                                 osrfAppSessionStatus(
3931                                         ctx->session,
3932                                         OSRF_STATUS_INTERNALSERVERERROR,
3933                                         "osrfMethodException",
3934                                         ctx->request,
3935                                         "Malformed ORDER BY clause -- see error log for more details"
3936                                 );
3937                         buffer_free( order_buf );
3938                         free(having_buf);
3939                         buffer_free(group_buf);
3940                         buffer_free(sql_buf);
3941                         if (defaultselhash) jsonObjectFree(defaultselhash);
3942                         return NULL;
3943                 }
3944
3945                 if( order_buf )
3946                         order_by_list = buffer_release( order_buf );
3947         }
3948
3949
3950         string = buffer_release(group_buf);
3951
3952         if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
3953                 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3954                 OSRF_BUFFER_ADD( sql_buf, string );
3955         }
3956
3957         free(string);
3958
3959         if( having_buf && *having_buf ) {
3960                 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3961                 OSRF_BUFFER_ADD( sql_buf, having_buf );
3962                 free( having_buf );
3963         }
3964
3965         if( order_by_list ) {
3966
3967                 if ( *order_by_list ) {
3968                         OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3969                         OSRF_BUFFER_ADD( sql_buf, order_by_list );
3970                 }
3971
3972                 free( order_by_list );
3973         }
3974
3975         if ( limit ){
3976                 const char* str = jsonObjectGetString(limit);
3977                 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
3978         }
3979
3980         if (offset) {
3981                 const char* str = jsonObjectGetString(offset);
3982                 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
3983         }
3984
3985         if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3986
3987         if (defaultselhash) jsonObjectFree(defaultselhash);
3988
3989         return buffer_release(sql_buf);
3990
3991 } // end of SELECT()
3992
3993 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
3994
3995         const char* locale = osrf_message_get_last_locale();
3996
3997         osrfHash* fields = osrfHashGet(meta, "fields");
3998         char* core_class = osrfHashGet(meta, "classname");
3999
4000         const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4001
4002         jsonObject* node = NULL;
4003         jsonObject* snode = NULL;
4004         jsonObject* onode = NULL;
4005         const jsonObject* _tmp = NULL;
4006         jsonObject* selhash = NULL;
4007         jsonObject* defaultselhash = NULL;
4008
4009         growing_buffer* sql_buf = buffer_init(128);
4010         growing_buffer* select_buf = buffer_init(128);
4011
4012         if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4013                 defaultselhash = jsonNewObjectType(JSON_HASH);
4014                 selhash = defaultselhash;
4015         }
4016         
4017         // If there's no SELECT list for the core class, build one
4018         if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
4019                 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4020                 
4021                 // Add every non-virtual field to the field list
4022                 osrfHash* field_def = NULL;
4023                 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4024                 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4025                         if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4026                                 const char* field = osrfHashIteratorKey( field_itr );
4027                                 jsonObjectPush( field_list, jsonNewObject( field ) );
4028                         }
4029                 }
4030                 osrfHashIteratorFree( field_itr );
4031                 jsonObjectSetKey( selhash, core_class, field_list );
4032         }
4033
4034         int first = 1;
4035         jsonIterator* class_itr = jsonNewIterator( selhash );
4036         while ( (snode = jsonIteratorNext( class_itr )) ) {
4037
4038                 const char* cname = class_itr->key;
4039                 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4040                 if (!idlClass) continue;
4041
4042                 if (strcmp(core_class,class_itr->key)) {
4043                         if (!join_hash) continue;
4044
4045                         jsonObject* found =  jsonObjectFindPath(join_hash, "//%s", class_itr->key);
4046                         if (!found->size) {
4047                                 jsonObjectFree(found);
4048                                 continue;
4049                         }
4050
4051                         jsonObjectFree(found);
4052                 }
4053
4054                 jsonIterator* select_itr = jsonNewIterator( snode );
4055                 while ( (node = jsonIteratorNext( select_itr )) ) {
4056                         const char* item_str = jsonObjectGetString( node );
4057                         osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4058                         char* fname = osrfHashGet(field, "name");
4059
4060                         if (!field) continue;
4061
4062                         if (first) {
4063                                 first = 0;
4064                         } else {
4065                                 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
4066                         }
4067
4068             if (locale) {
4069                         const char* i18n;
4070                                 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4071                                 if ( obj_is_true( no_i18n_obj ) )    // Suppress internationalization?
4072                                         i18n = NULL;
4073                                 else
4074                                         i18n = osrfHashGet(field, "i18n");
4075
4076                                 if( str_is_true( i18n ) ) {
4077                         char* pkey = osrfHashGet(idlClass, "primarykey");
4078                         char* tname = osrfHashGet(idlClass, "tablename");
4079
4080                     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);
4081                 } else {
4082                                 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4083                 }
4084             } else {
4085                             buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4086             }
4087                 }
4088
4089         jsonIteratorFree(select_itr);
4090         }
4091
4092     jsonIteratorFree(class_itr);
4093
4094         char* col_list = buffer_release(select_buf);
4095         char* table = getSourceDefinition(meta);
4096         if( !table )
4097                 table = strdup( "(null)" );
4098
4099         buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4100         free(col_list);
4101         free(table);
4102
4103         // Clear the query stack (as a fail-safe precaution against possible
4104         // leftover garbage); then push the first query frame onto the stack.
4105         clear_query_stack();
4106         push_query_frame();
4107         if( add_query_core( NULL, core_class ) ) {
4108                 if( ctx )
4109                         osrfAppSessionStatus(
4110                                 ctx->session,
4111                                 OSRF_STATUS_INTERNALSERVERERROR,
4112                                 "osrfMethodException",
4113                                 ctx->request,
4114                                 "Unable to build query frame for core class"
4115                         );
4116                 return NULL;
4117         }
4118
4119         if ( join_hash ) {
4120                 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4121                 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
4122                 OSRF_BUFFER_ADD(sql_buf, join_clause);
4123                 free(join_clause);
4124         }
4125
4126         osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL =  %s",
4127                                  MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
4128
4129         OSRF_BUFFER_ADD(sql_buf, " WHERE ");
4130
4131         char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4132         if (!pred) {
4133                 osrfAppSessionStatus(
4134                         ctx->session,
4135                         OSRF_STATUS_INTERNALSERVERERROR,
4136                                 "osrfMethodException",
4137                                 ctx->request,
4138                                 "Severe query error -- see error log for more details"
4139                         );
4140                 buffer_free(sql_buf);
4141                 if(defaultselhash) jsonObjectFree(defaultselhash);
4142                 clear_query_stack();
4143                 return NULL;
4144         } else {
4145                 buffer_add(sql_buf, pred);
4146                 free(pred);
4147         }
4148
4149         if (order_hash) {
4150                 char* string = NULL;
4151                 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4152
4153                         growing_buffer* order_buf = buffer_init(128);
4154
4155                         first = 1;
4156                         jsonIterator* class_itr = jsonNewIterator( _tmp );
4157                         while ( (snode = jsonIteratorNext( class_itr )) ) {
4158
4159                                 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4160                                         continue;
4161
4162                                 if ( snode->type == JSON_HASH ) {
4163
4164                                         jsonIterator* order_itr = jsonNewIterator( snode );
4165                                         while ( (onode = jsonIteratorNext( order_itr )) ) {
4166
4167                                                 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4168                                                                 class_itr->key, order_itr->key );
4169                                                 if ( !field_def )
4170                                                         continue;
4171
4172                                                 char* direction = NULL;
4173                                                 if ( onode->type == JSON_HASH ) {
4174                                                         if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4175                                                                 string = searchFieldTransform( class_itr->key, field_def, onode );
4176                                                                 if( ! string ) {
4177                                                                         osrfAppSessionStatus(
4178                                                                                 ctx->session,
4179                                                                                 OSRF_STATUS_INTERNALSERVERERROR,
4180                                                                                 "osrfMethodException",
4181                                                                                 ctx->request,
4182                                                                                 "Severe query error in ORDER BY clause -- see error log for more details"
4183                                                                         );
4184                                                                         jsonIteratorFree( order_itr );
4185                                                                         jsonIteratorFree( class_itr );
4186                                                                         buffer_free( order_buf );
4187                                                                         buffer_free( sql_buf );
4188                                                                         if( defaultselhash ) jsonObjectFree( defaultselhash );
4189                                                                         clear_query_stack();
4190                                                                         return NULL;
4191                                                                 }
4192                                                         } else {
4193                                                                 growing_buffer* field_buf = buffer_init(16);
4194                                                                 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4195                                                                 string = buffer_release(field_buf);
4196                                                         }
4197
4198                                                         if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4199                                                                 const char* dir = jsonObjectGetString(_tmp);
4200                                                                 if (!strncasecmp(dir, "d", 1)) {
4201                                                                         direction = " DESC";
4202                                                                 } else {
4203                                                                         free(direction);
4204                                                                 }
4205                                                         }
4206
4207                                                 } else {
4208                                                         string = strdup(order_itr->key);
4209                                                         const char* dir = jsonObjectGetString(onode);
4210                                                         if (!strncasecmp(dir, "d", 1)) {
4211                                                                 direction = " DESC";
4212                                                         } else {
4213                                                                 direction = " ASC";
4214                                                         }
4215                                                 }
4216
4217                                                 if (first) {
4218                                                         first = 0;
4219                                                 } else {
4220                                                         buffer_add(order_buf, ", ");
4221                                                 }
4222
4223                                                 buffer_add(order_buf, string);
4224                                                 free(string);
4225
4226                                                 if (direction) {
4227                                                         buffer_add(order_buf, direction);
4228                                                 }
4229
4230                                         }
4231
4232                     jsonIteratorFree(order_itr);
4233
4234                                 } else {
4235                                         const char* str = jsonObjectGetString(snode);
4236                                         buffer_add(order_buf, str);
4237                                         break;
4238                                 }
4239
4240                         }
4241
4242                         jsonIteratorFree(class_itr);
4243
4244                         string = buffer_release(order_buf);
4245
4246                         if ( *string ) {
4247                                 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4248                                 OSRF_BUFFER_ADD( sql_buf, string );
4249                         }
4250
4251                         free(string);
4252                 }
4253
4254                 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4255                         const char* str = jsonObjectGetString(_tmp);
4256                         buffer_fadd(
4257                                 sql_buf,
4258                                 " LIMIT %d",
4259                                 atoi(str)
4260                         );
4261                 }
4262
4263                 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4264                 if (_tmp) {
4265                         const char* str = jsonObjectGetString(_tmp);
4266                         buffer_fadd(
4267                                 sql_buf,
4268                                 " OFFSET %d",
4269                                 atoi(str)
4270                         );
4271                 }
4272         }
4273
4274         if (defaultselhash) jsonObjectFree(defaultselhash);
4275         clear_query_stack();
4276
4277         OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4278         return buffer_release(sql_buf);
4279 }
4280
4281 int doJSONSearch ( osrfMethodContext* ctx ) {
4282         if(osrfMethodVerifyContext( ctx )) {
4283                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
4284                 return -1;
4285         }
4286
4287         osrfLogDebug(OSRF_LOG_MARK, "Received query request");
4288
4289         int err = 0;
4290
4291         // XXX for now...
4292         dbhandle = writehandle;
4293
4294         jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4295
4296         int flags = 0;
4297
4298         if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4299                 flags |= SELECT_DISTINCT;
4300
4301         if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4302                 flags |= DISABLE_I18N;
4303
4304         osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4305         char* sql = SELECT(
4306                         ctx,
4307                         jsonObjectGetKey( hash, "select" ),
4308                         jsonObjectGetKey( hash, "from" ),
4309                         jsonObjectGetKey( hash, "where" ),
4310                         jsonObjectGetKey( hash, "having" ),
4311                         jsonObjectGetKey( hash, "order_by" ),
4312                         jsonObjectGetKey( hash, "limit" ),
4313                         jsonObjectGetKey( hash, "offset" ),
4314                         flags
4315         );
4316         clear_query_stack();
4317
4318         if (!sql) {
4319                 err = -1;
4320                 return err;
4321         }
4322
4323         osrfLogDebug(OSRF_LOG_MARK, "%s SQL =  %s", MODULENAME, sql);
4324         dbi_result result = dbi_conn_query(dbhandle, sql);
4325
4326         if(result) {
4327                 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4328
4329                 if (dbi_result_first_row(result)) {
4330                         /* JSONify the result */
4331                         osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4332
4333                         do {
4334                                 jsonObject* return_val = oilsMakeJSONFromResult( result );
4335                                 osrfAppRespond( ctx, return_val );
4336                 jsonObjectFree( return_val );
4337                         } while (dbi_result_next_row(result));
4338
4339                 } else {
4340                         osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4341                 }
4342
4343                 osrfAppRespondComplete( ctx, NULL );
4344
4345                 /* clean up the query */
4346                 dbi_result_free(result); 
4347
4348         } else {
4349                 err = -1;
4350                 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4351                 osrfAppSessionStatus(
4352                         ctx->session,
4353                         OSRF_STATUS_INTERNALSERVERERROR,
4354                         "osrfMethodException",
4355                         ctx->request,
4356                         "Severe query error -- see error log for more details"
4357                 );
4358         }
4359
4360         free(sql);
4361         return err;
4362 }
4363
4364 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4365                 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4366
4367         // XXX for now...
4368         dbhandle = writehandle;
4369
4370         osrfHash* links = osrfHashGet(meta, "links");
4371         osrfHash* fields = osrfHashGet(meta, "fields");
4372         char* core_class = osrfHashGet(meta, "classname");
4373         char* pkey = osrfHashGet(meta, "primarykey");
4374
4375         const jsonObject* _tmp;
4376         jsonObject* obj;
4377
4378         char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4379         if (!sql) {
4380                 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4381                 *err = -1;
4382                 return NULL;
4383         }
4384
4385         osrfLogDebug(OSRF_LOG_MARK, "%s SQL =  %s", MODULENAME, sql);
4386
4387         dbi_result result = dbi_conn_query(dbhandle, sql);
4388         if( NULL == result ) {
4389                 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4390                         MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4391                 osrfAppSessionStatus(
4392                         ctx->session,
4393                         OSRF_STATUS_INTERNALSERVERERROR,
4394                         "osrfMethodException",
4395                         ctx->request,
4396                         "Severe query error -- see error log for more details"
4397                 );
4398                 *err = -1;
4399                 free(sql);
4400                 return jsonNULL;
4401
4402         } else {
4403                 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4404         }
4405
4406         jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
4407         osrfHash* dedup = osrfNewHash();
4408
4409         if (dbi_result_first_row(result)) {
4410                 /* JSONify the result */
4411                 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4412                 do {
4413                         obj = oilsMakeFieldmapperFromResult( result, meta );
4414                         char* pkey_val = oilsFMGetString( obj, pkey );
4415                         if ( osrfHashGet( dedup, pkey_val ) ) {
4416                                 jsonObjectFree(obj);
4417                                 free(pkey_val);
4418                         } else {
4419                                 osrfHashSet( dedup, pkey_val, pkey_val );
4420                                 jsonObjectPush(res_list, obj);
4421                         }
4422                 } while (dbi_result_next_row(result));
4423         } else {
4424                 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
4425                         MODULENAME, sql );
4426         }
4427
4428         osrfHashFree(dedup);
4429         /* clean up the query */
4430         dbi_result_free(result);
4431         free(sql);
4432
4433         if (res_list->size && query_hash) {
4434                 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
4435                 if (_tmp) {
4436                         int x = (int)jsonObjectGetNumber(_tmp);
4437                         if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
4438
4439                         const jsonObject* temp_blob;
4440                         if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
4441
4442                                 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
4443                                 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
4444
4445                                 osrfStringArray* link_fields = NULL;
4446
4447                                 if (flesh_fields) {
4448                                         if (flesh_fields->size == 1) {
4449                                                 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
4450                                                 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
4451                                         }
4452
4453                                         if (!link_fields) {
4454                                                 jsonObject* _f;
4455                                                 link_fields = osrfNewStringArray(1);
4456                                                 jsonIterator* _i = jsonNewIterator( flesh_fields );
4457                                                 while ((_f = jsonIteratorNext( _i ))) {
4458                                                         osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4459                                                 }
4460                         jsonIteratorFree(_i);
4461                                         }
4462                                 }
4463
4464                                 jsonObject* cur;
4465                                 unsigned long res_idx = 0;
4466                                 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
4467
4468                                         int i = 0;
4469                                         char* link_field;
4470                                         
4471                                         while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4472
4473                                                 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4474
4475                                                 osrfHash* kid_link = osrfHashGet(links, link_field);
4476                                                 if (!kid_link) continue;
4477
4478                                                 osrfHash* field = osrfHashGet(fields, link_field);
4479                                                 if (!field) continue;
4480
4481                                                 osrfHash* value_field = field;
4482
4483                                                 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4484                                                 if (!kid_idl) continue;
4485
4486                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4487                                                         value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4488                                                 }
4489                                                         
4490                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4491                                                         value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4492                                                 }
4493
4494                                                 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4495
4496                                                 if (link_map->size > 0) {
4497                                                         jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4498                                                         jsonObjectPush(
4499                                                                 _kid_key,
4500                                                                 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4501                                                         );
4502
4503                                                         jsonObjectSetKey(
4504                                                                 flesh_blob,
4505                                                                 osrfHashGet(kid_link, "class"),
4506                                                                 _kid_key
4507                                                         );
4508                                                 };
4509
4510                                                 osrfLogDebug(
4511                                                         OSRF_LOG_MARK,
4512                                                         "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4513                                                         osrfHashGet(kid_link, "field"),
4514                                                         osrfHashGet(kid_link, "class"),
4515                                                         osrfHashGet(kid_link, "key"),
4516                                                         osrfHashGet(kid_link, "reltype")
4517                                                 );
4518
4519                                                 const char* search_key = jsonObjectGetString(
4520                                                         jsonObjectGetIndex(
4521                                                                 cur,
4522                                                                 atoi( osrfHashGet(value_field, "array_position") )
4523                                                         )
4524                                                 );
4525
4526                                                 if (!search_key) {
4527                                                         osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4528                                                         continue;
4529                                                 }
4530
4531                                                 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
4532
4533                                                 // construct WHERE clause
4534                                                 jsonObject* where_clause  = jsonNewObjectType(JSON_HASH);
4535                                                 jsonObjectSetKey(
4536                                                         where_clause,
4537                                                         osrfHashGet(kid_link, "key"),
4538                                                         jsonNewObject( search_key )
4539                                                 );
4540
4541                                                 // construct the rest of the query
4542                                                 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
4543                                                 jsonObjectSetKey( rest_of_query, "flesh",
4544                                                         jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4545                                                 );
4546
4547                                                 if (flesh_blob)
4548                                                         jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
4549
4550                                                 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
4551                                                         jsonObjectSetKey( rest_of_query, "order_by",
4552                                                                 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
4553                                                         );
4554                                                 }
4555
4556                                                 if (jsonObjectGetKeyConst(query_hash, "select")) {
4557                                                         jsonObjectSetKey( rest_of_query, "select",
4558                                                                 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
4559                                                         );
4560                                                 }
4561
4562                                                 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
4563                                                         where_clause, rest_of_query, err);
4564
4565                                                 jsonObjectFree( where_clause );
4566                                                 jsonObjectFree( rest_of_query );
4567
4568                                                 if(*err) {
4569                                                         osrfStringArrayFree(link_fields);
4570                                                         jsonObjectFree(res_list);
4571                                                         jsonObjectFree(flesh_blob);
4572                                                         return jsonNULL;
4573                                                 }
4574
4575                                                 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4576
4577                                                 jsonObject* X = NULL;
4578                                                 if ( link_map->size > 0 && kids->size > 0 ) {
4579                                                         X = kids;
4580                                                         kids = jsonNewObjectType(JSON_ARRAY);
4581
4582                                                         jsonObject* _k_node;
4583                                                         unsigned long res_idx = 0;
4584                                                         while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
4585                                                                 jsonObjectPush(
4586                                                                         kids,
4587                                                                         jsonObjectClone(
4588                                                                                 jsonObjectGetIndex(
4589                                                                                         _k_node,
4590                                                                                         (unsigned long)atoi(
4591                                                                                                 osrfHashGet(
4592                                                                                                         osrfHashGet(
4593                                                                                                                 osrfHashGet(
4594                                                                                                                         osrfHashGet(
4595                                                                                                                                 oilsIDL(),
4596                                                                                                                                 osrfHashGet(kid_link, "class")
4597                                                                                                                         ),
4598                                                                                                                         "fields"
4599                                                                                                                 ),
4600                                                                                                                 osrfStringArrayGetString( link_map, 0 )
4601                                                                                                         ),
4602                                                                                                         "array_position"
4603                                                                                                 )
4604                                                                                         )
4605                                                                                 )
4606                                                                         )
4607                                                                 );
4608                                                         } // end while loop traversing X
4609                                                 }
4610
4611                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4612                                                         osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4613                                                         jsonObjectSetIndex(
4614                                                                 cur,
4615                                                                 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4616                                                                 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4617                                                         );
4618                                                 }
4619
4620                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4621                                                         osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4622                                                         jsonObjectSetIndex(
4623                                                                 cur,
4624                                                                 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4625                                                                 jsonObjectClone( kids )
4626                                                         );
4627                                                 }
4628
4629                                                 if (X) {
4630                                                         jsonObjectFree(kids);
4631                                                         kids = X;
4632                                                 }
4633
4634                                                 jsonObjectFree( kids );
4635
4636                                                 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4637                                                 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
4638
4639                                         }
4640                                 } // end while loop traversing res_list
4641                                 jsonObjectFree( flesh_blob );
4642                                 osrfStringArrayFree(link_fields);
4643                         }
4644                 }
4645         }
4646
4647         return res_list;
4648 }
4649
4650
4651 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
4652
4653         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4654 #ifdef PCRUD
4655         jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
4656 #else
4657         jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
4658 #endif
4659
4660         if (!verifyObjectClass(ctx, target)) {
4661                 *err = -1;
4662                 return jsonNULL;
4663         }
4664
4665         if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4666                 osrfAppSessionStatus(
4667                         ctx->session,
4668                         OSRF_STATUS_BADREQUEST,
4669                         "osrfMethodException",
4670                         ctx->request,
4671                         "No active transaction -- required for UPDATE"
4672                 );
4673                 *err = -1;
4674                 return jsonNULL;
4675         }
4676
4677         // The following test is harmless but redundant.  If a class is
4678         // readonly, we don't register an update method for it.
4679         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4680                 osrfAppSessionStatus(
4681                         ctx->session,
4682                         OSRF_STATUS_BADREQUEST,
4683                         "osrfMethodException",
4684                         ctx->request,
4685                         "Cannot UPDATE readonly class"
4686                 );
4687                 *err = -1;
4688                 return jsonNULL;
4689         }
4690
4691         dbhandle = writehandle;
4692
4693         char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
4694
4695         // Set the last_xact_id
4696         int index = oilsIDL_ntop( target->classname, "last_xact_id" );
4697         if (index > -1) {
4698                 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
4699                                 trans_id, target->classname, index);
4700                 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
4701         }
4702
4703         char* pkey = osrfHashGet(meta, "primarykey");
4704         osrfHash* fields = osrfHashGet(meta, "fields");
4705
4706         char* id = oilsFMGetString( target, pkey );
4707
4708         osrfLogDebug(
4709                 OSRF_LOG_MARK,
4710                 "%s updating %s object with %s = %s",
4711                 MODULENAME,
4712                 osrfHashGet(meta, "fieldmapper"),
4713                 pkey,
4714                 id
4715         );
4716
4717         growing_buffer* sql = buffer_init(128);
4718         buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
4719
4720         int first = 1;
4721         osrfHash* field_def = NULL;
4722         osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4723         while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
4724
4725                 // Skip virtual fields, and the primary key
4726                 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
4727                         continue;
4728
4729                 const char* field_name = osrfHashIteratorKey( field_itr );
4730                 if( ! strcmp( field_name, pkey ) )
4731                         continue;
4732
4733                 const jsonObject* field_object = oilsFMGetObject( target, field_name );
4734
4735                 int value_is_numeric = 0;    // boolean
4736                 char* value;
4737                 if (field_object && field_object->classname) {
4738                         value = oilsFMGetString(
4739                                 field_object,
4740                                 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
4741                         );
4742                 } else if( JSON_BOOL == field_object->type ) {
4743                         if( jsonBoolIsTrue( field_object ) )
4744                                 value = strdup( "t" );
4745                         else
4746                                 value = strdup( "f" );
4747                 } else {
4748                         value = jsonObjectToSimpleString( field_object );
4749                         if( field_object && JSON_NUMBER == field_object->type )
4750                                 value_is_numeric = 1;
4751                 }
4752
4753                 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
4754                                 osrfHashGet(meta, "fieldmapper"), field_name, value);
4755
4756                 if (!field_object || field_object->type == JSON_NULL) {
4757                         if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
4758                                         && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
4759                                 if (first) first = 0;
4760                                 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4761                                 buffer_fadd( sql, " %s = NULL", field_name );
4762                         }
4763
4764                 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
4765                         if (first) first = 0;
4766                         else OSRF_BUFFER_ADD_CHAR(sql, ',');
4767
4768                         const char* numtype = get_datatype( field_def );
4769                         if ( !strncmp( numtype, "INT", 3 ) ) {
4770                                 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
4771                         } else if ( !strcmp( numtype, "NUMERIC" ) ) {
4772                                 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
4773                         } else {
4774                                 // Must really be intended as a string, so quote it
4775                                 if ( dbi_conn_quote_string(dbhandle, &value) ) {
4776                                         buffer_fadd( sql, " %s = %s", field_name, value );
4777                                 } else {
4778                                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4779                                         osrfAppSessionStatus(
4780                                                 ctx->session,
4781                                                 OSRF_STATUS_INTERNALSERVERERROR,
4782                                                 "osrfMethodException",
4783                                                 ctx->request,
4784                                                 "Error quoting string -- please see the error log for more details"
4785                                         );
4786                                         free(value);
4787                                         free(id);
4788                                         osrfHashIteratorFree( field_itr );
4789                                         buffer_free(sql);
4790                                         *err = -1;
4791                                         return jsonNULL;
4792                                 }
4793                         }
4794
4795                         osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
4796
4797                 } else {
4798                         if ( dbi_conn_quote_string(dbhandle, &value) ) {
4799                                 if (first) first = 0;
4800                                 else OSRF_BUFFER_ADD_CHAR(sql, ',');
4801                                 buffer_fadd( sql, " %s = %s", field_name, value );
4802
4803                         } else {
4804                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
4805                                 osrfAppSessionStatus(
4806                                         ctx->session,
4807                                         OSRF_STATUS_INTERNALSERVERERROR,
4808                                         "osrfMethodException",
4809                                         ctx->request,
4810                                         "Error quoting string -- please see the error log for more details"
4811                                 );
4812                                 free(value);
4813                                 free(id);
4814                                 osrfHashIteratorFree( field_itr );
4815                                 buffer_free(sql);
4816                                 *err = -1;
4817                                 return jsonNULL;
4818                         }
4819                 }
4820
4821                 free(value);
4822
4823         } // end while
4824
4825         osrfHashIteratorFree( field_itr );
4826
4827         jsonObject* obj = jsonNewObject(id);
4828
4829         if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4830                 dbi_conn_quote_string(dbhandle, &id);
4831
4832         buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
4833
4834         char* query = buffer_release(sql);
4835         osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
4836
4837         dbi_result result = dbi_conn_query(dbhandle, query);
4838         free(query);
4839
4840         if (!result) {
4841                 jsonObjectFree(obj);
4842                 obj = jsonNewObject(NULL);
4843                 osrfLogError(
4844                         OSRF_LOG_MARK,
4845                         "%s ERROR updating %s object with %s = %s",
4846                         MODULENAME,
4847                         osrfHashGet(meta, "fieldmapper"),
4848                         pkey,
4849                         id
4850                 );
4851         }
4852
4853         free(id);
4854
4855         return obj;
4856 }
4857
4858 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4859
4860         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4861
4862         if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4863                 osrfAppSessionStatus(
4864                         ctx->session,
4865                         OSRF_STATUS_BADREQUEST,
4866                         "osrfMethodException",
4867                         ctx->request,
4868                         "No active transaction -- required for DELETE"
4869                 );
4870                 *err = -1;
4871                 return jsonNULL;
4872         }
4873
4874         // The following test is harmless but redundant.  If a class is
4875         // readonly, we don't register a delete method for it.
4876         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4877                 osrfAppSessionStatus(
4878                         ctx->session,
4879                         OSRF_STATUS_BADREQUEST,
4880                         "osrfMethodException",
4881                         ctx->request,
4882                         "Cannot DELETE readonly class"
4883                 );
4884                 *err = -1;
4885                 return jsonNULL;
4886         }
4887
4888         dbhandle = writehandle;
4889
4890         jsonObject* obj;
4891
4892         char* pkey = osrfHashGet(meta, "primarykey");
4893
4894         int _obj_pos = 0;
4895 #ifdef PCRUD
4896                 _obj_pos = 1;
4897 #endif
4898
4899         char* id;
4900         if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4901                 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4902                         *err = -1;
4903                         return jsonNULL;
4904                 }
4905
4906                 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4907         } else {
4908 #ifdef PCRUD
4909         if (!verifyObjectPCRUD( ctx, NULL )) {
4910                         *err = -1;
4911                         return jsonNULL;
4912         }
4913 #endif
4914                 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4915         }
4916
4917         osrfLogDebug(
4918                 OSRF_LOG_MARK,
4919                 "%s deleting %s object with %s = %s",
4920                 MODULENAME,
4921                 osrfHashGet(meta, "fieldmapper"),
4922                 pkey,
4923                 id
4924         );
4925
4926         obj = jsonNewObject(id);
4927
4928         if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
4929                 dbi_conn_quote_string(writehandle, &id);
4930
4931         dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4932
4933         if (!result) {
4934                 jsonObjectFree(obj);
4935                 obj = jsonNewObject(NULL);
4936                 osrfLogError(
4937                         OSRF_LOG_MARK,
4938                         "%s ERROR deleting %s object with %s = %s",
4939                         MODULENAME,
4940                         osrfHashGet(meta, "fieldmapper"),
4941                         pkey,
4942                         id
4943                 );
4944         }
4945
4946         free(id);
4947
4948         return obj;
4949
4950 }
4951
4952
4953 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4954         if(!(result && meta)) return jsonNULL;
4955
4956         jsonObject* object = jsonNewObject(NULL);
4957         jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4958
4959         osrfHash* fields = osrfHashGet(meta, "fields");
4960
4961         osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4962
4963         osrfHash* _f;
4964         time_t _tmp_dt;
4965         char dt_string[256];
4966         struct tm gmdt;
4967
4968         int fmIndex;
4969         int columnIndex = 1;
4970         int attr;
4971         unsigned short type;
4972         const char* columnName;
4973
4974         /* cycle through the column list */
4975         while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
4976
4977                 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4978
4979                 fmIndex = -1; // reset the position
4980                 
4981                 /* determine the field type and storage attributes */
4982                 type = dbi_result_get_field_type_idx(result, columnIndex);
4983                 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
4984
4985                 /* fetch the fieldmapper index */
4986                 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4987                         
4988                         if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4989                                 continue;
4990                         
4991                         const char* pos = (char*)osrfHashGet(_f, "array_position");
4992                         if ( !pos ) continue;
4993
4994                         fmIndex = atoi( pos );
4995                         osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
4996                 } else {
4997                         continue;
4998                 }
4999
5000                 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5001                         jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
5002                 } else {
5003
5004                         switch( type ) {
5005
5006                                 case DBI_TYPE_INTEGER :
5007
5008                                         if( attr & DBI_INTEGER_SIZE8 ) 
5009                                                 jsonObjectSetIndex( object, fmIndex, 
5010                                                         jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
5011                                         else 
5012                                                 jsonObjectSetIndex( object, fmIndex, 
5013                                                         jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
5014
5015                                         break;
5016
5017                                 case DBI_TYPE_DECIMAL :
5018                                         jsonObjectSetIndex( object, fmIndex, 
5019                                                         jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
5020                                         break;
5021
5022                                 case DBI_TYPE_STRING :
5023
5024
5025                                         jsonObjectSetIndex(
5026                                                 object,
5027                                                 fmIndex,
5028                                                 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
5029                                         );
5030
5031                                         break;
5032
5033                                 case DBI_TYPE_DATETIME :
5034
5035                                         memset(dt_string, '\0', sizeof(dt_string));
5036                                         memset(&gmdt, '\0', sizeof(gmdt));
5037
5038                                         _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5039
5040
5041                                         if (!(attr & DBI_DATETIME_DATE)) {
5042                                                 gmtime_r( &_tmp_dt, &gmdt );
5043                                                 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5044                                         } else if (!(attr & DBI_DATETIME_TIME)) {
5045                                                 localtime_r( &_tmp_dt, &gmdt );
5046                                                 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5047                                         } else {
5048                                                 localtime_r( &_tmp_dt, &gmdt );
5049                                                 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5050                                         }
5051
5052                                         jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
5053
5054                                         break;
5055
5056                                 case DBI_TYPE_BINARY :
5057                                         osrfLogError( OSRF_LOG_MARK, 
5058                                                 "Can't do binary at column %s : index %d", columnName, columnIndex);
5059                         }
5060                 }
5061                 ++columnIndex;
5062         }
5063
5064         return object;
5065 }
5066
5067 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5068         if(!result) return jsonNULL;
5069
5070         jsonObject* object = jsonNewObject(NULL);
5071
5072         time_t _tmp_dt;
5073         char dt_string[256];
5074         struct tm gmdt;
5075
5076         int fmIndex;
5077         int columnIndex = 1;
5078         int attr;
5079         unsigned short type;
5080         const char* columnName;
5081
5082         /* cycle through the column list */
5083         while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5084
5085                 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5086
5087                 fmIndex = -1; // reset the position
5088                 
5089                 /* determine the field type and storage attributes */
5090                 type = dbi_result_get_field_type_idx(result, columnIndex);
5091                 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5092
5093                 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5094                         jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
5095                 } else {
5096
5097                         switch( type ) {
5098
5099                                 case DBI_TYPE_INTEGER :
5100
5101                                         if( attr & DBI_INTEGER_SIZE8 ) 
5102                                                 jsonObjectSetKey( object, columnName,
5103                                                                 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)) );
5104                                         else 
5105                                                 jsonObjectSetKey( object, columnName,
5106                                                                 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
5107                                         break;
5108
5109                                 case DBI_TYPE_DECIMAL :
5110                                         jsonObjectSetKey( object, columnName,
5111                                                         jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
5112                                         break;
5113
5114                                 case DBI_TYPE_STRING :
5115                                         jsonObjectSetKey( object, columnName,
5116                                                         jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
5117                                         break;
5118
5119                                 case DBI_TYPE_DATETIME :
5120
5121                                         memset(dt_string, '\0', sizeof(dt_string));
5122                                         memset(&gmdt, '\0', sizeof(gmdt));
5123
5124                                         _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5125
5126
5127                                         if (!(attr & DBI_DATETIME_DATE)) {
5128                                                 gmtime_r( &_tmp_dt, &gmdt );
5129                                                 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5130                                         } else if (!(attr & DBI_DATETIME_TIME)) {
5131                                                 localtime_r( &_tmp_dt, &gmdt );
5132                                                 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5133                                         } else {
5134                                                 localtime_r( &_tmp_dt, &gmdt );
5135                                                 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5136                                         }
5137
5138                                         jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
5139                                         break;
5140
5141                                 case DBI_TYPE_BINARY :
5142                                         osrfLogError( OSRF_LOG_MARK, 
5143                                                 "Can't do binary at column %s : index %d", columnName, columnIndex );
5144                         }
5145                 }
5146                 ++columnIndex;
5147         } // end while loop traversing result
5148
5149         return object;
5150 }
5151
5152 // Interpret a string as true or false
5153 static int str_is_true( const char* str ) {
5154         if( NULL == str || strcasecmp( str, "true" ) )
5155                 return 0;
5156         else
5157                 return 1;
5158 }
5159
5160 // Interpret a jsonObject as true or false
5161 static int obj_is_true( const jsonObject* obj ) {
5162         if( !obj )
5163                 return 0;
5164         else switch( obj->type )
5165         {
5166                 case JSON_BOOL :
5167                         if( obj->value.b )
5168                                 return 1;
5169                         else
5170                                 return 0;
5171                 case JSON_STRING :
5172                         if( strcasecmp( obj->value.s, "true" ) )
5173                                 return 0;
5174                         else
5175                                 return 1;
5176                 case JSON_NUMBER :          // Support 1/0 for perl's sake
5177                         if( jsonObjectGetNumber( obj ) == 1.0 )
5178                                 return 1;
5179                         else
5180                                 return 0;
5181                 default :
5182                         return 0;
5183         }
5184 }
5185
5186 // Translate a numeric code into a text string identifying a type of
5187 // jsonObject.  To be used for building error messages.
5188 static const char* json_type( int code ) {
5189         switch ( code )
5190         {
5191                 case 0 :
5192                         return "JSON_HASH";
5193                 case 1 :
5194                         return "JSON_ARRAY";
5195                 case 2 :
5196                         return "JSON_STRING";
5197                 case 3 :
5198                         return "JSON_NUMBER";
5199                 case 4 :
5200                         return "JSON_NULL";
5201                 case 5 :
5202                         return "JSON_BOOL";
5203                 default :
5204                         return "(unrecognized)";
5205         }
5206 }
5207
5208 // Extract the "primitive" attribute from an IDL field definition.
5209 // If we haven't initialized the app, then we must be running in
5210 // some kind of testbed.  In that case, default to "string".
5211 static const char* get_primitive( osrfHash* field ) {
5212         const char* s = osrfHashGet( field, "primitive" );
5213         if( !s ) {
5214                 if( child_initialized )
5215                         osrfLogError(
5216                                 OSRF_LOG_MARK,
5217                                 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5218                                 MODULENAME,
5219                                 osrfHashGet( field, "name" )
5220                         );
5221                 else
5222                         s = "string";
5223         }
5224         return s;
5225 }
5226
5227 // Extract the "datatype" attribute from an IDL field definition.
5228 // If we haven't initialized the app, then we must be running in
5229 // some kind of testbed.  In that case, default to to NUMERIC,
5230 // since we look at the datatype only for numbers.
5231 static const char* get_datatype( osrfHash* field ) {
5232         const char* s = osrfHashGet( field, "datatype" );
5233         if( !s ) {
5234                 if( child_initialized )
5235                         osrfLogError(
5236                                 OSRF_LOG_MARK,
5237                                 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5238                                 MODULENAME,
5239                                 osrfHashGet( field, "name" )
5240                         );
5241                 else
5242                         s = "NUMERIC";
5243         }
5244         return s;
5245 }
5246
5247 /*
5248 If the input string is potentially a valid SQL identifier, return 1.
5249 Otherwise return 0.
5250
5251 Purpose: to prevent certain kinds of SQL injection.  To that end we
5252 don't necessarily need to follow all the rules exactly, such as requiring
5253 that the first character not be a digit.
5254
5255 We allow leading and trailing white space.  In between, we do not allow
5256 punctuation (except for underscores and dollar signs), control 
5257 characters, or embedded white space.
5258
5259 More pedantically we should allow quoted identifiers containing arbitrary
5260 characters, but for the foreseeable future such quoted identifiers are not
5261 likely to be an issue.
5262 */
5263 static int is_identifier( const char* s) {
5264         if( !s )
5265                 return 0;
5266
5267         // Skip leading white space
5268         while( isspace( (unsigned char) *s ) )
5269                 ++s;
5270
5271         if( !s )
5272                 return 0;   // Nothing but white space?  Not okay.
5273
5274         // Check each character until we reach white space or
5275         // end-of-string.  Letters, digits, underscores, and 
5276         // dollar signs are okay. With the exception of periods
5277         // (as in schema.identifier), control characters and other
5278         // punctuation characters are not okay.  Anything else
5279         // is okay -- it could for example be part of a multibyte
5280         // UTF8 character such as a letter with diacritical marks,
5281         // and those are allowed.
5282         do {
5283                 if( isalnum( (unsigned char) *s )
5284                         || '.' == *s
5285                         || '_' == *s
5286                         || '$' == *s )
5287                         ;  // Fine; keep going
5288                 else if(   ispunct( (unsigned char) *s )
5289                                 || iscntrl( (unsigned char) *s ) )
5290                         return 0;
5291                         ++s;
5292         } while( *s && ! isspace( (unsigned char) *s ) );
5293
5294         // If we found any white space in the above loop,
5295         // the rest had better be all white space.
5296         
5297         while( isspace( (unsigned char) *s ) )
5298                 ++s;
5299
5300         if( *s )
5301                 return 0;   // White space was embedded within non-white space
5302
5303         return 1;
5304 }
5305
5306 /*
5307 Determine whether to accept a character string as a comparison operator.
5308 Return 1 if it's good, or 0 if it's bad.
5309
5310 We don't validate it for real.  We just make sure that it doesn't contain
5311 any semicolons or white space (with special exceptions for a few specific
5312 operators).   The idea is to block certain kinds of SQL injection.  If it
5313 has no semicolons or white space but it's still not a valid operator, then
5314 the database will complain.
5315
5316 Another approach would be to compare the string against a short list of
5317 approved operators.  We don't do that because we want to allow custom
5318 operators like ">100*", which would be difficult or impossible to
5319 express otherwise in a JSON query.
5320 */
5321 static int is_good_operator( const char* op ) {
5322         if( !op ) return 0;   // Sanity check
5323
5324         const char* s = op;
5325         while( *s ) {
5326                 if( isspace( (unsigned char) *s ) ) {
5327                         // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
5328                         // and IS NOT DISTINCT FROM.
5329                         if( !strcasecmp( op, "similar to" ) )
5330                                 return 1;
5331                         else if( !strcasecmp( op, "is distinct from" ) )
5332                                 return 1;
5333                         else if( !strcasecmp( op, "is not distinct from" ) )
5334                                 return 1;
5335                         else
5336                                 return 0;
5337                 }
5338                 else if( ';' == *s )
5339                         return 0;
5340                 ++s;
5341         }
5342         return 1;
5343 }
5344
5345 /* ----------------------------------------------------------------------------------
5346 The following machinery supports a stack of query frames for use by SELECT().
5347
5348 A query frame caches information about one level of a SELECT query.  When we enter
5349 a subquery, we push another query frame onto the stack, and pop it off when we leave.
5350
5351 The query frame stores information about the core class, and about any joined classes
5352 in the FROM clause.
5353
5354 The main purpose is to map table aliases to classes and tables, so that a query can
5355 join to the same table more than once.  A secondary goal is to reduce the number of
5356 lookups in the IDL by caching the results.
5357  ----------------------------------------------------------------------------------*/
5358
5359 #define STATIC_CLASS_INFO_COUNT 3
5360
5361 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
5362
5363 /* ---------------------------------------------------------------------------
5364  Allocate a ClassInfo as raw memory.  Except for the in_use flag, we don't
5365  initialize it here.
5366  ---------------------------------------------------------------------------*/
5367 static ClassInfo* allocate_class_info( void ) {
5368         // In order to reduce the number of mallocs and frees, we return a static
5369         // instance of ClassInfo, if we can find one that we're not already using.
5370         // We rely on the fact that the compiler will implicitly initialize the
5371         // static instances so that in_use == 0.
5372
5373         int i;
5374         for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5375                 if( ! static_class_info[ i ].in_use ) {
5376                         static_class_info[ i ].in_use = 1;
5377                         return static_class_info + i;
5378                 }
5379         }
5380
5381         // The static ones are all in use.  Malloc one.
5382
5383         return safe_malloc( sizeof( ClassInfo ) );
5384 }
5385
5386 /* --------------------------------------------------------------------------
5387  Free any malloc'd memory owned by a ClassInfo; return it to a pristine state
5388 ---------------------------------------------------------------------------*/
5389 static void clear_class_info( ClassInfo* info ) {
5390         // Sanity check
5391         if( ! info )
5392                 return;
5393
5394         // Free any malloc'd strings
5395
5396         if( info->alias != info->alias_store )
5397                 free( info->alias );
5398
5399         if( info->class_name != info->class_name_store )
5400                 free( info->class_name );
5401
5402         free( info->source_def );
5403
5404         info->alias = info->class_name = info->source_def = NULL;
5405         info->next = NULL;
5406 }
5407
5408 /* --------------------------------------------------------------------------
5409  Deallocate a ClassInfo and everything it owns
5410 ---------------------------------------------------------------------------*/
5411 static void free_class_info( ClassInfo* info ) {
5412         // Sanity check
5413         if( ! info )
5414                 return;
5415
5416         clear_class_info( info );
5417
5418         // If it's one of the static instances, just mark it as not in use
5419
5420         int i;
5421         for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5422                 if( info == static_class_info + i ) {
5423                         static_class_info[ i ].in_use = 0;
5424                         return;
5425                 }
5426         }
5427
5428         // Otherwise it must have been malloc'd, so free it
5429
5430         free( info );
5431 }
5432
5433 /* --------------------------------------------------------------------------
5434  Populate an already-allocated ClassInfo.  Return 0 if successful, 1 if not.
5435 ---------------------------------------------------------------------------*/
5436 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
5437         // Sanity checks
5438         if( ! info ){
5439                 osrfLogError( OSRF_LOG_MARK,
5440                                           "%s ERROR: No ClassInfo available to populate", MODULENAME );
5441                 info->alias = info->class_name = info->source_def = NULL;
5442                 info->class_def = info->fields = info->links = NULL;
5443                 return 1;
5444         }
5445
5446         if( ! class ) {
5447                 osrfLogError( OSRF_LOG_MARK,
5448                                           "%s ERROR: No class name provided for lookup", MODULENAME );
5449                 info->alias = info->class_name = info->source_def = NULL;
5450                 info->class_def = info->fields = info->links = NULL;
5451                 return 1;
5452         }
5453
5454         // Alias defaults to class name if not supplied
5455         if( ! alias || ! alias[ 0 ] )
5456                 alias = class;
5457
5458         // Look up class info in the IDL
5459         osrfHash* class_def = osrfHashGet( oilsIDL(), class );
5460         if( ! class_def ) {
5461                 osrfLogError( OSRF_LOG_MARK,
5462                                           "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
5463                 info->alias = info->class_name = info->source_def = NULL;
5464                 info->class_def = info->fields = info->links = NULL;
5465                 return 1;
5466         } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
5467                 osrfLogError( OSRF_LOG_MARK,
5468                                           "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
5469                 info->alias = info->class_name = info->source_def = NULL;
5470                 info->class_def = info->fields = info->links = NULL;
5471                 return 1;
5472         }
5473
5474         osrfHash* links = osrfHashGet( class_def, "links" );
5475         if( ! links ) {
5476                 osrfLogError( OSRF_LOG_MARK,
5477                                           "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
5478                 info->alias = info->class_name = info->source_def = NULL;
5479                 info->class_def = info->fields = info->links = NULL;
5480                 return 1;
5481         }
5482
5483         osrfHash* fields = osrfHashGet( class_def, "fields" );
5484         if( ! fields ) {
5485                 osrfLogError( OSRF_LOG_MARK,
5486                                           "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
5487                 info->alias = info->class_name = info->source_def = NULL;
5488                 info->class_def = info->fields = info->links = NULL;
5489                 return 1;
5490         }
5491
5492         char* source_def = getSourceDefinition( class_def );
5493         if( ! source_def )
5494                 return 1;
5495
5496         // We got everything we need, so populate the ClassInfo
5497         if( strlen( alias ) > ALIAS_STORE_SIZE )
5498                 info->alias = strdup( alias );
5499         else {
5500                 strcpy( info->alias_store, alias );
5501                 info->alias = info->alias_store;
5502         }
5503
5504         if( strlen( class ) > CLASS_NAME_STORE_SIZE )
5505                 info->class_name = strdup( class );
5506         else {
5507                 strcpy( info->class_name_store, class );
5508                 info->class_name = info->class_name_store;
5509         }
5510
5511         info->source_def = source_def;
5512
5513         info->class_def = class_def;
5514         info->links     = links;
5515         info->fields    = fields;
5516         
5517         return 0;
5518 }
5519
5520 #define STATIC_FRAME_COUNT 3
5521
5522 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
5523
5524 /* ---------------------------------------------------------------------------
5525  Allocate a ClassInfo as raw memory.  Except for the in_use flag, we don't
5526  initialize it here.
5527  ---------------------------------------------------------------------------*/
5528 static QueryFrame* allocate_frame( void ) {
5529         // In order to reduce the number of mallocs and frees, we return a static
5530         // instance of QueryFrame, if we can find one that we're not already using.
5531         // We rely on the fact that the compiler will implicitly initialize the
5532         // static instances so that in_use == 0.
5533
5534         int i;
5535         for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5536                 if( ! static_frame[ i ].in_use ) {
5537                         static_frame[ i ].in_use = 1;
5538                         return static_frame + i;
5539                 }
5540         }
5541
5542         // The static ones are all in use.  Malloc one.
5543
5544         return safe_malloc( sizeof( QueryFrame ) );
5545 }
5546
5547 /* --------------------------------------------------------------------------
5548  Free a QueryFrame, and all the memory it owns.
5549 ---------------------------------------------------------------------------*/
5550 static void free_query_frame( QueryFrame* frame ) {
5551         // Sanity check
5552         if( ! frame )
5553                 return;
5554
5555         clear_class_info( &frame->core );
5556
5557         // Free the join list
5558         ClassInfo* temp;
5559         ClassInfo* info = frame->join_list;
5560         while( info ) {
5561                 temp = info->next;
5562                 free_class_info( info );
5563                 info = temp;
5564         }
5565
5566         frame->join_list = NULL;
5567         frame->next = NULL;
5568
5569         // If the frame is a static instance, just mark it as unused
5570         int i;
5571         for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5572                 if( frame == static_frame + i ) {
5573                         static_frame[ i ].in_use = 0;
5574                         return;
5575                 }
5576         }
5577
5578         // Otherwise it must have been malloc'd, so free it
5579
5580         free( frame );
5581 }
5582
5583 /* --------------------------------------------------------------------------
5584  Search a given QueryFrame for a specified alias.  If you find it, return
5585  a pointer to the corresponding ClassInfo.  Otherwise return NULL.
5586 ---------------------------------------------------------------------------*/
5587 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
5588         if( ! frame || ! target ) {
5589                 return NULL;
5590         }
5591
5592         ClassInfo* found_class = NULL;
5593
5594         if( !strcmp( target, frame->core.alias ) )
5595                 return &(frame->core);
5596         else {
5597                 ClassInfo* curr_class = frame->join_list;
5598                 while( curr_class ) {
5599                         if( strcmp( target, curr_class->alias ) )
5600                                 curr_class = curr_class->next;
5601                         else {
5602                                 found_class = curr_class;
5603                                 break;
5604                         }
5605                 }
5606         }
5607
5608         return found_class;
5609 }
5610
5611 /* --------------------------------------------------------------------------
5612  Push a new (blank) QueryFrame onto the stack.
5613 ---------------------------------------------------------------------------*/
5614 static void push_query_frame( void ) {
5615         QueryFrame* frame = allocate_frame();
5616         frame->join_list = NULL;
5617         frame->next = curr_query;
5618
5619         // Initialize the ClassInfo for the core class
5620         ClassInfo* core = &frame->core;
5621         core->alias = core->class_name = core->source_def = NULL;
5622         core->class_def = core->fields = core->links = NULL;
5623
5624         curr_query = frame;
5625 }
5626
5627 /* --------------------------------------------------------------------------
5628  Pop a QueryFrame off the stack and destroy it
5629 ---------------------------------------------------------------------------*/
5630 static void pop_query_frame( void ) {
5631         // Sanity check
5632         if( ! curr_query )
5633                 return;
5634
5635         QueryFrame* popped = curr_query;
5636         curr_query = popped->next;
5637
5638         free_query_frame( popped );
5639 }
5640
5641 /* --------------------------------------------------------------------------
5642  Populate the ClassInfo for the core class.  Return 0 if successful, 1 if not.
5643 ---------------------------------------------------------------------------*/
5644 static int add_query_core( const char* alias, const char* class_name ) {
5645
5646         // Sanity checks
5647         if( ! curr_query ) {
5648                 osrfLogError( OSRF_LOG_MARK,
5649                                           "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
5650                 return 1;
5651         } else if( curr_query->core.alias ) {
5652                 osrfLogError( OSRF_LOG_MARK,
5653                                           "%s ERROR: Core class %s already populated as %s",
5654                                           MODULENAME, curr_query->core.class_name, curr_query->core.alias );
5655                 return 1;
5656         }
5657
5658         build_class_info( &curr_query->core, alias, class_name );
5659         if( curr_query->core.alias )
5660                 return 0;
5661         else {
5662                 osrfLogError( OSRF_LOG_MARK,
5663                                           "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
5664                 return 1;
5665         }
5666 }
5667
5668 /* --------------------------------------------------------------------------
5669  Search the current QueryFrame for a specified alias.  If you find it,
5670  return a pointer to the corresponding ClassInfo.  Otherwise return NULL.
5671 ---------------------------------------------------------------------------*/
5672 static ClassInfo* search_alias( const char* target ) {
5673         return search_alias_in_frame( curr_query, target );
5674 }
5675
5676 /* --------------------------------------------------------------------------
5677  Search all levels of query for a specified alias, starting with the
5678  current query.  If you find it, return a pointer to the corresponding
5679  ClassInfo.  Otherwise return NULL.
5680 ---------------------------------------------------------------------------*/
5681 static ClassInfo* search_all_alias( const char* target ) {
5682         ClassInfo* found_class = NULL;
5683         QueryFrame* curr_frame = curr_query;
5684         
5685         while( curr_frame ) {
5686                 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
5687                         break;
5688                 else
5689                         curr_frame = curr_frame->next;
5690         }
5691
5692         return found_class;
5693 }
5694
5695 /* --------------------------------------------------------------------------
5696  Add a class to the list of classes joined to the current query.
5697 ---------------------------------------------------------------------------*/
5698 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
5699
5700         if( ! classname || ! *classname ) {    // sanity check
5701                 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
5702                 return NULL;
5703         }
5704
5705         if( ! alias )
5706                 alias = classname;
5707
5708         const ClassInfo* conflict = search_alias( alias );
5709         if( conflict ) {
5710                 osrfLogError( OSRF_LOG_MARK,
5711                                           "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
5712                                           MODULENAME, alias, conflict->class_name );
5713                 return NULL;
5714         }
5715         
5716         ClassInfo* info = allocate_class_info();
5717
5718         if( build_class_info( info, alias, classname ) ) {
5719                 free_class_info( info );
5720                 return NULL;
5721         }
5722
5723         // Add the new ClassInfo to the join list of the current QueryFrame
5724         info->next = curr_query->join_list;
5725         curr_query->join_list = info;
5726
5727         return info;
5728 }
5729
5730 /* --------------------------------------------------------------------------
5731  Destroy all nodes on the query stack.
5732 ---------------------------------------------------------------------------*/
5733 static void clear_query_stack( void ) {
5734         while( curr_query )
5735                 pop_query_frame();
5736 }