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