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