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