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