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