]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/c-apps/oils_cstore.c
Enhance error handling in SELECT(). When a selected column is
[Evergreen.git] / Open-ILS / src / c-apps / oils_cstore.c
1 #include "opensrf/osrf_application.h"
2 #include "opensrf/osrf_settings.h"
3 #include "opensrf/osrf_message.h"
4 #include "opensrf/utils.h"
5 #include "opensrf/osrf_json.h"
6 #include "opensrf/log.h"
7 #include "openils/oils_utils.h"
8 #include <dbi/dbi.h>
9
10 #include <time.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <unistd.h>
14
15 #ifdef RSTORE
16 #  define MODULENAME "open-ils.reporter-store"
17 #else
18 #  ifdef PCRUD
19 #    define MODULENAME "open-ils.pcrud"
20 #  else
21 #    define MODULENAME "open-ils.cstore"
22 #  endif
23 #endif
24
25 #define SUBSELECT       4
26 #define DISABLE_I18N    2
27 #define SELECT_DISTINCT 1
28 #define AND_OP_JOIN     0
29 #define OR_OP_JOIN      1
30
31 int osrfAppChildInit();
32 int osrfAppInitialize();
33 void osrfAppChildExit();
34
35 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
36
37 int beginTransaction ( osrfMethodContext* );
38 int commitTransaction ( osrfMethodContext* );
39 int rollbackTransaction ( osrfMethodContext* );
40
41 int setSavepoint ( osrfMethodContext* );
42 int releaseSavepoint ( osrfMethodContext* );
43 int rollbackSavepoint ( osrfMethodContext* );
44
45 int doJSONSearch ( osrfMethodContext* );
46
47 int dispatchCRUDMethod ( osrfMethodContext* );
48 static jsonObject* doCreate ( osrfMethodContext*, int* );
49 static jsonObject* doRetrieve ( osrfMethodContext*, int* );
50 static jsonObject* doUpdate ( osrfMethodContext*, int* );
51 static jsonObject* doDelete ( osrfMethodContext*, int* );
52 static jsonObject* doFieldmapperSearch ( osrfMethodContext*, osrfHash*,
53         const jsonObject*, int* );
54 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
55 static jsonObject* oilsMakeJSONFromResult( dbi_result );
56
57 static char* searchWriteSimplePredicate ( const char*, osrfHash*,
58         const char*, const char*, const char* );
59 static char* searchSimplePredicate ( const char*, const char*, osrfHash*, const jsonObject* );
60 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
61 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject*);
62 static char* searchFieldTransformPredicate ( const char*, osrfHash*, jsonObject*, const char* );
63 static char* searchBETWEENPredicate ( const char*, osrfHash*, jsonObject* );
64 static char* searchINPredicate ( const char*, osrfHash*,
65                                                                  jsonObject*, const char*, osrfMethodContext* );
66 static char* searchPredicate ( const char*, osrfHash*, jsonObject*, osrfMethodContext* );
67 static char* searchJOIN ( const jsonObject*, osrfHash* );
68 static char* searchWHERE ( const jsonObject*, osrfHash*, int, osrfMethodContext* );
69 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
70
71 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
72
73 void userDataFree( void* );
74 static void sessionDataFree( char*, void* );
75 static char* getSourceDefinition( osrfHash* );
76 static int str_is_true( const char* str );
77 static int obj_is_true( const jsonObject* obj );
78 static const char* json_type( int code );
79
80 #ifdef PCRUD
81 static jsonObject* verifyUserPCRUD( osrfMethodContext* );
82 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
83 #endif
84
85 static dbi_conn writehandle; /* our MASTER db connection */
86 static dbi_conn dbhandle; /* our CURRENT db connection */
87 //static osrfHash * readHandles;
88 static jsonObject* jsonNULL = NULL; // 
89 static int max_flesh_depth = 100;
90
91 /* called when this process is about to exit */
92 void osrfAppChildExit() {
93     osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
94
95     int same = 0;
96     if (writehandle == dbhandle) same = 1;
97     if (writehandle) {
98         dbi_conn_query(writehandle, "ROLLBACK;");
99         dbi_conn_close(writehandle);
100         writehandle = NULL;
101     }
102     if (dbhandle && !same)
103         dbi_conn_close(dbhandle);
104
105     // XXX add cleanup of readHandles whenever that gets used
106
107     return;
108 }
109
110 int osrfAppInitialize() {
111
112     osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
113     osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
114
115     if (!oilsIDLInit( osrf_settings_host_value("/IDL") )) return 1; /* return non-zero to indicate error */
116
117     growing_buffer* method_name = buffer_init(64);
118 #ifndef PCRUD
119     // Generic search thingy
120     buffer_add(method_name, MODULENAME);
121         buffer_add(method_name, ".json_query");
122         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
123                                                    "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
124 #endif
125
126     // first we register all the transaction and savepoint methods
127     buffer_reset(method_name);
128         OSRF_BUFFER_ADD(method_name, MODULENAME);
129         OSRF_BUFFER_ADD(method_name, ".transaction.begin");
130         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
131                                                    "beginTransaction", "", 0, 0 );
132
133     buffer_reset(method_name);
134         OSRF_BUFFER_ADD(method_name, MODULENAME);
135         OSRF_BUFFER_ADD(method_name, ".transaction.commit");
136         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
137                                                    "commitTransaction", "", 0, 0 );
138
139     buffer_reset(method_name);
140         OSRF_BUFFER_ADD(method_name, MODULENAME);
141         OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
142         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
143                                                    "rollbackTransaction", "", 0, 0 );
144
145     buffer_reset(method_name);
146         OSRF_BUFFER_ADD(method_name, MODULENAME);
147         OSRF_BUFFER_ADD(method_name, ".savepoint.set");
148         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
149                                                    "setSavepoint", "", 1, 0 );
150
151     buffer_reset(method_name);
152         OSRF_BUFFER_ADD(method_name, MODULENAME);
153         OSRF_BUFFER_ADD(method_name, ".savepoint.release");
154         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
155                                                    "releaseSavepoint", "", 1, 0 );
156
157     buffer_reset(method_name);
158         OSRF_BUFFER_ADD(method_name, MODULENAME);
159         OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
160         osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
161                                                    "rollbackSavepoint", "", 1, 0 );
162
163     buffer_free(method_name);
164
165         static const char* global_method[] = {
166                 "create",
167                 "retrieve",
168                 "update",
169                 "delete",
170                 "search",
171                 "id_list"
172         };
173         const int global_method_count
174                 = sizeof( global_method ) / sizeof ( global_method[0] );
175         
176     int c_index = 0; 
177     char* classname;
178     osrfStringArray* classes = osrfHashKeys( oilsIDL() );
179     osrfLogDebug(OSRF_LOG_MARK, "%d classes loaded", classes->size );
180     osrfLogDebug(OSRF_LOG_MARK,
181                 "At least %d methods will be generated", classes->size * global_method_count);
182
183     while ( (classname = osrfStringArrayGetString(classes, c_index++)) ) {
184         osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
185
186         osrfHash* idlClass = osrfHashGet(oilsIDL(), classname);
187
188         if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), MODULENAME )) {
189             osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on", MODULENAME, classname);
190             continue;
191         }
192
193                 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
194                         osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
195                         continue;
196                 }
197
198         // Look up some other attributes of the current class
199         const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
200                 const char* readonly = osrfHashGet(idlClass, "readonly");
201 #ifdef PCRUD
202         osrfHash* idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
203 #endif
204
205         int i;
206         for( i = 0; i < global_method_count; ++i ) {
207             const char* method_type = global_method[ i ];
208             osrfLogDebug(OSRF_LOG_MARK,
209                 "Using files to build %s class methods for %s", method_type, classname);
210
211             if (!idlClass_fieldmapper) continue;
212
213 #ifdef PCRUD
214             if (!idlClass_permacrud) continue;
215
216             const char* tmp_method = method_type;
217             if ( *tmp_method == 'i' || *tmp_method == 's') {
218                 tmp_method = "retrieve";
219             }
220             if (!osrfHashGet( idlClass_permacrud, tmp_method )) continue;
221 #endif
222
223             if (    str_is_true( readonly ) &&
224                     ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd')
225                ) continue;
226
227             osrfHash* method_meta = osrfNewHash();
228             osrfHashSet(method_meta, idlClass, "class");
229
230             method_name =  buffer_init(64);
231 #ifdef PCRUD
232             buffer_fadd(method_name, "%s.%s.%s", MODULENAME, method_type, classname);
233 #else
234             char* st_tmp = NULL;
235             char* part = NULL;
236             char* _fm = strdup( idlClass_fieldmapper );
237             part = strtok_r(_fm, ":", &st_tmp);
238
239             buffer_fadd(method_name, "%s.direct.%s", MODULENAME, part);
240
241             while ((part = strtok_r(NULL, ":", &st_tmp))) {
242                                 OSRF_BUFFER_ADD_CHAR(method_name, '.');
243                                 OSRF_BUFFER_ADD(method_name, part);
244             }
245                         OSRF_BUFFER_ADD_CHAR(method_name, '.');
246                         OSRF_BUFFER_ADD(method_name, method_type);
247             free(_fm);
248 #endif
249
250             char* method = buffer_release(method_name);
251
252             osrfHashSet( method_meta, method, "methodname" );
253             osrfHashSet( method_meta, strdup(method_type), "methodtype" );
254
255             int flags = 0;
256             if (*method_type == 'i' || *method_type == 's') {
257                 flags = flags | OSRF_METHOD_STREAMING;
258             }
259
260             osrfAppRegisterExtendedMethod(
261                     MODULENAME,
262                     method,
263                     "dispatchCRUDMethod",
264                     "",
265                     1,
266                     flags,
267                     (void*)method_meta
268                     );
269
270             free(method);
271         }
272     }
273
274     return 0;
275 }
276
277 static char* getSourceDefinition( osrfHash* class ) {
278
279         char* tabledef = osrfHashGet(class, "tablename");
280
281         if (tabledef) {
282                 tabledef = strdup(tabledef);
283         } else {
284                 tabledef = osrfHashGet(class, "source_definition");
285                 if( tabledef ) {
286                         growing_buffer* tablebuf = buffer_init(128);
287                         buffer_fadd( tablebuf, "(%s)", tabledef );
288                         tabledef = buffer_release(tablebuf);
289                 } else {
290                         const char* classname = osrfHashGet( class, "classname" );
291                         if( !classname )
292                                 classname = "???";
293                         osrfLogError(
294                                 OSRF_LOG_MARK,
295                                 "%s ERROR No tablename or source_definition for class \"%s\"",
296                                 MODULENAME,
297                                 classname
298                         );
299                 }
300         }
301
302         return tabledef;
303 }
304
305 /**
306  * Connects to the database 
307  */
308 int osrfAppChildInit() {
309
310     osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
311     dbi_initialize(NULL);
312     osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
313
314     char* driver        = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
315     char* user  = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
316     char* host  = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
317     char* port  = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
318     char* db    = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
319     char* pw    = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
320     char* md    = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion", MODULENAME);
321
322     osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
323     writehandle = dbi_conn_new(driver);
324
325     if(!writehandle) {
326         osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
327         return -1;
328     }
329     osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
330
331     osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database.  host=%s, "
332             "port=%s, user=%s, pw=%s, db=%s", MODULENAME, host, port, user, pw, db );
333
334     if(host) dbi_conn_set_option(writehandle, "host", host );
335     if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
336     if(user) dbi_conn_set_option(writehandle, "username", user);
337     if(pw) dbi_conn_set_option(writehandle, "password", pw );
338     if(db) dbi_conn_set_option(writehandle, "dbname", db );
339
340     if(md) max_flesh_depth = atoi(md);
341     if(max_flesh_depth < 0) max_flesh_depth = 1;
342     if(max_flesh_depth > 1000) max_flesh_depth = 1000;
343
344     free(user);
345     free(host);
346     free(port);
347     free(db);
348     free(pw);
349
350     const char* err;
351     if (dbi_conn_connect(writehandle) < 0) {
352         sleep(1);
353         if (dbi_conn_connect(writehandle) < 0) {
354             dbi_conn_error(writehandle, &err);
355             osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
356             return -1;
357         }
358     }
359
360     osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
361
362     int attr;
363     unsigned short type;
364     int i = 0; 
365     char* classname;
366     osrfStringArray* classes = osrfHashKeys( oilsIDL() );
367
368     while ( (classname = osrfStringArrayGetString(classes, i++)) ) {
369         osrfHash* class = osrfHashGet( oilsIDL(), classname );
370         osrfHash* fields = osrfHashGet( class, "fields" );
371
372                 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
373                         osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
374                         continue;
375                 }
376
377         char* tabledef = getSourceDefinition(class);
378                 if( !tabledef )
379                         tabledef = strdup( "(null)" );
380
381         growing_buffer* sql_buf = buffer_init(32);
382         buffer_fadd( sql_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
383
384         free(tabledef);
385
386         char* sql = buffer_release(sql_buf);
387         osrfLogDebug(OSRF_LOG_MARK, "%s Investigatory SQL = %s", MODULENAME, sql);
388
389         dbi_result result = dbi_conn_query(writehandle, sql);
390         free(sql);
391
392         if (result) {
393
394             int columnIndex = 1;
395             const char* columnName;
396             osrfHash* _f;
397             while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
398
399                 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
400
401                 /* fetch the fieldmapper index */
402                 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
403
404                     osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", (char*)columnName);
405
406                     /* determine the field type and storage attributes */
407                     type = dbi_result_get_field_type(result, columnName);
408                     attr = dbi_result_get_field_attribs(result, columnName);
409
410                     switch( type ) {
411
412                         case DBI_TYPE_INTEGER :
413
414                             if ( !osrfHashGet(_f, "primitive") )
415                                 osrfHashSet(_f,"number", "primitive");
416
417                             if( attr & DBI_INTEGER_SIZE8 ) 
418                                 osrfHashSet(_f,"INT8", "datatype");
419                             else 
420                                 osrfHashSet(_f,"INT", "datatype");
421                             break;
422
423                         case DBI_TYPE_DECIMAL :
424                             if ( !osrfHashGet(_f, "primitive") )
425                                 osrfHashSet(_f,"number", "primitive");
426
427                             osrfHashSet(_f,"NUMERIC", "datatype");
428                             break;
429
430                         case DBI_TYPE_STRING :
431                             if ( !osrfHashGet(_f, "primitive") )
432                                 osrfHashSet(_f,"string", "primitive");
433                             osrfHashSet(_f,"TEXT", "datatype");
434                             break;
435
436                         case DBI_TYPE_DATETIME :
437                             if ( !osrfHashGet(_f, "primitive") )
438                                 osrfHashSet(_f,"string", "primitive");
439
440                             osrfHashSet(_f,"TIMESTAMP", "datatype");
441                             break;
442
443                         case DBI_TYPE_BINARY :
444                             if ( !osrfHashGet(_f, "primitive") )
445                                 osrfHashSet(_f,"string", "primitive");
446
447                             osrfHashSet(_f,"BYTEA", "datatype");
448                     }
449
450                     osrfLogDebug(
451                             OSRF_LOG_MARK,
452                             "Setting [%s] to primitive [%s] and datatype [%s]...",
453                             (char*)columnName,
454                             osrfHashGet(_f, "primitive"),
455                             osrfHashGet(_f, "datatype")
456                             );
457                 }
458             }
459             dbi_result_free(result);
460         } else {
461             osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", (char*)classname);
462         }
463     }
464
465     osrfStringArrayFree(classes);
466
467     return 0;
468 }
469
470 void userDataFree( void* blob ) {
471     osrfHashFree( (osrfHash*)blob );
472     return;
473 }
474
475 static void sessionDataFree( char* key, void* item ) {
476     if (!(strcmp(key,"xact_id"))) {
477         if (writehandle)
478             dbi_conn_query(writehandle, "ROLLBACK;");
479         free(item);
480     }
481
482     return;
483 }
484
485 int beginTransaction ( osrfMethodContext* ctx ) {
486         if(osrfMethodVerifyContext( ctx )) {
487                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
488                 return -1;
489         }
490
491 #ifdef PCRUD
492     jsonObject* user = verifyUserPCRUD( ctx );
493     if (!user) return -1;
494     jsonObjectFree(user);
495 #endif
496
497     dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
498     if (!result) {
499         osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
500         osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error starting transaction" );
501         return -1;
502     } else {
503         jsonObject* ret = jsonNewObject(ctx->session->session_id);
504         osrfAppRespondComplete( ctx, ret );
505         jsonObjectFree(ret);
506
507         if (!ctx->session->userData) {
508             ctx->session->userData = osrfNewHash();
509             osrfHashSetCallback((osrfHash*)ctx->session->userData, &sessionDataFree);
510         }
511
512         osrfHashSet( (osrfHash*)ctx->session->userData, strdup( ctx->session->session_id ), "xact_id" );
513         ctx->session->userDataFree = &userDataFree;
514
515     }
516     return 0;
517 }
518
519 int setSavepoint ( osrfMethodContext* ctx ) {
520         if(osrfMethodVerifyContext( ctx )) {
521                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
522                 return -1;
523         }
524
525     int spNamePos = 0;
526 #ifdef PCRUD
527     spNamePos = 1;
528     jsonObject* user = verifyUserPCRUD( ctx );
529     if (!user) return -1;
530     jsonObjectFree(user);
531 #endif
532
533     if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
534         osrfAppSessionStatus(
535                 ctx->session,
536                 OSRF_STATUS_INTERNALSERVERERROR,
537                 "osrfMethodException",
538                 ctx->request,
539                 "No active transaction -- required for savepoints"
540                 );
541         return -1;
542     }
543
544     char* spName = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, spNamePos));
545
546     dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
547     if (!result) {
548         osrfLogError(
549                 OSRF_LOG_MARK,
550                 "%s: Error creating savepoint %s in transaction %s",
551                 MODULENAME,
552                 spName,
553                 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
554                 );
555         osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error creating savepoint" );
556         free(spName);
557         return -1;
558     } else {
559         jsonObject* ret = jsonNewObject(spName);
560         osrfAppRespondComplete( ctx, ret );
561         jsonObjectFree(ret);
562     }
563     free(spName);
564     return 0;
565 }
566
567 int releaseSavepoint ( osrfMethodContext* ctx ) {
568         if(osrfMethodVerifyContext( ctx )) {
569                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
570                 return -1;
571         }
572
573         int spNamePos = 0;
574 #ifdef PCRUD
575     spNamePos = 1;
576     jsonObject* user = verifyUserPCRUD( ctx );
577     if (!user) return -1;
578     jsonObjectFree(user);
579 #endif
580
581     if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
582         osrfAppSessionStatus(
583                 ctx->session,
584                 OSRF_STATUS_INTERNALSERVERERROR,
585                 "osrfMethodException",
586                 ctx->request,
587                 "No active transaction -- required for savepoints"
588                 );
589         return -1;
590     }
591
592     char* spName = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, spNamePos));
593
594     dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
595     if (!result) {
596         osrfLogError(
597                 OSRF_LOG_MARK,
598                 "%s: Error releasing savepoint %s in transaction %s",
599                 MODULENAME,
600                 spName,
601                 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
602                 );
603         osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error releasing savepoint" );
604         free(spName);
605         return -1;
606     } else {
607         jsonObject* ret = jsonNewObject(spName);
608         osrfAppRespondComplete( ctx, ret );
609         jsonObjectFree(ret);
610     }
611     free(spName);
612     return 0;
613 }
614
615 int rollbackSavepoint ( osrfMethodContext* ctx ) {
616         if(osrfMethodVerifyContext( ctx )) {
617                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
618                 return -1;
619         }
620
621         int spNamePos = 0;
622 #ifdef PCRUD
623     spNamePos = 1;
624     jsonObject* user = verifyUserPCRUD( ctx );
625     if (!user) return -1;
626     jsonObjectFree(user);
627 #endif
628
629     if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
630         osrfAppSessionStatus(
631                 ctx->session,
632                 OSRF_STATUS_INTERNALSERVERERROR,
633                 "osrfMethodException",
634                 ctx->request,
635                 "No active transaction -- required for savepoints"
636                 );
637         return -1;
638     }
639
640     char* spName = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, spNamePos));
641
642     dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
643     if (!result) {
644         osrfLogError(
645                 OSRF_LOG_MARK,
646                 "%s: Error rolling back savepoint %s in transaction %s",
647                 MODULENAME,
648                 spName,
649                 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
650                 );
651         osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error rolling back savepoint" );
652         free(spName);
653         return -1;
654     } else {
655         jsonObject* ret = jsonNewObject(spName);
656         osrfAppRespondComplete( ctx, ret );
657         jsonObjectFree(ret);
658     }
659     free(spName);
660     return 0;
661 }
662
663 int commitTransaction ( osrfMethodContext* ctx ) {
664         if(osrfMethodVerifyContext( ctx )) {
665                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
666                 return -1;
667         }
668
669 #ifdef PCRUD
670     jsonObject* user = verifyUserPCRUD( ctx );
671     if (!user) return -1;
672     jsonObjectFree(user);
673 #endif
674
675     if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
676         osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to commit" );
677         return -1;
678     }
679
680     dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
681     if (!result) {
682         osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
683         osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error committing transaction" );
684         return -1;
685     } else {
686         osrfHashRemove(ctx->session->userData, "xact_id");
687         jsonObject* ret = jsonNewObject(ctx->session->session_id);
688         osrfAppRespondComplete( ctx, ret );
689         jsonObjectFree(ret);
690     }
691     return 0;
692 }
693
694 int rollbackTransaction ( osrfMethodContext* ctx ) {
695         if(osrfMethodVerifyContext( ctx )) {
696                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
697                 return -1;
698         }
699
700 #ifdef PCRUD
701     jsonObject* user = verifyUserPCRUD( ctx );
702     if (!user) return -1;
703     jsonObjectFree(user);
704 #endif
705
706     if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
707         osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "No active transaction to roll back" );
708         return -1;
709     }
710
711     dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
712     if (!result) {
713         osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
714         osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error rolling back transaction" );
715         return -1;
716     } else {
717         osrfHashRemove(ctx->session->userData, "xact_id");
718         jsonObject* ret = jsonNewObject(ctx->session->session_id);
719         osrfAppRespondComplete( ctx, ret );
720         jsonObjectFree(ret);
721     }
722     return 0;
723 }
724
725 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
726         if(osrfMethodVerifyContext( ctx )) {
727                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
728                 return -1;
729         }
730
731         osrfHash* meta = (osrfHash*) ctx->method->userData;
732     osrfHash* class_obj = osrfHashGet( meta, "class" );
733
734     int err = 0;
735
736     const char* methodtype = osrfHashGet(meta, "methodtype");
737     jsonObject * obj = NULL;
738
739     if (!strcmp(methodtype, "create")) {
740         obj = doCreate(ctx, &err);
741         osrfAppRespondComplete( ctx, obj );
742     }
743     else if (!strcmp(methodtype, "retrieve")) {
744         obj = doRetrieve(ctx, &err);
745         osrfAppRespondComplete( ctx, obj );
746     }
747     else if (!strcmp(methodtype, "update")) {
748         obj = doUpdate(ctx, &err);
749         osrfAppRespondComplete( ctx, obj );
750     }
751     else if (!strcmp(methodtype, "delete")) {
752         obj = doDelete(ctx, &err);
753         osrfAppRespondComplete( ctx, obj );
754     }
755     else if (!strcmp(methodtype, "search")) {
756
757         jsonObject* _p = jsonObjectClone( ctx->params );
758 #ifdef PCRUD
759         jsonObjectFree(_p);
760         _p = jsonParseString("[]");
761         jsonObjectPush(_p, jsonObjectClone(jsonObjectGetIndex(ctx->params, 1)));
762         jsonObjectPush(_p, jsonObjectClone(jsonObjectGetIndex(ctx->params, 2)));
763 #endif
764
765         obj = doFieldmapperSearch(ctx, class_obj, _p, &err);
766
767         jsonObjectFree(_p);
768         if(err) return err;
769
770         jsonObject* cur;
771         jsonIterator* itr = jsonNewIterator( obj );
772         while ((cur = jsonIteratorNext( itr ))) {
773 #ifdef PCRUD
774             if(!verifyObjectPCRUD(ctx, cur)) continue;
775 #endif
776             osrfAppRespond( ctx, cur );
777         }
778         jsonIteratorFree(itr);
779         osrfAppRespondComplete( ctx, NULL );
780
781     } else if (!strcmp(methodtype, "id_list")) {
782
783         jsonObject* _p = jsonObjectClone( ctx->params );
784 #ifdef PCRUD
785         jsonObjectFree(_p);
786         _p = jsonParseString("[]");
787         jsonObjectPush(_p, jsonObjectClone(jsonObjectGetIndex(ctx->params, 1)));
788         jsonObjectPush(_p, jsonObjectClone(jsonObjectGetIndex(ctx->params, 2)));
789 #endif
790
791         if (jsonObjectGetIndex( _p, 1 )) {
792             jsonObjectRemoveKey( jsonObjectGetIndex( _p, 1 ), "select" );
793             jsonObjectRemoveKey( jsonObjectGetIndex( _p, 1 ), "no_i18n" );
794             jsonObjectRemoveKey( jsonObjectGetIndex( _p, 1 ), "flesh" );
795             jsonObjectRemoveKey( jsonObjectGetIndex( _p, 1 ), "flesh_columns" );
796         } else {
797             jsonObjectSetIndex( _p, 1, jsonNewObjectType(JSON_HASH) );
798         }
799
800                 jsonObjectSetKey( jsonObjectGetIndex( _p, 1 ), "no_i18n", jsonNewBoolObject( 1 ) );
801
802         jsonObjectSetKey(
803             jsonObjectGetIndex( _p, 1 ),
804             "select",
805             jsonParseStringFmt(
806                 "{ \"%s\":[\"%s\"] }",
807                 osrfHashGet( class_obj, "classname" ),
808                 osrfHashGet( class_obj, "primarykey" )
809             )
810         );
811
812         obj = doFieldmapperSearch(ctx, class_obj, _p, &err);
813
814         jsonObjectFree(_p);
815         if(err) return err;
816
817         jsonObject* cur;
818         jsonIterator* itr = jsonNewIterator( obj );
819         while ((cur = jsonIteratorNext( itr ))) {
820 #ifdef PCRUD
821             if(!verifyObjectPCRUD(ctx, cur)) continue;
822 #endif
823             osrfAppRespond(
824                     ctx,
825                     oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
826                     );
827         }
828         jsonIteratorFree(itr);
829         osrfAppRespondComplete( ctx, NULL );
830
831     } else {
832         osrfAppRespondComplete( ctx, obj );
833     }
834
835     jsonObjectFree(obj);
836
837     return err;
838 }
839
840 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
841
842     int ret = 1;
843     osrfHash* meta = (osrfHash*) ctx->method->userData;
844     osrfHash* class = osrfHashGet( meta, "class" );
845
846     if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
847
848         growing_buffer* msg = buffer_init(128);
849         buffer_fadd(
850                 msg,
851                 "%s: %s method for type %s was passed a %s",
852                 MODULENAME,
853                 osrfHashGet(meta, "methodtype"),
854                 osrfHashGet(class, "classname"),
855                 param->classname
856                 );
857
858         char* m = buffer_release(msg);
859         osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException", ctx->request, m );
860
861         free(m);
862
863         return 0;
864     }
865
866 #ifdef PCRUD
867     ret = verifyObjectPCRUD( ctx, param );
868 #endif
869
870     return ret;
871 }
872
873 #ifdef PCRUD
874
875 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
876     char* auth = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, 0 ) );
877     jsonObject* auth_object = jsonNewObject(auth);
878     jsonObject* user = oilsUtilsQuickReq("open-ils.auth","open-ils.auth.session.retrieve", auth_object);
879     jsonObjectFree(auth_object);
880
881     if (!user->classname || strcmp(user->classname, "au")) {
882
883         growing_buffer* msg = buffer_init(128);
884         buffer_fadd(
885             msg,
886             "%s: permacrud received a bad auth token: %s",
887             MODULENAME,
888             auth
889         );
890
891         char* m = buffer_release(msg);
892         osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException", ctx->request, m );
893
894         free(m);
895         jsonObjectFree(user);
896         user = jsonNULL;
897     }
898
899     free(auth);
900     return user;
901
902 }
903
904 static int verifyObjectPCRUD (  osrfMethodContext* ctx, const jsonObject* obj ) {
905
906     dbhandle = writehandle;
907
908     osrfHash* meta = (osrfHash*) ctx->method->userData;
909     osrfHash* class = osrfHashGet( meta, "class" );
910     char* method_type = strdup( osrfHashGet(meta, "methodtype") );
911     int fetch = 0;
912
913     if ( ( *method_type == 's' || *method_type == 'i' ) ) {
914         free(method_type);
915         method_type = strdup("retrieve"); // search and id_list are equivelant to retrieve for this
916     } else if ( *method_type == 'u' || *method_type == 'd' ) {
917         fetch = 1; // MUST go to the db for the object for update and delete
918     }
919
920     osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
921     free(method_type);
922
923     if (!pcrud) {
924         // No permacrud for this method type on this class
925
926         growing_buffer* msg = buffer_init(128);
927         buffer_fadd(
928             msg,
929             "%s: %s on class %s has no permacrud IDL entry",
930             MODULENAME,
931             osrfHashGet(meta, "methodtype"),
932             osrfHashGet(class, "classname")
933         );
934
935         char* m = buffer_release(msg);
936         osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN, "osrfMethodException", ctx->request, m );
937
938         free(m);
939
940         return 0;
941     }
942
943     jsonObject* user = verifyUserPCRUD( ctx );
944     if (!user) return 0;
945
946     int userid = atoi( oilsFMGetString( user, "id" ) );
947     jsonObjectFree(user);
948
949     osrfStringArray* permission = osrfHashGet(pcrud, "permission");
950     osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
951     osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
952
953     osrfStringArray* context_org_array = osrfNewStringArray(1);
954
955     int err = 0;
956     char* pkey_value = NULL;
957         if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
958             osrfLogDebug( OSRF_LOG_MARK, "global-level permissions required, fetching top of the org tree" );
959
960         // check for perm at top of org tree
961         jsonObject* _tmp_params = jsonParseString("[{\"parent_ou\":null}]");
962                 jsonObject* _list = doFieldmapperSearch(ctx, osrfHashGet( oilsIDL(), "aou" ), _tmp_params, &err);
963
964         jsonObject* _tree_top = jsonObjectGetIndex(_list, 0);
965
966         if (!_tree_top) {
967             jsonObjectFree(_tmp_params);
968             jsonObjectFree(_list);
969     
970             growing_buffer* msg = buffer_init(128);
971                         OSRF_BUFFER_ADD( msg, MODULENAME );
972                         OSRF_BUFFER_ADD( msg,
973                                 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
974     
975             char* m = buffer_release(msg);
976             osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
977             free(m);
978
979             return 0;
980         }
981
982         osrfStringArrayAdd( context_org_array, oilsFMGetString( _tree_top, "id" ) );
983             osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", osrfStringArrayGetString(context_org_array, 0) );
984
985         jsonObjectFree(_tmp_params);
986         jsonObjectFree(_list);
987
988     } else {
989             osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, fetching context org ids" );
990             char* pkey = osrfHashGet(class, "primarykey");
991         jsonObject *param = NULL;
992
993         if (obj->classname) {
994             pkey_value = oilsFMGetString( obj, pkey );
995             if (!fetch) param = jsonObjectClone(obj);
996                 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s", pkey_value );
997         } else {
998             pkey_value = jsonObjectToSimpleString( obj );
999             fetch = 1;
1000                 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value of %s and retrieving from the database", pkey_value );
1001         }
1002
1003         if (fetch) {
1004             jsonObject* _tmp_params = jsonParseStringFmt("[{\"%s\":\"%s\"}]", pkey, pkey_value);
1005                 jsonObject* _list = doFieldmapperSearch(
1006                 ctx,
1007                 class,
1008                 _tmp_params,
1009                 &err
1010             );
1011     
1012             param = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1013     
1014             jsonObjectFree(_tmp_params);
1015             jsonObjectFree(_list);
1016         }
1017
1018         if (!param) {
1019             osrfLogDebug( OSRF_LOG_MARK, "Object not found in the database with primary key %s of %s", pkey, pkey_value );
1020
1021             growing_buffer* msg = buffer_init(128);
1022             buffer_fadd(
1023                 msg,
1024                 "%s: no object found with primary key %s of %s",
1025                 MODULENAME,
1026                 pkey,
1027                 pkey_value
1028             );
1029         
1030             char* m = buffer_release(msg);
1031             osrfAppSessionStatus(
1032                 ctx->session,
1033                 OSRF_STATUS_INTERNALSERVERERROR,
1034                 "osrfMethodException",
1035                 ctx->request,
1036                 m
1037             );
1038         
1039             free(m);
1040             if (pkey_value) free(pkey_value);
1041
1042             return 0;
1043         }
1044
1045         if (local_context->size > 0) {
1046                 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified", local_context->size);
1047             int i = 0;
1048             char* lcontext = NULL;
1049             while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1050                 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1051                     osrfLogDebug(
1052                     OSRF_LOG_MARK,
1053                     "adding class-local field %s (value: %s) to the context org list",
1054                     lcontext,
1055                     osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1056                 );
1057             }
1058         }
1059
1060         osrfStringArray* class_list;
1061
1062         if (foreign_context) {
1063             class_list = osrfHashKeys( foreign_context );
1064                 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_list->size);
1065
1066             if (class_list->size > 0) {
1067     
1068                 int i = 0;
1069                 char* class_name = NULL;
1070                 while ( (class_name = osrfStringArrayGetString(class_list, i++)) ) {
1071                     osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1072
1073                         osrfLogDebug(
1074                         OSRF_LOG_MARK,
1075                         "%d foreign context fields(s) specified for class %s",
1076                         ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1077                         class_name
1078                     );
1079     
1080                     char* foreign_pkey = osrfHashGet(fcontext, "field");
1081                     char* foreign_pkey_value = oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1082
1083                     jsonObject* _tmp_params = jsonParseStringFmt(
1084                         "[{\"%s\":\"%s\"}]",
1085                         foreign_pkey,
1086                         foreign_pkey_value
1087                     );
1088     
1089                         jsonObject* _list = doFieldmapperSearch(
1090                         ctx,
1091                         osrfHashGet( oilsIDL(), class_name ),
1092                         _tmp_params,
1093                         &err
1094                     );
1095
1096                     jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1097                     jsonObjectFree(_tmp_params);
1098                     jsonObjectFree(_list);
1099  
1100                     osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1101
1102                     if (_fparam && jump_list) {
1103                         char* flink = NULL;
1104                         int k = 0;
1105                         while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1106                             free(foreign_pkey_value);
1107
1108                             osrfHash* foreign_link_hash = oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1109
1110                             foreign_pkey_value = oilsFMGetString(_fparam, flink);
1111                             foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1112
1113                             _tmp_params = jsonParseStringFmt(
1114                                 "[{\"%s\":\"%s\"}]",
1115                                 foreign_pkey,
1116                                 foreign_pkey_value
1117                             );
1118
1119                                 _list = doFieldmapperSearch(
1120                                 ctx,
1121                                 osrfHashGet( oilsIDL(), osrfHashGet( foreign_link_hash, "class" ) ),
1122                                 _tmp_params,
1123                                 &err
1124                             );
1125
1126                             _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1127                             jsonObjectFree(_tmp_params);
1128                             jsonObjectFree(_list);
1129                         }
1130                     }
1131
1132            
1133                     if (!_fparam) {
1134
1135                         growing_buffer* msg = buffer_init(128);
1136                         buffer_fadd(
1137                             msg,
1138                             "%s: no object found with primary key %s of %s",
1139                             MODULENAME,
1140                             foreign_pkey,
1141                             foreign_pkey_value
1142                         );
1143                 
1144                         char* m = buffer_release(msg);
1145                         osrfAppSessionStatus(
1146                             ctx->session,
1147                             OSRF_STATUS_INTERNALSERVERERROR,
1148                             "osrfMethodException",
1149                             ctx->request,
1150                             m
1151                         );
1152
1153                         free(m);
1154                         osrfStringArrayFree(class_list);
1155                         free(foreign_pkey_value);
1156                         jsonObjectFree(param);
1157
1158                         return 0;
1159                     }
1160         
1161                     free(foreign_pkey_value);
1162     
1163                     int j = 0;
1164                     char* foreign_field = NULL;
1165                     while ( (foreign_field = osrfStringArrayGetString(osrfHashGet(fcontext,"context"), j++)) ) {
1166                         osrfStringArrayAdd( context_org_array, oilsFMGetString( _fparam, foreign_field ) );
1167                             osrfLogDebug(
1168                             OSRF_LOG_MARK,
1169                             "adding foreign class %s field %s (value: %s) to the context org list",
1170                             class_name,
1171                             foreign_field,
1172                             osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1173                         );
1174                     }
1175
1176                     jsonObjectFree(_fparam);
1177                 }
1178     
1179                 osrfStringArrayFree(class_list);
1180             }
1181         }
1182
1183         jsonObjectFree(param);
1184     }
1185
1186     char* context_org = NULL;
1187     char* perm = NULL;
1188     int OK = 0;
1189
1190     if (permission->size == 0) {
1191             osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1192         OK = 1;
1193     }
1194     
1195     int i = 0;
1196     while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1197         int j = 0;
1198         while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1199             dbi_result result;
1200
1201             if (pkey_value) {
1202                     osrfLogDebug(
1203                     OSRF_LOG_MARK,
1204                     "Checking object permission [%s] for user %d on object %s (class %s) at org %d",
1205                     perm,
1206                     userid,
1207                     pkey_value,
1208                     osrfHashGet(class, "classname"),
1209                     atoi(context_org)
1210                 );
1211
1212                 result = dbi_conn_queryf(
1213                     writehandle,
1214                     "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1215                     userid,
1216                     perm,
1217                     osrfHashGet(class, "classname"),
1218                     pkey_value,
1219                     atoi(context_org)
1220                 );
1221
1222                 if (result) {
1223                     osrfLogDebug(
1224                         OSRF_LOG_MARK,
1225                         "Recieved a result for object permission [%s] for user %d on object %s (class %s) at org %d",
1226                         perm,
1227                         userid,
1228                         pkey_value,
1229                         osrfHashGet(class, "classname"),
1230                         atoi(context_org)
1231                     );
1232
1233                     if (dbi_result_first_row(result)) {
1234                         jsonObject* return_val = oilsMakeJSONFromResult( result );
1235                         char* has_perm = jsonObjectToSimpleString( jsonObjectGetKeyConst(return_val, "has_perm") );
1236
1237                             osrfLogDebug(
1238                             OSRF_LOG_MARK,
1239                             "Status of object permission [%s] for user %d on object %s (class %s) at org %d is %s",
1240                             perm,
1241                             userid,
1242                             pkey_value,
1243                             osrfHashGet(class, "classname"),
1244                             atoi(context_org),
1245                             has_perm
1246                         );
1247
1248                         if ( *has_perm == 't' ) OK = 1;
1249                         free(has_perm); 
1250                         jsonObjectFree(return_val);
1251                     }
1252
1253                     dbi_result_free(result); 
1254                     if (OK) break;
1255                 }
1256             }
1257
1258                 osrfLogDebug( OSRF_LOG_MARK, "Checking non-object permission [%s] for user %d at org %d", perm, userid, atoi(context_org) );
1259             result = dbi_conn_queryf(
1260                 writehandle,
1261                 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1262                 userid,
1263                 perm,
1264                 atoi(context_org)
1265             );
1266
1267             if (result) {
1268                     osrfLogDebug( OSRF_LOG_MARK, "Received a result for permission [%s] for user %d at org %d", perm, userid, atoi(context_org) );
1269                 if (dbi_result_first_row(result)) {
1270                     jsonObject* return_val = oilsMakeJSONFromResult( result );
1271                     char* has_perm = jsonObjectToSimpleString( jsonObjectGetKeyConst(return_val, "has_perm") );
1272                         osrfLogDebug( OSRF_LOG_MARK, "Status of permission [%s] for user %d at org %d is [%s]", perm, userid, atoi(context_org), has_perm );
1273                     if ( *has_perm == 't' ) OK = 1;
1274                     free(has_perm); 
1275                     jsonObjectFree(return_val);
1276                 }
1277
1278                 dbi_result_free(result); 
1279                 if (OK) break;
1280             }
1281
1282         }
1283         if (OK) break;
1284     }
1285
1286     if (pkey_value) free(pkey_value);
1287     osrfStringArrayFree(context_org_array);
1288
1289     return OK;
1290 }
1291 #endif
1292
1293
1294 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1295
1296         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1297 #ifdef PCRUD
1298         jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1299         jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1300 #else
1301         jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1302         jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1303 #endif
1304
1305         if (!verifyObjectClass(ctx, target)) {
1306                 *err = -1;
1307                 return jsonNULL;
1308         }
1309
1310         osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1311
1312         if (!ctx->session || !ctx->session->userData || !osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
1313                 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1314
1315                 osrfAppSessionStatus(
1316                         ctx->session,
1317                         OSRF_STATUS_BADREQUEST,
1318                         "osrfMethodException",
1319                         ctx->request,
1320                         "No active transaction -- required for CREATE"
1321                 );
1322                 *err = -1;
1323                 return jsonNULL;
1324         }
1325
1326         // The following test is harmless but redundant.  If a class is
1327         // readonly, we don't register a create method for it.
1328         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1329                 osrfAppSessionStatus(
1330                         ctx->session,
1331                         OSRF_STATUS_BADREQUEST,
1332                         "osrfMethodException",
1333                         ctx->request,
1334                         "Cannot INSERT readonly class"
1335                 );
1336                 *err = -1;
1337                 return jsonNULL;
1338         }
1339
1340
1341         char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
1342
1343         // Set the last_xact_id
1344         int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1345         if (index > -1) {
1346                 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
1347                 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1348         }       
1349
1350         osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1351
1352         dbhandle = writehandle;
1353
1354         osrfHash* fields = osrfHashGet(meta, "fields");
1355         char* pkey = osrfHashGet(meta, "primarykey");
1356         char* seq = osrfHashGet(meta, "sequence");
1357
1358         growing_buffer* table_buf = buffer_init(128);
1359         growing_buffer* col_buf = buffer_init(128);
1360         growing_buffer* val_buf = buffer_init(128);
1361
1362         OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1363         OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1364         OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1365         buffer_add(val_buf,"VALUES (");
1366
1367
1368         int i = 0;
1369         int first = 1;
1370         char* field_name;
1371         osrfStringArray* field_list = osrfHashKeys( fields );
1372         while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
1373
1374                 osrfHash* field = osrfHashGet( fields, field_name );
1375
1376                 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1377                         continue;
1378
1379                 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1380
1381                 char* value;
1382                 if (field_object && field_object->classname) {
1383                         value = oilsFMGetString(
1384                                 field_object,
1385                                 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1386                         );
1387                 } else {
1388                         value = jsonObjectToSimpleString( field_object );
1389                 }
1390
1391
1392                 if (first) {
1393                         first = 0;
1394                 } else {
1395                         OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1396                         OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1397                 }
1398
1399                 buffer_add(col_buf, field_name);
1400
1401                 if (!field_object || field_object->type == JSON_NULL) {
1402                         buffer_add( val_buf, "DEFAULT" );
1403                         
1404                 } else if ( !strcmp(osrfHashGet(field, "primitive"), "number") ) {
1405                         if ( !strcmp(osrfHashGet(field, "datatype"), "INT8") ) {
1406                                 buffer_fadd( val_buf, "%lld", atoll(value) );
1407                                 
1408                         } else if ( !strcmp(osrfHashGet(field, "datatype"), "INT") ) {
1409                                 buffer_fadd( val_buf, "%d", atoi(value) );
1410                                 
1411                         } else if ( !strcmp(osrfHashGet(field, "datatype"), "NUMERIC") ) {
1412                                 buffer_fadd( val_buf, "%f", atof(value) );
1413                         }
1414                 } else {
1415                         if ( dbi_conn_quote_string(writehandle, &value) ) {
1416                                 OSRF_BUFFER_ADD( val_buf, value );
1417
1418                         } else {
1419                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1420                                 osrfAppSessionStatus(
1421                                         ctx->session,
1422                                         OSRF_STATUS_INTERNALSERVERERROR,
1423                                         "osrfMethodException",
1424                                         ctx->request,
1425                                         "Error quoting string -- please see the error log for more details"
1426                                 );
1427                                 free(value);
1428                                 buffer_free(table_buf);
1429                                 buffer_free(col_buf);
1430                                 buffer_free(val_buf);
1431                                 *err = -1;
1432                                 return jsonNULL;
1433                         }
1434                 }
1435
1436                 free(value);
1437                 
1438         }
1439
1440
1441         OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1442         OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1443
1444         char* table_str = buffer_release(table_buf);
1445         char* col_str   = buffer_release(col_buf);
1446         char* val_str   = buffer_release(val_buf);
1447         growing_buffer* sql = buffer_init(128);
1448         buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1449         free(table_str);
1450         free(col_str);
1451         free(val_str);
1452
1453         char* query = buffer_release(sql);
1454
1455         osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1456
1457         
1458         dbi_result result = dbi_conn_query(writehandle, query);
1459
1460         jsonObject* obj = NULL;
1461
1462         if (!result) {
1463                 obj = jsonNewObject(NULL);
1464                 osrfLogError(
1465                         OSRF_LOG_MARK,
1466                         "%s ERROR inserting %s object using query [%s]",
1467                         MODULENAME,
1468                         osrfHashGet(meta, "fieldmapper"),
1469                         query
1470                 );
1471                 osrfAppSessionStatus(
1472                         ctx->session,
1473                         OSRF_STATUS_INTERNALSERVERERROR,
1474                         "osrfMethodException",
1475                         ctx->request,
1476                         "INSERT error -- please see the error log for more details"
1477                 );
1478                 *err = -1;
1479         } else {
1480
1481                 char* id = oilsFMGetString(target, pkey);
1482                 if (!id) {
1483                         unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1484                         growing_buffer* _id = buffer_init(10);
1485                         buffer_fadd(_id, "%lld", new_id);
1486                         id = buffer_release(_id);
1487                 }
1488
1489                 // Find quietness specification, if present
1490                 char* quiet_str = NULL;
1491                 if ( options ) {
1492                         const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1493                         if( quiet_obj )
1494                                 quiet_str = jsonObjectToSimpleString( quiet_obj );
1495                 }
1496
1497                 if( str_is_true( quiet_str ) ) {  // if quietness is specified
1498                         obj = jsonNewObject(id);
1499                 }
1500                 else {
1501
1502                         jsonObject* fake_params = jsonNewObjectType(JSON_ARRAY);
1503                         jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH));
1504
1505                         jsonObjectSetKey(
1506                                 jsonObjectGetIndex(fake_params, 0),
1507                                 pkey,
1508                                 jsonNewObject(id)
1509                         );
1510
1511                         jsonObject* list = doFieldmapperSearch( ctx,meta, fake_params, err);
1512
1513                         if(*err) {
1514                                 jsonObjectFree( fake_params );
1515                                 obj = jsonNULL;
1516                         } else {
1517                                 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1518                         }
1519
1520                         jsonObjectFree( list );
1521                         jsonObjectFree( fake_params );
1522                 }
1523
1524                 if(quiet_str) free(quiet_str);
1525                 free(id);
1526         }
1527
1528         free(query);
1529
1530         return obj;
1531
1532 }
1533
1534
1535 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1536
1537     int id_pos = 0;
1538     int order_pos = 1;
1539
1540 #ifdef PCRUD
1541     id_pos = 1;
1542     order_pos = 2;
1543 #endif
1544
1545         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1546
1547         char* id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, id_pos));
1548         jsonObject* order_hash = jsonObjectGetIndex(ctx->params, order_pos);
1549
1550         osrfLogDebug(
1551                 OSRF_LOG_MARK,
1552                 "%s retrieving %s object with primary key value of %s",
1553                 MODULENAME,
1554                 osrfHashGet(meta, "fieldmapper"),
1555                 id
1556         );
1557         free(id);
1558
1559         jsonObject* fake_params = jsonNewObjectType(JSON_ARRAY);
1560         jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH));
1561
1562         jsonObjectSetKey(
1563                 jsonObjectGetIndex(fake_params, 0),
1564                 osrfHashGet(meta, "primarykey"),
1565                 jsonObjectClone(jsonObjectGetIndex(ctx->params, id_pos))
1566         );
1567
1568
1569         if (order_hash) jsonObjectPush(fake_params, jsonObjectClone(order_hash) );
1570
1571         jsonObject* list = doFieldmapperSearch( ctx,meta, fake_params, err);
1572
1573         if(*err) {
1574                 jsonObjectFree( fake_params );
1575                 return jsonNULL;
1576         }
1577
1578         jsonObject* obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1579
1580         jsonObjectFree( list );
1581         jsonObjectFree( fake_params );
1582
1583 #ifdef PCRUD
1584         if(!verifyObjectPCRUD(ctx, obj)) {
1585         jsonObjectFree(obj);
1586         *err = -1;
1587
1588         growing_buffer* msg = buffer_init(128);
1589                 OSRF_BUFFER_ADD( msg, MODULENAME );
1590                 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1591
1592         char* m = buffer_release(msg);
1593         osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1594
1595         free(m);
1596
1597                 return jsonNULL;
1598         }
1599 #endif
1600
1601         return obj;
1602 }
1603
1604 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1605         growing_buffer* val_buf = buffer_init(32);
1606
1607         if ( !strncmp(osrfHashGet(field, "datatype"), "INT", (size_t)3) ) {
1608                 if (value->type == JSON_NUMBER) buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1609                 else {
1610                         char* val_str = jsonObjectToSimpleString(value);
1611                         buffer_fadd( val_buf, "%ld", atol(val_str) );
1612                         free(val_str);
1613                 }
1614
1615         } else if ( !strcmp(osrfHashGet(field, "datatype"), "NUMERIC") ) {
1616                 if (value->type == JSON_NUMBER) buffer_fadd( val_buf, "%f",  jsonObjectGetNumber(value) );
1617                 else {
1618                         char* val_str = jsonObjectToSimpleString(value);
1619                         buffer_fadd( val_buf, "%f", atof(val_str) );
1620                         free(val_str);
1621                 }
1622         }
1623
1624         return buffer_release(val_buf);
1625 }
1626
1627 static char* searchINPredicate (const char* class, osrfHash* field,
1628                 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1629         growing_buffer* sql_buf = buffer_init(32);
1630         
1631         buffer_fadd(
1632                 sql_buf,
1633                 "\"%s\".%s ",
1634                 class,
1635                 osrfHashGet(field, "name")
1636         );
1637
1638         if (!op) {
1639                 buffer_add(sql_buf, "IN (");
1640         } else if (!(strcasecmp(op,"not in"))) {
1641                 buffer_add(sql_buf, "NOT IN (");
1642         } else {
1643                 buffer_add(sql_buf, "IN (");
1644         }
1645
1646     if (node->type == JSON_HASH) {
1647         // subquery predicate
1648         char* subpred = SELECT(
1649             ctx,
1650             jsonObjectGetKey( node, "select" ),
1651             jsonObjectGetKey( node, "from" ),
1652             jsonObjectGetKey( node, "where" ),
1653             jsonObjectGetKey( node, "having" ),
1654             jsonObjectGetKey( node, "order_by" ),
1655             jsonObjectGetKey( node, "limit" ),
1656             jsonObjectGetKey( node, "offset" ),
1657             SUBSELECT
1658         );
1659
1660         buffer_add(sql_buf, subpred);
1661         free(subpred);
1662
1663     } else if (node->type == JSON_ARRAY) {
1664         // litteral value list
1665         int in_item_index = 0;
1666         int in_item_first = 1;
1667         jsonObject* in_item;
1668         while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1669     
1670                 if (in_item_first)
1671                         in_item_first = 0;
1672                 else
1673                         buffer_add(sql_buf, ", ");
1674     
1675                 if ( !strcmp(osrfHashGet(field, "primitive"), "number") ) {
1676                         char* val = jsonNumberToDBString( field, in_item );
1677                         OSRF_BUFFER_ADD( sql_buf, val );
1678                         free(val);
1679     
1680                 } else {
1681                         char* key_string = jsonObjectToSimpleString(in_item);
1682                         if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1683                                 OSRF_BUFFER_ADD( sql_buf, key_string );
1684                                 free(key_string);
1685                         } else {
1686                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1687                                 free(key_string);
1688                                 buffer_free(sql_buf);
1689                                 return NULL;
1690                         }
1691                 }
1692         }
1693     }
1694     
1695         OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1696
1697         return buffer_release(sql_buf);
1698 }
1699
1700 // Receive a JSON_ARRAY representing a function call.  The first
1701 // entry in the array is the function name.  The rest are parameters.
1702 static char* searchValueTransform( const jsonObject* array ) {
1703         growing_buffer* sql_buf = buffer_init(32);
1704
1705         char* val = NULL;
1706         jsonObject* func_item;
1707         
1708         // Get the function name
1709         if( array->size > 0 ) {
1710                 func_item = jsonObjectGetIndex( array, 0 );
1711                 val = jsonObjectToSimpleString( func_item );
1712                 OSRF_BUFFER_ADD( sql_buf, val );
1713                 OSRF_BUFFER_ADD( sql_buf, "( " );
1714                 free(val);
1715         }
1716         
1717         // Get the parameters
1718         int func_item_index = 1;   // We already grabbed the zeroth entry
1719         while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1720
1721                 // Add a separator comma, if we need one
1722                 if( func_item_index > 2 )
1723                         buffer_add( sql_buf, ", " );
1724
1725                 // Add the current parameter
1726                 if (func_item->type == JSON_NULL) {
1727                         buffer_add( sql_buf, "NULL" );
1728                 } else {
1729                         val = jsonObjectToSimpleString(func_item);
1730                         if ( dbi_conn_quote_string(dbhandle, &val) ) {
1731                                 OSRF_BUFFER_ADD( sql_buf, val );
1732                                 free(val);
1733                         } else {
1734                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1735                                 buffer_free(sql_buf);
1736                                 free(val);
1737                                 return NULL;
1738                         }
1739                 }
1740         }
1741
1742         buffer_add( sql_buf, " )" );
1743
1744         return buffer_release(sql_buf);
1745 }
1746
1747 static char* searchFunctionPredicate (const char* class, osrfHash* field,
1748                 const jsonObject* node, const char* node_key) {
1749         growing_buffer* sql_buf = buffer_init(32);
1750
1751         char* val = searchValueTransform(node);
1752         
1753         buffer_fadd(
1754                 sql_buf,
1755                 "\"%s\".%s %s %s",
1756                 class,
1757                 osrfHashGet(field, "name"),
1758                 node_key,
1759                 val
1760         );
1761
1762         free(val);
1763
1764         return buffer_release(sql_buf);
1765 }
1766
1767 // class is a class name
1768 // field is a field definition as stored in the IDL
1769 // node comes from the method parameter, and represents an entry in the SELECT list
1770 static char* searchFieldTransform (const char* class, osrfHash* field, const jsonObject* node) {
1771         growing_buffer* sql_buf = buffer_init(32);
1772         
1773         char* field_transform = jsonObjectToSimpleString( jsonObjectGetKeyConst( node, "transform" ) );
1774         char* transform_subcolumn = jsonObjectToSimpleString( jsonObjectGetKeyConst( node, "result_field" ) );
1775
1776         if(transform_subcolumn)
1777                 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' );    // enclose transform in parentheses
1778
1779         if (field_transform) {
1780                 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class, osrfHashGet(field, "name"));
1781             const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
1782
1783         if (array) {
1784                 int func_item_index = 0;
1785                 jsonObject* func_item;
1786                 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1787
1788                         char* val = jsonObjectToSimpleString(func_item);
1789
1790                     if ( !val ) {
1791                             buffer_add( sql_buf, ",NULL" );
1792                     } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
1793                                         OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
1794                                         OSRF_BUFFER_ADD( sql_buf, val );
1795                         } else {
1796                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1797                                         free(transform_subcolumn);
1798                                         free(field_transform);
1799                                         free(val);
1800                                 buffer_free(sql_buf);
1801                                 return NULL;
1802                 }
1803                                 free(val);
1804                         }
1805         }
1806
1807                 buffer_add( sql_buf, " )" );
1808
1809         } else {
1810                 buffer_fadd( sql_buf, "\"%s\".%s", class, osrfHashGet(field, "name"));
1811         }
1812
1813     if (transform_subcolumn)
1814         buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
1815  
1816         if (field_transform) free(field_transform);
1817         if (transform_subcolumn) free(transform_subcolumn);
1818
1819         return buffer_release(sql_buf);
1820 }
1821
1822 static char* searchFieldTransformPredicate (const char* class, osrfHash* field, jsonObject* node, const char* node_key) {
1823         char* field_transform = searchFieldTransform( class, field, node );
1824         char* value = NULL;
1825
1826         if (!jsonObjectGetKeyConst( node, "value" )) {
1827                 value = searchWHERE( node, osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1828         } else if (jsonObjectGetKeyConst( node, "value" )->type == JSON_ARRAY) {
1829                 value = searchValueTransform(jsonObjectGetKeyConst( node, "value" ));
1830         } else if (jsonObjectGetKeyConst( node, "value" )->type == JSON_HASH) {
1831                 value = searchWHERE( jsonObjectGetKeyConst( node, "value" ), osrfHashGet( oilsIDL(), class ), AND_OP_JOIN, NULL );
1832         } else if (jsonObjectGetKeyConst( node, "value" )->type != JSON_NULL) {
1833                 if ( !strcmp(osrfHashGet(field, "primitive"), "number") ) {
1834                         value = jsonNumberToDBString( field, jsonObjectGetKeyConst( node, "value" ) );
1835                 } else {
1836                         value = jsonObjectToSimpleString(jsonObjectGetKeyConst( node, "value" ));
1837                         if ( !dbi_conn_quote_string(dbhandle, &value) ) {
1838                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
1839                                 free(value);
1840                                 free(field_transform);
1841                                 return NULL;
1842                         }
1843                 }
1844         }
1845
1846         growing_buffer* sql_buf = buffer_init(32);
1847         
1848         buffer_fadd(
1849                 sql_buf,
1850                 "%s %s %s",
1851                 field_transform,
1852                 node_key,
1853                 value
1854         );
1855
1856         free(value);
1857         free(field_transform);
1858
1859         return buffer_release(sql_buf);
1860 }
1861
1862 static char* searchSimplePredicate (const char* orig_op, const char* class,
1863                 osrfHash* field, const jsonObject* node) {
1864
1865         char* val = NULL;
1866
1867         if (node->type != JSON_NULL) {
1868                 if ( !strcmp(osrfHashGet(field, "primitive"), "number") ) {
1869                         val = jsonNumberToDBString( field, node );
1870                 } else {
1871                         val = jsonObjectToSimpleString(node);
1872                 }
1873         }
1874
1875         char* pred = searchWriteSimplePredicate( class, field, osrfHashGet(field, "name"), orig_op, val );
1876
1877         if (val) free(val);
1878
1879         return pred;
1880 }
1881
1882 static char* searchWriteSimplePredicate ( const char* class, osrfHash* field,
1883         const char* left, const char* orig_op, const char* right ) {
1884
1885         char* val = NULL;
1886         char* op = NULL;
1887         if (right == NULL) {
1888                 val = strdup("NULL");
1889
1890                 if (strcmp( orig_op, "=" ))
1891                         op = strdup("IS NOT");
1892                 else
1893                         op = strdup("IS");
1894
1895         } else if ( !strcmp(osrfHashGet(field, "primitive"), "number") ) {
1896                 val = strdup(right);
1897                 op = strdup(orig_op);
1898
1899         } else {
1900                 val = strdup(right);
1901                 if ( !dbi_conn_quote_string(dbhandle, &val) ) {
1902                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1903                         free(val);
1904                         return NULL;
1905                 }
1906                 op = strdup(orig_op);
1907         }
1908
1909         growing_buffer* sql_buf = buffer_init(16);
1910         buffer_fadd( sql_buf, "\"%s\".%s %s %s", class, left, op, val );
1911         free(val);
1912         free(op);
1913
1914         return buffer_release(sql_buf);
1915 }
1916
1917 static char* searchBETWEENPredicate (const char* class, osrfHash* field, jsonObject* node) {
1918
1919         char* x_string;
1920         char* y_string;
1921
1922         if ( !strcmp(osrfHashGet(field, "primitive"), "number") ) {
1923                 x_string = jsonNumberToDBString(field, jsonObjectGetIndex(node,0));
1924                 y_string = jsonNumberToDBString(field, jsonObjectGetIndex(node,1));
1925
1926         } else {
1927                 x_string = jsonObjectToSimpleString(jsonObjectGetIndex(node,0));
1928                 y_string = jsonObjectToSimpleString(jsonObjectGetIndex(node,1));
1929                 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
1930                         osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]", MODULENAME, x_string, y_string);
1931                         free(x_string);
1932                         free(y_string);
1933                         return NULL;
1934                 }
1935         }
1936
1937         growing_buffer* sql_buf = buffer_init(32);
1938         buffer_fadd( sql_buf, "%s BETWEEN %s AND %s", osrfHashGet(field, "name"), x_string, y_string );
1939         free(x_string);
1940         free(y_string);
1941
1942         return buffer_release(sql_buf);
1943 }
1944
1945 static char* searchPredicate ( const char* class, osrfHash* field, 
1946                                                            jsonObject* node, osrfMethodContext* ctx ) {
1947
1948         char* pred = NULL;
1949         if (node->type == JSON_ARRAY) { // equality IN search
1950                 pred = searchINPredicate( class, field, node, NULL, ctx );
1951         } else if (node->type == JSON_HASH) { // non-equality search
1952                 jsonObject* pred_node;
1953                 jsonIterator* pred_itr = jsonNewIterator( node );
1954                 while ( (pred_node = jsonIteratorNext( pred_itr )) ) {
1955                         if ( !(strcasecmp( pred_itr->key,"between" )) )
1956                                 pred = searchBETWEENPredicate( class, field, pred_node );
1957                         else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
1958                                 pred = searchINPredicate( class, field, pred_node, pred_itr->key, ctx );
1959                         else if ( pred_node->type == JSON_ARRAY )
1960                                 pred = searchFunctionPredicate( class, field, pred_node, pred_itr->key );
1961                         else if ( pred_node->type == JSON_HASH )
1962                                 pred = searchFieldTransformPredicate( class, field, pred_node, pred_itr->key );
1963                         else 
1964                                 pred = searchSimplePredicate( pred_itr->key, class, field, pred_node );
1965
1966                         break;
1967                 }
1968         jsonIteratorFree(pred_itr);
1969         } else if (node->type == JSON_NULL) { // IS NULL search
1970                 growing_buffer* _p = buffer_init(64);
1971                 buffer_fadd(
1972                         _p,
1973                         "\"%s\".%s IS NULL",
1974                         class,
1975                         osrfHashGet(field, "name")
1976                 );
1977                 pred = buffer_release(_p);
1978         } else { // equality search
1979                 pred = searchSimplePredicate( "=", class, field, node );
1980         }
1981
1982         return pred;
1983
1984 }
1985
1986
1987 /*
1988
1989 join : {
1990         acn : {
1991                 field : record,
1992                 fkey : id
1993                 type : left
1994                 filter_op : or
1995                 filter : { ... },
1996                 join : {
1997                         acp : {
1998                                 field : call_number,
1999                                 fkey : id,
2000                                 filter : { ... },
2001                         },
2002                 },
2003         },
2004         mrd : {
2005                 field : record,
2006                 type : inner
2007                 fkey : id,
2008                 filter : { ... },
2009         }
2010 }
2011
2012 */
2013
2014 static char* searchJOIN ( const jsonObject* join_hash, osrfHash* leftmeta ) {
2015
2016         const jsonObject* working_hash;
2017         jsonObject* freeable_hash = NULL;
2018
2019         if (join_hash->type == JSON_STRING) {
2020                 // create a wrapper around a copy of the original
2021                 char* _tmp = jsonObjectToSimpleString( join_hash );
2022                 freeable_hash = jsonNewObjectType(JSON_HASH);
2023                 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2024                 free(_tmp);
2025                 working_hash = freeable_hash;
2026         }
2027         else {
2028                 if( join_hash->type != JSON_HASH ) {
2029                         osrfLogError(
2030                                 OSRF_LOG_MARK,
2031                                 "%s: JOIN failed; expected JSON object type not found",
2032                                 MODULENAME
2033                         );
2034                         return NULL;
2035                 }
2036                 working_hash = join_hash;
2037         }
2038
2039         growing_buffer* join_buf = buffer_init(128);
2040         const char* leftclass = osrfHashGet(leftmeta, "classname");
2041
2042         jsonObject* snode = NULL;
2043         jsonIterator* search_itr = jsonNewIterator( working_hash );
2044
2045         while ( (snode = jsonIteratorNext( search_itr )) ) {
2046                 const char* class = search_itr->key;
2047                 osrfHash* idlClass = osrfHashGet( oilsIDL(), class );
2048                 if( !idlClass ) {
2049                         osrfLogError(
2050                                 OSRF_LOG_MARK,
2051                                 "%s: JOIN failed.  No class \"%s\" defined in IDL",
2052                                 MODULENAME,
2053                                 search_itr->key
2054                         );
2055                         jsonIteratorFree( search_itr );
2056                         buffer_free( join_buf );
2057                         if( freeable_hash )
2058                                 jsonObjectFree( freeable_hash );
2059                         return NULL;
2060                 }
2061
2062                 char* fkey = jsonObjectToSimpleString( jsonObjectGetKeyConst( snode, "fkey" ) );
2063                 char* field = jsonObjectToSimpleString( jsonObjectGetKeyConst( snode, "field" ) );
2064
2065                 if (field && !fkey) {
2066                         fkey = (char*)oilsIDLFindPath("/%s/links/%s/key", class, field);
2067                         if (!fkey) {
2068                                 osrfLogError(
2069                                         OSRF_LOG_MARK,
2070                                         "%s: JOIN failed.  No link defined from %s.%s to %s",
2071                                         MODULENAME,
2072                                         class,
2073                                         field,
2074                                         leftclass
2075                                 );
2076                                 buffer_free(join_buf);
2077                                 if(freeable_hash)
2078                                         jsonObjectFree(freeable_hash);
2079                                 free(field);
2080                                 jsonIteratorFree(search_itr);
2081                                 return NULL;
2082                         }
2083                         fkey = strdup( fkey );
2084
2085                 } else if (!field && fkey) {
2086                         field = (char*)oilsIDLFindPath("/%s/links/%s/key", leftclass, fkey );
2087                         if (!field) {
2088                                 osrfLogError(
2089                                         OSRF_LOG_MARK,
2090                                         "%s: JOIN failed.  No link defined from %s.%s to %s",
2091                                         MODULENAME,
2092                                         leftclass,
2093                                         fkey,
2094                                         class
2095                                 );
2096                                 buffer_free(join_buf);
2097                                 if(freeable_hash)
2098                                         jsonObjectFree(freeable_hash);
2099                                 free(fkey);
2100                                 jsonIteratorFree(search_itr);
2101                                 return NULL;
2102                         }
2103                         field = strdup( field );
2104
2105                 } else if (!field && !fkey) {
2106                         osrfHash* _links = oilsIDL_links( leftclass );
2107
2108                         // For each link defined for the left class:
2109                         // see if the link references the joined class
2110                         osrfHashIterator* itr = osrfNewHashIterator( _links );
2111                         osrfHash* curr_link = NULL;
2112                         while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2113                                 const char* other_class = osrfHashGet( curr_link, "class" );
2114                                 if( other_class && !strcmp( other_class, class ) ) {
2115
2116                                         // Found a link between the classes
2117                                         fkey = strdup( osrfHashIteratorKey( itr ) );
2118                                         const char* other_key = osrfHashGet( curr_link, "key" );
2119                                         field = other_key ? strdup( other_key ) : NULL;
2120                                         break;
2121                                 }
2122                         }
2123                         osrfHashIteratorFree( itr );
2124
2125                         if (!field || !fkey) {
2126                                 // Do another such search, with the classes reversed
2127                                 _links = oilsIDL_links( class );
2128
2129                                 // For each link defined for the joined class:
2130                                 // see if the link references the left class
2131                                 osrfHashIterator* itr = osrfNewHashIterator( _links );
2132                                 osrfHash* curr_link = NULL;
2133                                 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2134                                         const char* other_class = osrfHashGet( curr_link, "class" );
2135                                         if( other_class && !strcmp( other_class, leftclass ) ) {
2136
2137                                                 // Found a link between the classes
2138                                                 fkey = strdup( osrfHashIteratorKey( itr ) );
2139                                                 const char* other_key = osrfHashGet( curr_link, "key" );
2140                                                 field = other_key ? strdup( other_key ) : NULL;
2141                                                 break;
2142                                         }
2143                                 }
2144                                 osrfHashIteratorFree( itr );
2145                         }
2146
2147                         if (!field || !fkey) {
2148                                 osrfLogError(
2149                                         OSRF_LOG_MARK,
2150                                         "%s: JOIN failed.  No link defined between %s and %s",
2151                                         MODULENAME,
2152                                         leftclass,
2153                                         class
2154                                 );
2155                                 free( fkey );
2156                                 free( field );
2157                                 buffer_free(join_buf);
2158                                 if(freeable_hash)
2159                                         jsonObjectFree(freeable_hash);
2160                                 jsonIteratorFree(search_itr);
2161                                 return NULL;
2162                         }
2163
2164                 }
2165
2166                 char* type = jsonObjectToSimpleString( jsonObjectGetKeyConst( snode, "type" ) );
2167                 if (type) {
2168                         if ( !strcasecmp(type,"left") ) {
2169                                 buffer_add(join_buf, " LEFT JOIN");
2170                         } else if ( !strcasecmp(type,"right") ) {
2171                                 buffer_add(join_buf, " RIGHT JOIN");
2172                         } else if ( !strcasecmp(type,"full") ) {
2173                                 buffer_add(join_buf, " FULL JOIN");
2174                         } else {
2175                                 buffer_add(join_buf, " INNER JOIN");
2176                         }
2177                 } else {
2178                         buffer_add(join_buf, " INNER JOIN");
2179                 }
2180                 free(type);
2181
2182                 char* table = getSourceDefinition(idlClass);
2183                 if( !table ) {
2184                         free( field );
2185                         free( fkey );
2186                         jsonIteratorFree( search_itr );
2187                         buffer_free( join_buf );
2188                         if( freeable_hash )
2189                                 jsonObjectFree( freeable_hash );
2190                         return NULL;
2191                 }
2192
2193                 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2194                                         table, class, class, field, leftclass, fkey);
2195                 free(table);
2196
2197                 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2198                 if (filter) {
2199                         char* filter_op = jsonObjectToSimpleString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2200                         if (filter_op) {
2201                                 if (!strcasecmp("or",filter_op)) {
2202                                         buffer_add( join_buf, " OR " );
2203                                 } else {
2204                                         buffer_add( join_buf, " AND " );
2205                                 }
2206                         } else {
2207                                 buffer_add( join_buf, " AND " );
2208                         }
2209
2210                         char* jpred = searchWHERE( filter, idlClass, AND_OP_JOIN, NULL );
2211                         OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2212                         OSRF_BUFFER_ADD( join_buf, jpred );
2213                         free(jpred);
2214                         free(filter_op);
2215                 }
2216
2217                 buffer_add(join_buf, " ) ");
2218                 
2219                 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2220                 if (join_filter) {
2221                         char* jpred = searchJOIN( join_filter, idlClass );
2222                         OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2223                         OSRF_BUFFER_ADD( join_buf, jpred );
2224                         free(jpred);
2225                 }
2226
2227                 free(fkey);
2228                 free(field);
2229         }
2230
2231         if(freeable_hash)
2232                 jsonObjectFree(freeable_hash);
2233         jsonIteratorFree(search_itr);
2234
2235         return buffer_release(join_buf);
2236 }
2237
2238 /*
2239
2240 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2241 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2242 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2243
2244 */
2245
2246 static char* searchWHERE ( const jsonObject* search_hash, osrfHash* meta, int opjoin_type, osrfMethodContext* ctx ) {
2247
2248         osrfLogDebug(
2249         OSRF_LOG_MARK,
2250         "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2251         MODULENAME,
2252         search_hash,
2253         meta,
2254         opjoin_type,
2255         ctx
2256     );
2257
2258         growing_buffer* sql_buf = buffer_init(128);
2259
2260         jsonObject* node = NULL;
2261
2262     int first = 1;
2263     if ( search_hash->type == JSON_ARRAY ) {
2264             osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2265         jsonIterator* search_itr = jsonNewIterator( search_hash );
2266         while ( (node = jsonIteratorNext( search_itr )) ) {
2267             if (first) {
2268                 first = 0;
2269             } else {
2270                 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2271                 else buffer_add(sql_buf, " AND ");
2272             }
2273
2274             char* subpred = searchWHERE( node, meta, opjoin_type, ctx );
2275             buffer_fadd(sql_buf, "( %s )", subpred);
2276             free(subpred);
2277         }
2278         jsonIteratorFree(search_itr);
2279
2280     } else if ( search_hash->type == JSON_HASH ) {
2281             osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2282         jsonIterator* search_itr = jsonNewIterator( search_hash );
2283         while ( (node = jsonIteratorNext( search_itr )) ) {
2284
2285             if (first) {
2286                 first = 0;
2287             } else {
2288                 if (opjoin_type == OR_OP_JOIN) buffer_add(sql_buf, " OR ");
2289                 else buffer_add(sql_buf, " AND ");
2290             }
2291
2292             if ( !strncmp("+",search_itr->key,1) ) {
2293                 if ( node->type == JSON_STRING ) {
2294                     char* subpred = jsonObjectToSimpleString( node );
2295                     buffer_fadd(sql_buf, " \"%s\".%s ", search_itr->key + 1, subpred);
2296                     free(subpred);
2297                 } else {
2298                     char* subpred = searchWHERE( node, osrfHashGet( oilsIDL(), search_itr->key + 1 ), AND_OP_JOIN, ctx );
2299                     buffer_fadd(sql_buf, "( %s )", subpred);
2300                     free(subpred);
2301                 }
2302             } else if ( !strcasecmp("-or",search_itr->key) ) {
2303                 char* subpred = searchWHERE( node, meta, OR_OP_JOIN, ctx );
2304                 buffer_fadd(sql_buf, "( %s )", subpred);
2305                 free(subpred);
2306             } else if ( !strcasecmp("-and",search_itr->key) ) {
2307                 char* subpred = searchWHERE( node, meta, AND_OP_JOIN, ctx );
2308                 buffer_fadd(sql_buf, "( %s )", subpred);
2309                 free(subpred);
2310             } else if ( !strcasecmp("-exists",search_itr->key) ) {
2311                 char* subpred = SELECT(
2312                     ctx,
2313                     jsonObjectGetKey( node, "select" ),
2314                     jsonObjectGetKey( node, "from" ),
2315                     jsonObjectGetKey( node, "where" ),
2316                     jsonObjectGetKey( node, "having" ),
2317                     jsonObjectGetKey( node, "order_by" ),
2318                     jsonObjectGetKey( node, "limit" ),
2319                     jsonObjectGetKey( node, "offset" ),
2320                     SUBSELECT
2321                 );
2322
2323                 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2324                 free(subpred);
2325             } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2326                 char* subpred = SELECT(
2327                     ctx,
2328                     jsonObjectGetKey( node, "select" ),
2329                     jsonObjectGetKey( node, "from" ),
2330                     jsonObjectGetKey( node, "where" ),
2331                     jsonObjectGetKey( node, "having" ),
2332                     jsonObjectGetKey( node, "order_by" ),
2333                     jsonObjectGetKey( node, "limit" ),
2334                     jsonObjectGetKey( node, "offset" ),
2335                     SUBSELECT
2336                 );
2337
2338                 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2339                 free(subpred);
2340             } else {
2341
2342                 char* class = osrfHashGet(meta, "classname");
2343                 osrfHash* fields = osrfHashGet(meta, "fields");
2344                 osrfHash* field = osrfHashGet( fields, search_itr->key );
2345
2346
2347                 if (!field) {
2348                     char* table = getSourceDefinition(meta);
2349                                         if( !table )
2350                                                 table = strdup( "(?)" );
2351                     osrfLogError(
2352                         OSRF_LOG_MARK,
2353                         "%s: Attempt to reference non-existent column %s on %s (%s)",
2354                         MODULENAME,
2355                         search_itr->key,
2356                         table,
2357                         class
2358                     );
2359                     buffer_free(sql_buf);
2360                     free(table);
2361                                         jsonIteratorFree(search_itr);
2362                                         return NULL;
2363                 }
2364
2365                 char* subpred = searchPredicate( class, field, node, ctx );
2366                 buffer_add( sql_buf, subpred );
2367                 free(subpred);
2368             }
2369         }
2370             jsonIteratorFree(search_itr);
2371
2372     } else {
2373         // ERROR ... only hash and array allowed at this level
2374         char* predicate_string = jsonObjectToJSON( search_hash );
2375         osrfLogError(
2376             OSRF_LOG_MARK,
2377             "%s: Invalid predicate structure: %s",
2378             MODULENAME,
2379             predicate_string
2380         );
2381         buffer_free(sql_buf);
2382         free(predicate_string);
2383         return NULL;
2384     }
2385
2386
2387         return buffer_release(sql_buf);
2388 }
2389
2390 char* SELECT (
2391                 /* method context */ osrfMethodContext* ctx,
2392                 
2393                 /* SELECT   */ jsonObject* selhash,
2394                 /* FROM     */ jsonObject* join_hash,
2395                 /* WHERE    */ jsonObject* search_hash,
2396                 /* HAVING   */ jsonObject* having_hash,
2397                 /* ORDER BY */ jsonObject* order_hash,
2398                 /* LIMIT    */ jsonObject* limit,
2399                 /* OFFSET   */ jsonObject* offset,
2400                 /* flags    */ int flags
2401 ) {
2402         const char* locale = osrf_message_get_last_locale();
2403
2404         // in case we don't get a select list
2405         jsonObject* defaultselhash = NULL;
2406
2407         // general tmp objects
2408         const jsonObject* tmp_const;
2409         jsonObject* selclass = NULL;
2410         jsonObject* selfield = NULL;
2411         jsonObject* snode = NULL;
2412         jsonObject* onode = NULL;
2413
2414         char* string = NULL;
2415         int from_function = 0;
2416         int first = 1;
2417         int gfirst = 1;
2418         //int hfirst = 1;
2419
2420         // the core search class
2421         char* core_class = NULL;
2422
2423         // metadata about the core search class
2424         osrfHash* core_meta = NULL;
2425
2426         // punt if there's no core class
2427         if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
2428                 osrfLogError(
2429                         OSRF_LOG_MARK,
2430                         "%s: FROM clause is missing or empty",
2431                         MODULENAME
2432                 );
2433                 if( ctx )
2434                         osrfAppSessionStatus(
2435                                 ctx->session,
2436                                 OSRF_STATUS_INTERNALSERVERERROR,
2437                                 "osrfMethodException",
2438                                 ctx->request,
2439                                 "FROM clause is missing or empty in JSON query"
2440                         );
2441                 return NULL;
2442         }
2443
2444         // get the core class -- the only key of the top level FROM clause, or a string
2445         if (join_hash->type == JSON_HASH) {
2446                 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
2447                 snode = jsonIteratorNext( tmp_itr );
2448                 
2449                 core_class = strdup( tmp_itr->key );
2450                 join_hash = snode;
2451                 
2452                 jsonObject* extra = jsonIteratorNext( tmp_itr );
2453
2454                 jsonIteratorFree( tmp_itr );
2455                 snode = NULL;
2456
2457                 // There shouldn't be more than one entry in join_hash
2458                 if( extra ) {
2459                         osrfLogError(
2460                                 OSRF_LOG_MARK,
2461                                 "%s: Malformed FROM clause: extra entry in JSON_HASH",
2462                                 MODULENAME
2463                         );
2464                         if( ctx )
2465                                 osrfAppSessionStatus(
2466                                         ctx->session,
2467                                         OSRF_STATUS_INTERNALSERVERERROR,
2468                                         "osrfMethodException",
2469                                         ctx->request,
2470                                         "Malformed FROM clause in JSON query"
2471                                 );
2472                         free( core_class );
2473                         return NULL;    // Malformed join_hash; extra entry
2474                 }
2475         } else if (join_hash->type == JSON_ARRAY) {
2476                 from_function = 1;
2477                 core_class = jsonObjectToSimpleString( jsonObjectGetIndex(join_hash, 0) );
2478                 selhash = NULL;
2479
2480         } else if (join_hash->type == JSON_STRING) {
2481                 core_class = jsonObjectToSimpleString( join_hash );
2482                 join_hash = NULL;
2483         }
2484         else {
2485                 osrfLogError(
2486                         OSRF_LOG_MARK,
2487                         "%s: FROM clause is unexpected JSON type: %s",
2488                         MODULENAME,
2489                         json_type( join_hash->type )
2490                 );
2491                 if( ctx )
2492                         osrfAppSessionStatus(
2493                                 ctx->session,
2494                                 OSRF_STATUS_INTERNALSERVERERROR,
2495                                 "osrfMethodException",
2496                                 ctx->request,
2497                                 "Ill-formed FROM clause in JSON query"
2498                         );
2499                 free( core_class );
2500                 return NULL;
2501         }
2502
2503         if (!from_function) {
2504                 // Get the IDL class definition for the core class
2505                 core_meta = osrfHashGet( oilsIDL(), core_class );
2506                 if( !core_meta ) {    // Didn't find it?
2507                         osrfLogError(
2508                                 OSRF_LOG_MARK,
2509                                 "%s: SELECT clause references undefined class: \"%s\"",
2510                                 MODULENAME,
2511                                 core_class
2512                         );
2513                         if( ctx )
2514                                 osrfAppSessionStatus(
2515                                         ctx->session,
2516                                         OSRF_STATUS_INTERNALSERVERERROR,
2517                                         "osrfMethodException",
2518                                         ctx->request,
2519                                         "SELECT clause references undefined class in JSON query"
2520                                 );
2521                         free( core_class );
2522                         return NULL;
2523                 }
2524
2525                 // Make sure the class isn't virtual
2526                 if( str_is_true( osrfHashGet( core_meta, "virtual" ) ) ) {
2527                         osrfLogError(
2528                                 OSRF_LOG_MARK,
2529                                 "%s: Core class is virtual: \"%s\"",
2530                                 MODULENAME,
2531                                 core_class
2532                         );
2533                         if( ctx )
2534                                 osrfAppSessionStatus(
2535                                         ctx->session,
2536                                         OSRF_STATUS_INTERNALSERVERERROR,
2537                                         "osrfMethodException",
2538                                         ctx->request,
2539                                         "FROM clause references virtual class in JSON query"
2540                                 );
2541                         free( core_class );
2542                         return NULL;
2543                 }
2544         }
2545
2546         // if the select list is empty, or the core class field list is '*',
2547         // build the default select list ...
2548         if (!selhash) {
2549                 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
2550                 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2551         } else if( selhash->type != JSON_HASH ) {
2552                 osrfLogError(
2553                         OSRF_LOG_MARK,
2554                         "%s: Expected JSON_HASH for SELECT clause; found %s",
2555                         MODULENAME,
2556                         json_type( selhash->type )
2557                 );
2558
2559                 if (ctx)
2560                         osrfAppSessionStatus(
2561                                 ctx->session,
2562                                 OSRF_STATUS_INTERNALSERVERERROR,
2563                                 "osrfMethodException",
2564                                 ctx->request,
2565                                 "Malformed SELECT clause in JSON query"
2566                         );
2567                 free( core_class );
2568                 return NULL;
2569         } else if ( (tmp_const = jsonObjectGetKeyConst( selhash, core_class )) && tmp_const->type == JSON_STRING ) {
2570                 char* _x = jsonObjectToSimpleString( tmp_const );
2571                 if (!strncmp( "*", _x, 1 )) {
2572                         jsonObjectRemoveKey( selhash, core_class );
2573                         jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
2574                 }
2575                 free(_x);
2576         }
2577
2578         // the query buffer
2579         growing_buffer* sql_buf = buffer_init(128);
2580
2581         // temp buffer for the SELECT list
2582         growing_buffer* select_buf = buffer_init(128);
2583         growing_buffer* order_buf = buffer_init(128);
2584         growing_buffer* group_buf = buffer_init(128);
2585         growing_buffer* having_buf = buffer_init(128);
2586
2587         // Build a select list
2588         if(from_function)   // From a function we select everything
2589                 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
2590         else {
2591
2592                 // If we need to build a default list, prepare to do so
2593                 jsonObject* _tmp = jsonObjectGetKey( selhash, core_class );
2594                 if ( _tmp && !_tmp->size ) {
2595
2596                         osrfHash* core_fields = osrfHashGet( core_meta, "fields" );
2597
2598                         osrfHashIterator* field_itr = osrfNewHashIterator( core_fields );
2599                         osrfHash* field_def;
2600                         while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2601                                 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2602                                         // This field is not virtual, so add it to the list
2603                                         jsonObjectPush( _tmp, jsonNewObject( osrfHashIteratorKey( field_itr ) ) );
2604                                 }
2605                         }
2606                         osrfHashIteratorFree( field_itr );
2607                 }
2608
2609                 // Now build the actual select list
2610             int sel_pos = 1;
2611             jsonObject* is_agg = jsonObjectFindPath(selhash, "//aggregate");
2612             first = 1;
2613             gfirst = 1;
2614             jsonIterator* selclass_itr = jsonNewIterator( selhash );
2615             while ( (selclass = jsonIteratorNext( selclass_itr )) ) {    // For each class
2616
2617                     // Make sure the class is defined in the IDL
2618                         const char* cname = selclass_itr->key;
2619                         osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
2620                     if (!idlClass) {
2621                                 osrfLogError(
2622                                         OSRF_LOG_MARK,
2623                                         "%s: Selected class \"%s\" not defined in IDL",
2624                                         MODULENAME,
2625                                         cname
2626                                 );
2627
2628                                 if (ctx)
2629                                         osrfAppSessionStatus(
2630                                                 ctx->session,
2631                                                 OSRF_STATUS_INTERNALSERVERERROR,
2632                                                 "osrfMethodException",
2633                                                 ctx->request,
2634                                                 "Selected class is not defined"
2635                                         );
2636                                 jsonIteratorFree( selclass_itr );
2637                                 jsonObjectFree( is_agg );
2638                                 buffer_free( sql_buf );
2639                                 buffer_free( select_buf );
2640                                 buffer_free( order_buf );
2641                                 buffer_free( group_buf );
2642                                 buffer_free( having_buf );
2643                                 free( core_class );
2644                                 return NULL;
2645                         }
2646
2647                     // Make sure the target relation is in the join tree.
2648                         
2649                         // At this point join_hash is a step down from the join_hash we
2650                         // received as a parameter.  If the original was a JSON_STRING,
2651                         // then json_hash is now NULL.  If the original was a JSON_HASH,
2652                         // then json_hash is now the first (and only) entry in it,
2653                         // denoting the core class.  We've already excluded the
2654                         // possibility that the original was a JSON_ARRAY, because in
2655                         // that case from_function would be non-NULL, and we wouldn't
2656                         // be here.
2657
2658                         int class_in_from_clause;    // boolean
2659                         
2660                     if ( ! strcmp( core_class, cname ))
2661                                 // This is the core class -- no problem
2662                                 class_in_from_clause = 1;
2663                         else {
2664                                 if (!join_hash) 
2665                                         // There's only one class in the FROM clause, and this isn't it
2666                                         class_in_from_clause = 0;
2667                                 else if (join_hash->type == JSON_STRING) {
2668                                         // There's only one class in the FROM clause
2669                                         string = jsonObjectToSimpleString(join_hash);
2670                                         if ( strcmp( string, cname ) )
2671                                                 class_in_from_clause = 0;    // This isn't it
2672                                         else 
2673                                                 class_in_from_clause = 1;    // This is it
2674                                         free( string );
2675                                 } else {
2676                                         jsonObject* found = jsonObjectFindPath(join_hash, "//%s", cname);
2677                                         if ( 0 == found->size )
2678                                                 class_in_from_clause = 0;   // Nowhere in the join tree
2679                                         else
2680                                                 class_in_from_clause = 1;   // Found it
2681                                         jsonObjectFree( found );
2682                                 }
2683                         }
2684
2685                         // If the class isn't in the FROM clause, bail out
2686                         if( ! class_in_from_clause ) {
2687                                 osrfLogError(
2688                                         OSRF_LOG_MARK,
2689                                         "%s: SELECT clause references class not in FROM clause: \"%s\"",
2690                                         MODULENAME,
2691                                         cname
2692                                 );
2693                                 if( ctx )
2694                                         osrfAppSessionStatus(
2695                                                 ctx->session,
2696                                                 OSRF_STATUS_INTERNALSERVERERROR,
2697                                                 "osrfMethodException",
2698                                                 ctx->request,
2699                                                 "Selected class not in FROM clause in JSON query"
2700                                         );
2701                                 jsonIteratorFree( selclass_itr );
2702                                 jsonObjectFree( is_agg );
2703                                 buffer_free( sql_buf );
2704                                 buffer_free( select_buf );
2705                                 buffer_free( order_buf );
2706                                 buffer_free( group_buf );
2707                                 buffer_free( having_buf );
2708                                 free( core_class );
2709                                 return NULL;
2710                         }
2711
2712                         // Look up some attributes of the current class, so that we 
2713                         // don't have to look them up again for each field
2714                         osrfHash* class_field_set = osrfHashGet( idlClass, "fields" );
2715                         const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
2716                         const char* class_tname = osrfHashGet( idlClass, "tablename" );
2717                         
2718                     // stitch together the column list ...
2719                     jsonIterator* select_itr = jsonNewIterator( selclass );
2720                     while ( (selfield = jsonIteratorNext( select_itr )) ) {   // for each SELECT column
2721
2722                                 // If we need a separator comma, add one
2723                                 if (first) {
2724                                         first = 0;
2725                                 } else {
2726                                         OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
2727                                 }
2728
2729                                 // ... if it's a string, just toss it on the pile
2730                                 if (selfield->type == JSON_STRING) {
2731
2732                                         // Look up the field in the IDL
2733                                         const char* col_name = selfield->value.s;
2734                                         osrfHash* field_def = osrfHashGet( class_field_set, col_name );
2735                                         if ( !field_def ) {
2736                                                 // No such field in current class
2737                                                 osrfLogError(
2738                                                         OSRF_LOG_MARK,
2739                                                         "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
2740                                                         MODULENAME,
2741                                                         col_name,
2742                                                         cname
2743                                                 );
2744                                                 if( ctx )
2745                                                         osrfAppSessionStatus(
2746                                                                 ctx->session,
2747                                                                 OSRF_STATUS_INTERNALSERVERERROR,
2748                                                                 "osrfMethodException",
2749                                                                 ctx->request,
2750                                                                 "Selected column not defined in JSON query"
2751                                                         );
2752                                                 jsonIteratorFree( select_itr );
2753                                                 jsonIteratorFree( selclass_itr );
2754                                                 jsonObjectFree( is_agg );
2755                                                 buffer_free( sql_buf );
2756                                                 buffer_free( select_buf );
2757                                                 buffer_free( order_buf );
2758                                                 buffer_free( group_buf );
2759                                                 buffer_free( having_buf );
2760                                                 free( core_class );
2761                                                 return NULL;
2762                                         }
2763
2764                                         if (locale) {
2765                                                 const char* i18n;
2766                                                 if (flags & DISABLE_I18N)
2767                                                         i18n = NULL;
2768                                                 else
2769                                                         i18n = osrfHashGet(field_def, "i18n");
2770
2771                                                 if( str_is_true( i18n ) ) {
2772                             buffer_fadd( select_buf,
2773                                                                 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
2774                                                                 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
2775                         } else {
2776                                             buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
2777                         }
2778                     } else {
2779                                         buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
2780                     }
2781                                         
2782                                 // ... but it could be an object, in which case we check for a Field Transform
2783                                 } else if (selfield->type == JSON_HASH) {
2784
2785                                         char* col_name = jsonObjectToSimpleString( jsonObjectGetKeyConst( selfield, "column" ) );
2786
2787                                         // Get the field definition from the IDL
2788                                         osrfHash* field_def = osrfHashGet( class_field_set, col_name );
2789                                         if ( !field_def ) {
2790                                                 // No such field in current class
2791                                                 osrfLogError(
2792                                                         OSRF_LOG_MARK,
2793                                                         "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
2794                                                         MODULENAME,
2795                                                         col_name,
2796                                                         cname
2797                                                 );
2798                                                 if( ctx )
2799                                                         osrfAppSessionStatus(
2800                                                                 ctx->session,
2801                                                                 OSRF_STATUS_INTERNALSERVERERROR,
2802                                                                 "osrfMethodException",
2803                                                                 ctx->request,
2804                                                                 "Selected column is not defined in JSON query"
2805                                                         );
2806                                                 jsonIteratorFree( select_itr );
2807                                                 jsonIteratorFree( selclass_itr );
2808                                                 jsonObjectFree( is_agg );
2809                                                 buffer_free( sql_buf );
2810                                                 buffer_free( select_buf );
2811                                                 buffer_free( order_buf );
2812                                                 buffer_free( group_buf );
2813                                                 buffer_free( having_buf );
2814                                                 free( core_class );
2815                                                 return NULL;
2816                                         }
2817
2818                                         // Decide what to use as a column alias
2819                                         char* _alias;
2820                                         if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
2821                                                 _alias = jsonObjectToSimpleString( tmp_const );
2822                                         } else {         // Use field name as the alias
2823                                                 _alias = col_name;
2824                                         }
2825
2826                                         if (jsonObjectGetKeyConst( selfield, "transform" )) {
2827                                                 char* transform_str = searchFieldTransform(cname, field_def, selfield);
2828                                                 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
2829                                                 free(transform_str);
2830                                         } else {
2831
2832                         if (locale) {
2833                                     const char* i18n;
2834                                         if (flags & DISABLE_I18N)
2835                                 i18n = NULL;
2836                                                         else
2837                                                                 i18n = osrfHashGet(field_def, "i18n");
2838
2839                                                         if( str_is_true( i18n ) ) {
2840                                 buffer_fadd( select_buf,
2841                                                                         " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
2842                                                                         class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
2843                             } else {
2844                                                     buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
2845                             }
2846                         } else {
2847                                                 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
2848                         }
2849                                     }
2850
2851                                         if( _alias != col_name )
2852                                             free(_alias);
2853                                         free( col_name );
2854                             }
2855                                 else {
2856                                         osrfLogError(
2857                                                 OSRF_LOG_MARK,
2858                                                 "%s: Selected item is unexpected JSON type: %s",
2859                                                 MODULENAME,
2860                                                 json_type( selfield->type )
2861                                         );
2862                                         if( ctx )
2863                                                 osrfAppSessionStatus(
2864                                                         ctx->session,
2865                                                         OSRF_STATUS_INTERNALSERVERERROR,
2866                                                         "osrfMethodException",
2867                                                         ctx->request,
2868                                                         "Ill-formed SELECT item in JSON query"
2869                                                 );
2870                                         jsonIteratorFree( select_itr );
2871                                         jsonIteratorFree( selclass_itr );
2872                                         jsonObjectFree( is_agg );
2873                                         buffer_free( sql_buf );
2874                                         buffer_free( select_buf );
2875                                         buffer_free( order_buf );
2876                                         buffer_free( group_buf );
2877                                         buffer_free( having_buf );
2878                                         free( core_class );
2879                                         return NULL;
2880                                 }
2881
2882                             if (is_agg->size || (flags & SELECT_DISTINCT)) {
2883
2884                                         const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
2885                                     if ( ! obj_is_true( aggregate_obj ) ) {
2886                                             if (gfirst) {
2887                                                     gfirst = 0;
2888                                             } else {
2889                                                         OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
2890                                             }
2891
2892                                             buffer_fadd(group_buf, " %d", sel_pos);
2893                                         /*
2894                                     } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
2895                                             if (gfirst) {
2896                                                     gfirst = 0;
2897                                             } else {
2898                                                         OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
2899                                             }
2900
2901                                             _column = searchFieldTransform(cname, field, selfield);
2902                                                 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
2903                                                 OSRF_BUFFER_ADD(group_buf, _column);
2904                                             _column = searchFieldTransform(cname, field, selfield);
2905                                         */
2906                                     }
2907                             }
2908
2909                             sel_pos++;
2910                     } // end while -- iterating across SELECT columns
2911
2912             jsonIteratorFree(select_itr);
2913             } // end while -- iterating across classes
2914
2915         jsonIteratorFree(selclass_itr);
2916
2917             if (is_agg) jsonObjectFree(is_agg);
2918     }
2919
2920
2921         char* col_list = buffer_release(select_buf);
2922         char* table = NULL;
2923         if (from_function) table = searchValueTransform(join_hash);
2924         else table = getSourceDefinition(core_meta);
2925         
2926         if( !table ) {
2927                 if (ctx)
2928                         osrfAppSessionStatus(
2929                                 ctx->session,
2930                                 OSRF_STATUS_INTERNALSERVERERROR,
2931                                 "osrfMethodException",
2932                                 ctx->request,
2933                                 "Unable to identify table for core class"
2934                         );
2935                 free( col_list );
2936                 buffer_free( sql_buf );
2937                 buffer_free( order_buf );
2938                 buffer_free( group_buf );
2939                 buffer_free( having_buf );
2940                 if( defaultselhash ) jsonObjectFree( defaultselhash );
2941                 free( core_class );
2942                 return NULL;    
2943         }
2944         
2945         // Put it all together
2946         buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
2947         free(col_list);
2948         free(table);
2949
2950     if (!from_function) {
2951             // Now, walk the join tree and add that clause
2952             if ( join_hash ) {
2953                     char* join_clause = searchJOIN( join_hash, core_meta );
2954                         if( join_clause ) {
2955                                 buffer_add(sql_buf, join_clause);
2956                         free(join_clause);
2957                         } else {
2958                                 if (ctx)
2959                                         osrfAppSessionStatus(
2960                                                 ctx->session,
2961                                                 OSRF_STATUS_INTERNALSERVERERROR,
2962                                                 "osrfMethodException",
2963                                                 ctx->request,
2964                                                 "Unable to construct JOIN clause(s)"
2965                                         );
2966                                 buffer_free( sql_buf );
2967                                 buffer_free( order_buf );
2968                                 buffer_free( group_buf );
2969                                 buffer_free( having_buf );
2970                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
2971                                 free( core_class );
2972                                 return NULL;
2973                         }
2974             }
2975
2976                 // Build a WHERE clause, if there is one
2977             if ( search_hash ) {
2978                     buffer_add(sql_buf, " WHERE ");
2979
2980                     // and it's on the WHERE clause
2981                     char* pred = searchWHERE( search_hash, core_meta, AND_OP_JOIN, ctx );
2982
2983                     if (pred) {
2984                                 buffer_add(sql_buf, pred);
2985                                 free(pred);
2986                         } else {
2987                                 if (ctx) {
2988                                 osrfAppSessionStatus(
2989                                         ctx->session,
2990                                         OSRF_STATUS_INTERNALSERVERERROR,
2991                                         "osrfMethodException",
2992                                         ctx->request,
2993                                         "Severe query error in WHERE predicate -- see error log for more details"
2994                                 );
2995                             }
2996                             free(core_class);
2997                             buffer_free(having_buf);
2998                             buffer_free(group_buf);
2999                             buffer_free(order_buf);
3000                             buffer_free(sql_buf);
3001                             if (defaultselhash) jsonObjectFree(defaultselhash);
3002                             return NULL;
3003                     }
3004         }
3005
3006                 // Build a HAVING clause, if there is one
3007             if ( having_hash ) {
3008                     buffer_add(sql_buf, " HAVING ");
3009
3010                     // and it's on the the WHERE clause
3011                     char* pred = searchWHERE( having_hash, core_meta, AND_OP_JOIN, ctx );
3012
3013                     if (pred) {
3014                                 buffer_add(sql_buf, pred);
3015                                 free(pred);
3016                         } else {
3017                                 if (ctx) {
3018                                 osrfAppSessionStatus(
3019                                         ctx->session,
3020                                         OSRF_STATUS_INTERNALSERVERERROR,
3021                                         "osrfMethodException",
3022                                         ctx->request,
3023                                         "Severe query error in HAVING predicate -- see error log for more details"
3024                                 );
3025                             }
3026                             free(core_class);
3027                             buffer_free(having_buf);
3028                             buffer_free(group_buf);
3029                             buffer_free(order_buf);
3030                             buffer_free(sql_buf);
3031                             if (defaultselhash) jsonObjectFree(defaultselhash);
3032                             return NULL;
3033                     }
3034             }
3035
3036                 // Build an ORDER BY clause, if there is one
3037             first = 1;
3038             jsonIterator* class_itr = jsonNewIterator( order_hash );
3039             while ( (snode = jsonIteratorNext( class_itr )) ) {
3040
3041                     if (!jsonObjectGetKeyConst(selhash,class_itr->key))
3042                             continue;
3043
3044                     if ( snode->type == JSON_HASH ) {
3045
3046                         jsonIterator* order_itr = jsonNewIterator( snode );
3047                             while ( (onode = jsonIteratorNext( order_itr )) ) {
3048
3049                                     if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ))
3050                                             continue;
3051
3052                                     char* direction = NULL;
3053                                     if ( onode->type == JSON_HASH ) {
3054                                             if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3055                                                     string = searchFieldTransform(
3056                                                             class_itr->key,
3057                                                             oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ),
3058                                                             onode
3059                                                     );
3060                                             } else {
3061                                                     growing_buffer* field_buf = buffer_init(16);
3062                                                     buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3063                                                     string = buffer_release(field_buf);
3064                                             }
3065
3066                                             if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3067                                                     direction = jsonObjectToSimpleString(tmp_const);
3068                                                     if (!strncasecmp(direction, "d", 1)) {
3069                                                             free(direction);
3070                                                             direction = " DESC";
3071                                                     } else {
3072                                                             free(direction);
3073                                                             direction = " ASC";
3074                                                     }
3075                                             }
3076
3077                                     } else {
3078                                             string = strdup(order_itr->key);
3079                                             direction = jsonObjectToSimpleString(onode);
3080                                             if (!strncasecmp(direction, "d", 1)) {
3081                                                     free(direction);
3082                                                     direction = " DESC";
3083                                             } else {
3084                                                     free(direction);
3085                                                     direction = " ASC";
3086                                             }
3087                                     }
3088
3089                                     if (first) {
3090                                             first = 0;
3091                                     } else {
3092                                             buffer_add(order_buf, ", ");
3093                                     }
3094
3095                                     buffer_add(order_buf, string);
3096                                     free(string);
3097
3098                                     if (direction) {
3099                                             buffer_add(order_buf, direction);
3100                                     }
3101
3102                             } // end while
3103                 // jsonIteratorFree(order_itr);
3104
3105                     } else if ( snode->type == JSON_ARRAY ) {
3106
3107                         jsonIterator* order_itr = jsonNewIterator( snode );
3108                             while ( (onode = jsonIteratorNext( order_itr )) ) {
3109
3110                                     char* _f = jsonObjectToSimpleString( onode );
3111
3112                                     if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, _f))
3113                                             continue;
3114
3115                                     if (first) {
3116                                             first = 0;
3117                                     } else {
3118                                             buffer_add(order_buf, ", ");
3119                                     }
3120
3121                                     buffer_add(order_buf, _f);
3122                                     free(_f);
3123
3124                             } // end while
3125                 // jsonIteratorFree(order_itr);
3126
3127
3128                     // IT'S THE OOOOOOOOOOOLD STYLE!
3129                     } else {
3130                             osrfLogError(OSRF_LOG_MARK, "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3131                             if (ctx) {
3132                                 osrfAppSessionStatus(
3133                                         ctx->session,
3134                                         OSRF_STATUS_INTERNALSERVERERROR,
3135                                         "osrfMethodException",
3136                                         ctx->request,
3137                                         "Severe query error -- see error log for more details"
3138                                 );
3139                             }
3140
3141                             free(core_class);
3142                             buffer_free(having_buf);
3143                             buffer_free(group_buf);
3144                             buffer_free(order_buf);
3145                             buffer_free(sql_buf);
3146                             if (defaultselhash) jsonObjectFree(defaultselhash);
3147                             jsonIteratorFree(class_itr);
3148                             return NULL;
3149                     }
3150
3151             } // end while
3152                 // jsonIteratorFree(class_itr);
3153         }
3154
3155
3156         string = buffer_release(group_buf);
3157
3158         if ( *string ) {
3159                 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3160                 OSRF_BUFFER_ADD( sql_buf, string );
3161         }
3162
3163         free(string);
3164
3165         string = buffer_release(having_buf);
3166  
3167         if ( *string ) {
3168                 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3169                 OSRF_BUFFER_ADD( sql_buf, string );
3170         }
3171
3172         free(string);
3173
3174         string = buffer_release(order_buf);
3175
3176         if ( *string ) {
3177                 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3178                 OSRF_BUFFER_ADD( sql_buf, string );
3179         }
3180
3181         free(string);
3182
3183         if ( limit ){
3184                 string = jsonObjectToSimpleString(limit);
3185                 buffer_fadd( sql_buf, " LIMIT %d", atoi(string) );
3186                 free(string);
3187         }
3188
3189         if (offset) {
3190                 string = jsonObjectToSimpleString(offset);
3191                 buffer_fadd( sql_buf, " OFFSET %d", atoi(string) );
3192                 free(string);
3193         }
3194
3195         if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3196
3197         free(core_class);
3198         if (defaultselhash) jsonObjectFree(defaultselhash);
3199
3200         return buffer_release(sql_buf);
3201
3202 }
3203
3204 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
3205
3206         const char* locale = osrf_message_get_last_locale();
3207
3208         osrfHash* fields = osrfHashGet(meta, "fields");
3209         char* core_class = osrfHashGet(meta, "classname");
3210
3211         const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
3212
3213         jsonObject* node = NULL;
3214         jsonObject* snode = NULL;
3215         jsonObject* onode = NULL;
3216         const jsonObject* _tmp = NULL;
3217         jsonObject* selhash = NULL;
3218         jsonObject* defaultselhash = NULL;
3219
3220         growing_buffer* sql_buf = buffer_init(128);
3221         growing_buffer* select_buf = buffer_init(128);
3222
3223         if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
3224                 defaultselhash = jsonNewObjectType(JSON_HASH);
3225                 selhash = defaultselhash;
3226         }
3227         
3228         if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
3229                 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
3230                 jsonObject* flist = jsonObjectGetKey( selhash, core_class );
3231                 
3232                 int i = 0;
3233                 char* field;
3234
3235                 osrfStringArray* keys = osrfHashKeys( fields );
3236                 while ( (field = osrfStringArrayGetString(keys, i++)) ) {
3237                         if( ! str_is_true( osrfHashGet( osrfHashGet( fields, field ), "virtual" ) ) )
3238                                 jsonObjectPush( flist, jsonNewObject( field ) );
3239                 }
3240                 osrfStringArrayFree(keys);
3241         }
3242
3243         int first = 1;
3244         jsonIterator* class_itr = jsonNewIterator( selhash );
3245         while ( (snode = jsonIteratorNext( class_itr )) ) {
3246
3247                 char* cname = class_itr->key;
3248                 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
3249                 if (!idlClass) continue;
3250
3251                 if (strcmp(core_class,class_itr->key)) {
3252                         if (!join_hash) continue;
3253
3254                         jsonObject* found =  jsonObjectFindPath(join_hash, "//%s", class_itr->key);
3255                         if (!found->size) {
3256                                 jsonObjectFree(found);
3257                                 continue;
3258                         }
3259
3260                         jsonObjectFree(found);
3261                 }
3262
3263                 jsonIterator* select_itr = jsonNewIterator( snode );
3264                 while ( (node = jsonIteratorNext( select_itr )) ) {
3265                         char* item_str = jsonObjectToSimpleString(node);
3266                         osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
3267                         free(item_str);
3268                         char* fname = osrfHashGet(field, "name");
3269
3270                         if (!field) continue;
3271
3272                         if (first) {
3273                                 first = 0;
3274                         } else {
3275                                 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
3276                         }
3277
3278             if (locale) {
3279                         const char* i18n;
3280                                 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
3281                                 if ( obj_is_true( no_i18n_obj ) )    // Suppress internationalization?
3282                                         i18n = NULL;
3283                                 else
3284                                         i18n = osrfHashGet(field, "i18n");
3285
3286                                 if( str_is_true( i18n ) ) {
3287                         char* pkey = osrfHashGet(idlClass, "primarykey");
3288                         char* tname = osrfHashGet(idlClass, "tablename");
3289
3290                     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);
3291                 } else {
3292                                 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3293                 }
3294             } else {
3295                             buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3296             }
3297                 }
3298
3299         jsonIteratorFree(select_itr);
3300         }
3301
3302     jsonIteratorFree(class_itr);
3303
3304         char* col_list = buffer_release(select_buf);
3305         char* table = getSourceDefinition(meta);
3306         if( !table )
3307                 table = strdup( "(null)" );
3308
3309         buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
3310         free(col_list);
3311         free(table);
3312
3313         if ( join_hash ) {
3314                 char* join_clause = searchJOIN( join_hash, meta );
3315                 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
3316                 OSRF_BUFFER_ADD(sql_buf, join_clause);
3317                 free(join_clause);
3318         }
3319
3320         osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL =  %s",
3321                                  MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
3322
3323         buffer_add(sql_buf, " WHERE ");
3324
3325         char* pred = searchWHERE( search_hash, meta, AND_OP_JOIN, ctx );
3326         if (!pred) {
3327                 osrfAppSessionStatus(
3328                         ctx->session,
3329                         OSRF_STATUS_INTERNALSERVERERROR,
3330                                 "osrfMethodException",
3331                                 ctx->request,
3332                                 "Severe query error -- see error log for more details"
3333                         );
3334                 buffer_free(sql_buf);
3335                 if(defaultselhash) jsonObjectFree(defaultselhash);
3336                 return NULL;
3337         } else {
3338                 buffer_add(sql_buf, pred);
3339                 free(pred);
3340         }
3341
3342         if (order_hash) {
3343                 char* string = NULL;
3344                 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
3345
3346                         growing_buffer* order_buf = buffer_init(128);
3347
3348                         first = 1;
3349                         jsonIterator* class_itr = jsonNewIterator( _tmp );
3350                         while ( (snode = jsonIteratorNext( class_itr )) ) {
3351
3352                                 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
3353                                         continue;
3354
3355                                 if ( snode->type == JSON_HASH ) {
3356
3357                                         jsonIterator* order_itr = jsonNewIterator( snode );
3358                                         while ( (onode = jsonIteratorNext( order_itr )) ) {
3359
3360                                                 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ))
3361                                                         continue;
3362
3363                                                 char* direction = NULL;
3364                                                 if ( onode->type == JSON_HASH ) {
3365                                                         if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3366                                                                 string = searchFieldTransform(
3367                                                                         class_itr->key,
3368                                                                         oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ),
3369                                                                         onode
3370                                                                 );
3371                                                         } else {
3372                                                                 growing_buffer* field_buf = buffer_init(16);
3373                                                                 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3374                                                                 string = buffer_release(field_buf);
3375                                                         }
3376
3377                                                         if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
3378                                                                 direction = jsonObjectToSimpleString(_tmp);
3379                                                                 if (!strncasecmp(direction, "d", 1)) {
3380                                                                         free(direction);
3381                                                                         direction = " DESC";
3382                                                                 } else {
3383                                                                         free(direction);
3384                                                                         direction = " ASC";
3385                                                                 }
3386                                                         }
3387
3388                                                 } else {
3389                                                         string = strdup(order_itr->key);
3390                                                         direction = jsonObjectToSimpleString(onode);
3391                                                         if (!strncasecmp(direction, "d", 1)) {
3392                                                                 free(direction);
3393                                                                 direction = " DESC";
3394                                                         } else {
3395                                                                 free(direction);
3396                                                                 direction = " ASC";
3397                                                         }
3398                                                 }
3399
3400                                                 if (first) {
3401                                                         first = 0;
3402                                                 } else {
3403                                                         buffer_add(order_buf, ", ");
3404                                                 }
3405
3406                                                 buffer_add(order_buf, string);
3407                                                 free(string);
3408
3409                                                 if (direction) {
3410                                                         buffer_add(order_buf, direction);
3411                                                 }
3412
3413                                         }
3414
3415                     jsonIteratorFree(order_itr);
3416
3417                                 } else {
3418                                         string = jsonObjectToSimpleString(snode);
3419                                         buffer_add(order_buf, string);
3420                                         free(string);
3421                                         break;
3422                                 }
3423
3424                         }
3425
3426             jsonIteratorFree(class_itr);
3427
3428                         string = buffer_release(order_buf);
3429
3430                         if ( *string ) {
3431                                 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3432                                 OSRF_BUFFER_ADD( sql_buf, string );
3433                         }
3434
3435                         free(string);
3436                 }
3437
3438                 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
3439                         string = jsonObjectToSimpleString(_tmp);
3440                         buffer_fadd(
3441                                 sql_buf,
3442                                 " LIMIT %d",
3443                                 atoi(string)
3444                         );
3445                         free(string);
3446                 }
3447
3448                 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
3449                 if (_tmp) {
3450                         string = jsonObjectToSimpleString(_tmp);
3451                         buffer_fadd(
3452                                 sql_buf,
3453                                 " OFFSET %d",
3454                                 atoi(string)
3455                         );
3456                         free(string);
3457                 }
3458         }
3459
3460         if (defaultselhash) jsonObjectFree(defaultselhash);
3461
3462         OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3463         return buffer_release(sql_buf);
3464 }
3465
3466 int doJSONSearch ( osrfMethodContext* ctx ) {
3467         if(osrfMethodVerifyContext( ctx )) {
3468                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
3469                 return -1;
3470         }
3471
3472         osrfLogDebug(OSRF_LOG_MARK, "Recieved query request");
3473
3474         int err = 0;
3475
3476         // XXX for now...
3477         dbhandle = writehandle;
3478
3479         jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
3480
3481         int flags = 0;
3482
3483         if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
3484                 flags |= SELECT_DISTINCT;
3485
3486         if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
3487                 flags |= DISABLE_I18N;
3488
3489         osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
3490         char* sql = SELECT(
3491                         ctx,
3492                         jsonObjectGetKey( hash, "select" ),
3493                         jsonObjectGetKey( hash, "from" ),
3494                         jsonObjectGetKey( hash, "where" ),
3495                         jsonObjectGetKey( hash, "having" ),
3496                         jsonObjectGetKey( hash, "order_by" ),
3497                         jsonObjectGetKey( hash, "limit" ),
3498                         jsonObjectGetKey( hash, "offset" ),
3499                         flags
3500         );
3501
3502         if (!sql) {
3503                 err = -1;
3504                 return err;
3505         }
3506         
3507         osrfLogDebug(OSRF_LOG_MARK, "%s SQL =  %s", MODULENAME, sql);
3508         dbi_result result = dbi_conn_query(dbhandle, sql);
3509
3510         if(result) {
3511                 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
3512
3513                 if (dbi_result_first_row(result)) {
3514                         /* JSONify the result */
3515                         osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
3516
3517                         do {
3518                                 jsonObject* return_val = oilsMakeJSONFromResult( result );
3519                                 osrfAppRespond( ctx, return_val );
3520                 jsonObjectFree( return_val );
3521                         } while (dbi_result_next_row(result));
3522
3523                 } else {
3524                         osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
3525                 }
3526
3527                 osrfAppRespondComplete( ctx, NULL );
3528
3529                 /* clean up the query */
3530                 dbi_result_free(result); 
3531
3532         } else {
3533                 err = -1;
3534                 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
3535                 osrfAppSessionStatus(
3536                         ctx->session,
3537                         OSRF_STATUS_INTERNALSERVERERROR,
3538                         "osrfMethodException",
3539                         ctx->request,
3540                         "Severe query error -- see error log for more details"
3541                 );
3542         }
3543
3544         free(sql);
3545         return err;
3546 }
3547
3548 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
3549                 const jsonObject* params, int* err ) {
3550
3551         // XXX for now...
3552         dbhandle = writehandle;
3553
3554         osrfHash* links = osrfHashGet(meta, "links");
3555         osrfHash* fields = osrfHashGet(meta, "fields");
3556         char* core_class = osrfHashGet(meta, "classname");
3557         char* pkey = osrfHashGet(meta, "primarykey");
3558
3559         const jsonObject* _tmp;
3560         jsonObject* obj;
3561         jsonObject* search_hash = jsonObjectGetIndex(params, 0);
3562         jsonObject* order_hash = jsonObjectGetIndex(params, 1);
3563
3564         char* sql = buildSELECT( search_hash, order_hash, meta, ctx );
3565         if (!sql) {
3566                 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
3567                 *err = -1;
3568                 return NULL;
3569         }
3570         
3571         osrfLogDebug(OSRF_LOG_MARK, "%s SQL =  %s", MODULENAME, sql);
3572
3573         dbi_result result = dbi_conn_query(dbhandle, sql);
3574         if( NULL == result ) {
3575                 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
3576                         MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
3577                 osrfAppSessionStatus(
3578                         ctx->session,
3579                         OSRF_STATUS_INTERNALSERVERERROR,
3580                         "osrfMethodException",
3581                         ctx->request,
3582                         "Severe query error -- see error log for more details"
3583                 );
3584                 *err = -1;
3585                 free(sql);
3586                 return jsonNULL;
3587
3588         } else {
3589                 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
3590         }
3591
3592         jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
3593         osrfHash* dedup = osrfNewHash();
3594
3595         if (dbi_result_first_row(result)) {
3596                 /* JSONify the result */
3597                 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
3598                 do {
3599                         obj = oilsMakeFieldmapperFromResult( result, meta );
3600                         char* pkey_val = oilsFMGetString( obj, pkey );
3601                         if ( osrfHashGet( dedup, pkey_val ) ) {
3602                                 jsonObjectFree(obj);
3603                                 free(pkey_val);
3604                         } else {
3605                                 osrfHashSet( dedup, pkey_val, pkey_val );
3606                                 jsonObjectPush(res_list, obj);
3607                         }
3608                 } while (dbi_result_next_row(result));
3609         } else {
3610                 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
3611                         MODULENAME, sql );
3612         }
3613
3614         osrfHashFree(dedup);
3615         /* clean up the query */
3616         dbi_result_free(result);
3617         free(sql);
3618
3619         if (res_list->size && order_hash) {
3620                 _tmp = jsonObjectGetKeyConst( order_hash, "flesh" );
3621                 if (_tmp) {
3622                         int x = (int)jsonObjectGetNumber(_tmp);
3623                         if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
3624
3625                         const jsonObject* temp_blob;
3626                         if ((temp_blob = jsonObjectGetKeyConst( order_hash, "flesh_fields" )) && x > 0) {
3627
3628                                 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
3629                                 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
3630
3631                                 osrfStringArray* link_fields = NULL;
3632
3633                                 if (flesh_fields) {
3634                                         if (flesh_fields->size == 1) {
3635                                                 char* _t = jsonObjectToSimpleString( jsonObjectGetIndex( flesh_fields, 0 ) );
3636                                                 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
3637                                                 free(_t);
3638                                         }
3639
3640                                         if (!link_fields) {
3641                                                 jsonObject* _f;
3642                                                 link_fields = osrfNewStringArray(1);
3643                                                 jsonIterator* _i = jsonNewIterator( flesh_fields );
3644                                                 while ((_f = jsonIteratorNext( _i ))) {
3645                                                         osrfStringArrayAdd( link_fields, jsonObjectToSimpleString( _f ) );
3646                                                 }
3647                         jsonIteratorFree(_i);
3648                                         }
3649                                 }
3650
3651                                 jsonObject* cur;
3652                                 jsonIterator* itr = jsonNewIterator( res_list );
3653                                 while ((cur = jsonIteratorNext( itr ))) {
3654
3655                                         int i = 0;
3656                                         char* link_field;
3657                                         
3658                                         while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
3659
3660                                                 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
3661
3662                                                 osrfHash* kid_link = osrfHashGet(links, link_field);
3663                                                 if (!kid_link) continue;
3664
3665                                                 osrfHash* field = osrfHashGet(fields, link_field);
3666                                                 if (!field) continue;
3667
3668                                                 osrfHash* value_field = field;
3669
3670                                                 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
3671                                                 if (!kid_idl) continue;
3672
3673                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
3674                                                         value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
3675                                                 }
3676                                                         
3677                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
3678                                                         value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
3679                                                 }
3680
3681                                                 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
3682
3683                                                 if (link_map->size > 0) {
3684                                                         jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
3685                                                         jsonObjectPush(
3686                                                                 _kid_key,
3687                                                                 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
3688                                                         );
3689
3690                                                         jsonObjectSetKey(
3691                                                                 flesh_blob,
3692                                                                 osrfHashGet(kid_link, "class"),
3693                                                                 _kid_key
3694                                                         );
3695                                                 };
3696
3697                                                 osrfLogDebug(
3698                                                         OSRF_LOG_MARK,
3699                                                         "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
3700                                                         osrfHashGet(kid_link, "field"),
3701                                                         osrfHashGet(kid_link, "class"),
3702                                                         osrfHashGet(kid_link, "key"),
3703                                                         osrfHashGet(kid_link, "reltype")
3704                                                 );
3705
3706                                                 jsonObject* fake_params = jsonNewObjectType(JSON_ARRAY);
3707                                                 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH)); // search hash
3708                                                 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH)); // order/flesh hash
3709
3710                                                 osrfLogDebug(OSRF_LOG_MARK, "Creating dummy params object...");
3711
3712                                                 char* search_key =
3713                                                 jsonObjectToSimpleString(
3714                                                         jsonObjectGetIndex(
3715                                                                 cur,
3716                                                                 atoi( osrfHashGet(value_field, "array_position") )
3717                                                         )
3718                                                 );
3719
3720                                                 if (!search_key) {
3721                                                         osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
3722                                                         continue;
3723                                                 }
3724                                                         
3725                                                 jsonObjectSetKey(
3726                                                         jsonObjectGetIndex(fake_params, 0),
3727                                                         osrfHashGet(kid_link, "key"),
3728                                                         jsonNewObject( search_key )
3729                                                 );
3730
3731                                                 free(search_key);
3732
3733
3734                                                 jsonObjectSetKey(
3735                                                         jsonObjectGetIndex(fake_params, 1),
3736                                                         "flesh",
3737                                                         jsonNewNumberObject( (double)(x - 1 + link_map->size) )
3738                                                 );
3739
3740                                                 if (flesh_blob)
3741                                                         jsonObjectSetKey( jsonObjectGetIndex(fake_params, 1), "flesh_fields", jsonObjectClone(flesh_blob) );
3742
3743                                                 if (jsonObjectGetKeyConst(order_hash, "order_by")) {
3744                                                         jsonObjectSetKey(
3745                                                                 jsonObjectGetIndex(fake_params, 1),
3746                                                                 "order_by",
3747                                                                 jsonObjectClone(jsonObjectGetKeyConst(order_hash, "order_by"))
3748                                                         );
3749                                                 }
3750
3751                                                 if (jsonObjectGetKeyConst(order_hash, "select")) {
3752                                                         jsonObjectSetKey(
3753                                                                 jsonObjectGetIndex(fake_params, 1),
3754                                                                 "select",
3755                                                                 jsonObjectClone(jsonObjectGetKeyConst(order_hash, "select"))
3756                                                         );
3757                                                 }
3758
3759                                                 jsonObject* kids = doFieldmapperSearch(ctx, kid_idl, fake_params, err);
3760
3761                                                 if(*err) {
3762                                                         jsonObjectFree( fake_params );
3763                                                         osrfStringArrayFree(link_fields);
3764                                                         jsonIteratorFree(itr);
3765                                                         jsonObjectFree(res_list);
3766                                                         jsonObjectFree(flesh_blob);
3767                                                         return jsonNULL;
3768                                                 }
3769
3770                                                 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
3771
3772                                                 jsonObject* X = NULL;
3773                                                 if ( link_map->size > 0 && kids->size > 0 ) {
3774                                                         X = kids;
3775                                                         kids = jsonNewObjectType(JSON_ARRAY);
3776
3777                                                         jsonObject* _k_node;
3778                                                         jsonIterator* _k = jsonNewIterator( X );
3779                                                         while ((_k_node = jsonIteratorNext( _k ))) {
3780                                                                 jsonObjectPush(
3781                                                                         kids,
3782                                                                         jsonObjectClone(
3783                                                                                 jsonObjectGetIndex(
3784                                                                                         _k_node,
3785                                                                                         (unsigned long)atoi(
3786                                                                                                 osrfHashGet(
3787                                                                                                         osrfHashGet(
3788                                                                                                                 osrfHashGet(
3789                                                                                                                         osrfHashGet(
3790                                                                                                                                 oilsIDL(),
3791                                                                                                                                 osrfHashGet(kid_link, "class")
3792                                                                                                                         ),
3793                                                                                                                         "fields"
3794                                                                                                                 ),
3795                                                                                                                 osrfStringArrayGetString( link_map, 0 )
3796                                                                                                         ),
3797                                                                                                         "array_position"
3798                                                                                                 )
3799                                                                                         )
3800                                                                                 )
3801                                                                         )
3802                                                                 );
3803                                                         }
3804                                                         jsonIteratorFree(_k);
3805                                                 }
3806
3807                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
3808                                                         osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
3809                                                         jsonObjectSetIndex(
3810                                                                 cur,
3811                                                                 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
3812                                                                 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
3813                                                         );
3814                                                 }
3815
3816                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
3817                                                         osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
3818                                                         jsonObjectSetIndex(
3819                                                                 cur,
3820                                                                 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
3821                                                                 jsonObjectClone( kids )
3822                                                         );
3823                                                 }
3824
3825                                                 if (X) {
3826                                                         jsonObjectFree(kids);
3827                                                         kids = X;
3828                                                 }
3829
3830                                                 jsonObjectFree( kids );
3831                                                 jsonObjectFree( fake_params );
3832
3833                                                 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
3834                                                 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
3835
3836                                         }
3837                                 }
3838                                 jsonObjectFree( flesh_blob );
3839                                 osrfStringArrayFree(link_fields);
3840                                 jsonIteratorFree(itr);
3841                         }
3842                 }
3843         }
3844
3845         return res_list;
3846 }
3847
3848
3849 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
3850
3851         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
3852 #ifdef PCRUD
3853         jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
3854 #else
3855         jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
3856 #endif
3857
3858         if (!verifyObjectClass(ctx, target)) {
3859                 *err = -1;
3860                 return jsonNULL;
3861         }
3862
3863         if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
3864                 osrfAppSessionStatus(
3865                         ctx->session,
3866                         OSRF_STATUS_BADREQUEST,
3867                         "osrfMethodException",
3868                         ctx->request,
3869                         "No active transaction -- required for UPDATE"
3870                 );
3871                 *err = -1;
3872                 return jsonNULL;
3873         }
3874
3875         // The following test is harmless but redundant.  If a class is
3876         // readonly, we don't register an update method for it.
3877         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
3878                 osrfAppSessionStatus(
3879                         ctx->session,
3880                         OSRF_STATUS_BADREQUEST,
3881                         "osrfMethodException",
3882                         ctx->request,
3883                         "Cannot UPDATE readonly class"
3884                 );
3885                 *err = -1;
3886                 return jsonNULL;
3887         }
3888
3889         dbhandle = writehandle;
3890
3891         char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
3892
3893         // Set the last_xact_id
3894         int index = oilsIDL_ntop( target->classname, "last_xact_id" );
3895         if (index > -1) {
3896                 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
3897                 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
3898         }       
3899
3900         char* pkey = osrfHashGet(meta, "primarykey");
3901         osrfHash* fields = osrfHashGet(meta, "fields");
3902
3903         char* id = oilsFMGetString( target, pkey );
3904
3905         osrfLogDebug(
3906                 OSRF_LOG_MARK,
3907                 "%s updating %s object with %s = %s",
3908                 MODULENAME,
3909                 osrfHashGet(meta, "fieldmapper"),
3910                 pkey,
3911                 id
3912         );
3913
3914         growing_buffer* sql = buffer_init(128);
3915         buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
3916
3917         int i = 0;
3918         int first = 1;
3919         char* field_name;
3920         osrfStringArray* field_list = osrfHashKeys( fields );
3921         while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
3922
3923                 osrfHash* field = osrfHashGet( fields, field_name );
3924
3925                 if(!( strcmp( field_name, pkey ) )) continue;
3926                 if( str_is_true( osrfHashGet(osrfHashGet(fields,field_name), "virtual") ) )
3927                         continue;
3928
3929                 const jsonObject* field_object = oilsFMGetObject( target, field_name );
3930
3931                 char* value;
3932                 if (field_object && field_object->classname) {
3933                         value = oilsFMGetString(
3934                                 field_object,
3935                                 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
3936             );
3937                 } else {
3938                         value = jsonObjectToSimpleString( field_object );
3939                 }
3940
3941                 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s", osrfHashGet(meta, "fieldmapper"), field_name, value);
3942
3943                 if (!field_object || field_object->type == JSON_NULL) {
3944                         if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) ) && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
3945                                 if (first) first = 0;
3946                                 else OSRF_BUFFER_ADD_CHAR(sql, ',');
3947                                 buffer_fadd( sql, " %s = NULL", field_name );
3948                         }
3949                         
3950                 } else if ( !strcmp(osrfHashGet(field, "primitive"), "number") ) {
3951                         if (first) first = 0;
3952                         else OSRF_BUFFER_ADD_CHAR(sql, ',');
3953
3954                         if ( !strncmp(osrfHashGet(field, "datatype"), "INT", (size_t)3) ) {
3955                                 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
3956                         } else if ( !strcmp(osrfHashGet(field, "datatype"), "NUMERIC") ) {
3957                                 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
3958                         }
3959
3960                         osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, osrfHashGet(field, "datatype"));
3961
3962                 } else {
3963                         if ( dbi_conn_quote_string(dbhandle, &value) ) {
3964                                 if (first) first = 0;
3965                                 else OSRF_BUFFER_ADD_CHAR(sql, ',');
3966                                 buffer_fadd( sql, " %s = %s", field_name, value );
3967
3968                         } else {
3969                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
3970                                 osrfAppSessionStatus(
3971                                         ctx->session,
3972                                         OSRF_STATUS_INTERNALSERVERERROR,
3973                                         "osrfMethodException",
3974                                         ctx->request,
3975                                         "Error quoting string -- please see the error log for more details"
3976                                 );
3977                                 free(value);
3978                                 free(id);
3979                                 buffer_free(sql);
3980                                 *err = -1;
3981                                 return jsonNULL;
3982                         }
3983                 }
3984
3985                 free(value);
3986                 
3987         }
3988
3989         jsonObject* obj = jsonNewObject(id);
3990
3991         if ( strcmp( osrfHashGet( osrfHashGet( osrfHashGet(meta, "fields"), pkey ), "primitive" ), "number" ) )
3992                 dbi_conn_quote_string(dbhandle, &id);
3993
3994         buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
3995
3996         char* query = buffer_release(sql);
3997         osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
3998
3999         dbi_result result = dbi_conn_query(dbhandle, query);
4000         free(query);
4001
4002         if (!result) {
4003                 jsonObjectFree(obj);
4004                 obj = jsonNewObject(NULL);
4005                 osrfLogError(
4006                         OSRF_LOG_MARK,
4007                         "%s ERROR updating %s object with %s = %s",
4008                         MODULENAME,
4009                         osrfHashGet(meta, "fieldmapper"),
4010                         pkey,
4011                         id
4012                 );
4013         }
4014
4015         free(id);
4016
4017         return obj;
4018 }
4019
4020 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
4021
4022         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
4023
4024         if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
4025                 osrfAppSessionStatus(
4026                         ctx->session,
4027                         OSRF_STATUS_BADREQUEST,
4028                         "osrfMethodException",
4029                         ctx->request,
4030                         "No active transaction -- required for DELETE"
4031                 );
4032                 *err = -1;
4033                 return jsonNULL;
4034         }
4035
4036         // The following test is harmless but redundant.  If a class is
4037         // readonly, we don't register a delete method for it.
4038         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
4039                 osrfAppSessionStatus(
4040                         ctx->session,
4041                         OSRF_STATUS_BADREQUEST,
4042                         "osrfMethodException",
4043                         ctx->request,
4044                         "Cannot DELETE readonly class"
4045                 );
4046                 *err = -1;
4047                 return jsonNULL;
4048         }
4049
4050         dbhandle = writehandle;
4051
4052         jsonObject* obj;
4053
4054         char* pkey = osrfHashGet(meta, "primarykey");
4055
4056         int _obj_pos = 0;
4057 #ifdef PCRUD
4058                 _obj_pos = 1;
4059 #endif
4060
4061         char* id;
4062         if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4063                 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4064                         *err = -1;
4065                         return jsonNULL;
4066                 }
4067
4068                 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4069         } else {
4070 #ifdef PCRUD
4071         if (!verifyObjectPCRUD( ctx, NULL )) {
4072                         *err = -1;
4073                         return jsonNULL;
4074         }
4075 #endif
4076                 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4077         }
4078
4079         osrfLogDebug(
4080                 OSRF_LOG_MARK,
4081                 "%s deleting %s object with %s = %s",
4082                 MODULENAME,
4083                 osrfHashGet(meta, "fieldmapper"),
4084                 pkey,
4085                 id
4086         );
4087
4088         obj = jsonNewObject(id);
4089
4090         if ( strcmp( osrfHashGet( osrfHashGet( osrfHashGet(meta, "fields"), pkey ), "primitive" ), "number" ) )
4091                 dbi_conn_quote_string(writehandle, &id);
4092
4093         dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4094
4095         if (!result) {
4096                 jsonObjectFree(obj);
4097                 obj = jsonNewObject(NULL);
4098                 osrfLogError(
4099                         OSRF_LOG_MARK,
4100                         "%s ERROR deleting %s object with %s = %s",
4101                         MODULENAME,
4102                         osrfHashGet(meta, "fieldmapper"),
4103                         pkey,
4104                         id
4105                 );
4106         }
4107
4108         free(id);
4109
4110         return obj;
4111
4112 }
4113
4114
4115 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4116         if(!(result && meta)) return jsonNULL;
4117
4118         jsonObject* object = jsonNewObject(NULL);
4119         jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4120
4121         osrfHash* fields = osrfHashGet(meta, "fields");
4122
4123         osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4124
4125         osrfHash* _f;
4126         time_t _tmp_dt;
4127         char dt_string[256];
4128         struct tm gmdt;
4129
4130         int fmIndex;
4131         int columnIndex = 1;
4132         int attr;
4133         unsigned short type;
4134         const char* columnName;
4135
4136         /* cycle through the column list */
4137         while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4138
4139                 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4140
4141                 fmIndex = -1; // reset the position
4142                 
4143                 /* determine the field type and storage attributes */
4144                 type = dbi_result_get_field_type(result, columnName);
4145                 attr = dbi_result_get_field_attribs(result, columnName);
4146
4147                 /* fetch the fieldmapper index */
4148                 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4149                         
4150                         if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4151                                 continue;
4152                         
4153                         const char* pos = (char*)osrfHashGet(_f, "array_position");
4154                         if ( !pos ) continue;
4155
4156                         fmIndex = atoi( pos );
4157                         osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
4158                 } else {
4159                         continue;
4160                 }
4161
4162                 if (dbi_result_field_is_null(result, columnName)) {
4163                         jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
4164                 } else {
4165
4166                         switch( type ) {
4167
4168                                 case DBI_TYPE_INTEGER :
4169
4170                                         if( attr & DBI_INTEGER_SIZE8 ) 
4171                                                 jsonObjectSetIndex( object, fmIndex, 
4172                                                         jsonNewNumberObject(dbi_result_get_longlong(result, columnName)));
4173                                         else 
4174                                                 jsonObjectSetIndex( object, fmIndex, 
4175                                                         jsonNewNumberObject(dbi_result_get_int(result, columnName)));
4176
4177                                         break;
4178
4179                                 case DBI_TYPE_DECIMAL :
4180                                         jsonObjectSetIndex( object, fmIndex, 
4181                                                         jsonNewNumberObject(dbi_result_get_double(result, columnName)));
4182                                         break;
4183
4184                                 case DBI_TYPE_STRING :
4185
4186
4187                                         jsonObjectSetIndex(
4188                                                 object,
4189                                                 fmIndex,
4190                                                 jsonNewObject( dbi_result_get_string(result, columnName) )
4191                                         );
4192
4193                                         break;
4194
4195                                 case DBI_TYPE_DATETIME :
4196
4197                                         memset(dt_string, '\0', sizeof(dt_string));
4198                                         memset(&gmdt, '\0', sizeof(gmdt));
4199
4200                                         _tmp_dt = dbi_result_get_datetime(result, columnName);
4201
4202
4203                                         if (!(attr & DBI_DATETIME_DATE)) {
4204                                                 gmtime_r( &_tmp_dt, &gmdt );
4205                                                 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4206                                         } else if (!(attr & DBI_DATETIME_TIME)) {
4207                                                 localtime_r( &_tmp_dt, &gmdt );
4208                                                 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4209                                         } else {
4210                                                 localtime_r( &_tmp_dt, &gmdt );
4211                                                 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4212                                         }
4213
4214                                         jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
4215
4216                                         break;
4217
4218                                 case DBI_TYPE_BINARY :
4219                                         osrfLogError( OSRF_LOG_MARK, 
4220                                                 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4221                         }
4222                 }
4223         }
4224
4225         return object;
4226 }
4227
4228 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
4229         if(!result) return jsonNULL;
4230
4231         jsonObject* object = jsonNewObject(NULL);
4232
4233         time_t _tmp_dt;
4234         char dt_string[256];
4235         struct tm gmdt;
4236
4237         int fmIndex;
4238         int columnIndex = 1;
4239         int attr;
4240         unsigned short type;
4241         const char* columnName;
4242
4243         /* cycle through the column list */
4244         while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4245
4246                 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4247
4248                 fmIndex = -1; // reset the position
4249                 
4250                 /* determine the field type and storage attributes */
4251                 type = dbi_result_get_field_type(result, columnName);
4252                 attr = dbi_result_get_field_attribs(result, columnName);
4253
4254                 if (dbi_result_field_is_null(result, columnName)) {
4255                         jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
4256                 } else {
4257
4258                         switch( type ) {
4259
4260                                 case DBI_TYPE_INTEGER :
4261
4262                                         if( attr & DBI_INTEGER_SIZE8 ) 
4263                                                 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_longlong(result, columnName)) );
4264                                         else 
4265                                                 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_int(result, columnName)) );
4266                                         break;
4267
4268                                 case DBI_TYPE_DECIMAL :
4269                                         jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_double(result, columnName)) );
4270                                         break;
4271
4272                                 case DBI_TYPE_STRING :
4273                                         jsonObjectSetKey( object, columnName, jsonNewObject(dbi_result_get_string(result, columnName)) );
4274                                         break;
4275
4276                                 case DBI_TYPE_DATETIME :
4277
4278                                         memset(dt_string, '\0', sizeof(dt_string));
4279                                         memset(&gmdt, '\0', sizeof(gmdt));
4280
4281                                         _tmp_dt = dbi_result_get_datetime(result, columnName);
4282
4283
4284                                         if (!(attr & DBI_DATETIME_DATE)) {
4285                                                 gmtime_r( &_tmp_dt, &gmdt );
4286                                                 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4287                                         } else if (!(attr & DBI_DATETIME_TIME)) {
4288                                                 localtime_r( &_tmp_dt, &gmdt );
4289                                                 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4290                                         } else {
4291                                                 localtime_r( &_tmp_dt, &gmdt );
4292                                                 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4293                                         }
4294
4295                                         jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
4296                                         break;
4297
4298                                 case DBI_TYPE_BINARY :
4299                                         osrfLogError( OSRF_LOG_MARK, 
4300                                                 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4301                         }
4302                 }
4303         }
4304
4305         return object;
4306 }
4307
4308 // Interpret a string as true or false
4309 static int str_is_true( const char* str ) {
4310         if( NULL == str || strcasecmp( str, "true" ) )
4311                 return 0;
4312         else
4313                 return 1;
4314 }
4315
4316 // Interpret a jsonObject as true or false
4317 static int obj_is_true( const jsonObject* obj ) {
4318         if( !obj )
4319                 return 0;
4320         else switch( obj->type )
4321         {
4322                 case JSON_BOOL :
4323                         if( obj->value.b )
4324                                 return 1;
4325                         else
4326                                 return 0;
4327                 case JSON_STRING :
4328                         if( strcasecmp( obj->value.s, "true" ) )
4329                                 return 0;
4330                         else
4331                                 return 1;
4332                 case JSON_NUMBER :          // Support 1/0 for perl's sake
4333                         if( jsonObjectGetNumber( obj ) == 1.0 )
4334                                 return 1;
4335                         else
4336                                 return 0;
4337                 default :
4338                         return 0;
4339         }
4340 }
4341
4342 // Translate a numeric code into a text string identifying a type of
4343 // jsonObject.  To be used for building error messages.
4344 static const char* json_type( int code ) {
4345         switch ( code )
4346         {
4347                 case 0 :
4348                         return "JSON_HASH";
4349                 case 1 :
4350                         return "JSON_ARRAY";
4351                 case 2 :
4352                         return "JSON_STRING";
4353                 case 3 :
4354                         return "JSON_NUMBER";
4355                 case 4 :
4356                         return "JSON_NULL";
4357                 case 5 :
4358                         return "JSON_BOOL";
4359                 default :
4360                         return "(unrecognized)";
4361         }
4362 }