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