]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/c-apps/oils_cstore.c
In SELECT(): Enhanced the validation of JSON queries:
[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                                         // again, just to be safe
2733                                         const char* col_name = selfield->value.s;
2734                                         osrfHash* field_def = osrfHashGet( class_field_set, col_name );
2735                                         if ( !field_def ) continue;     // No such field in current class; skip it
2736
2737                                         if (locale) {
2738                                                 const char* i18n;
2739                                                 if (flags & DISABLE_I18N)
2740                                                         i18n = NULL;
2741                                                 else
2742                                                         i18n = osrfHashGet(field_def, "i18n");
2743
2744                                                 if( str_is_true( i18n ) ) {
2745                             buffer_fadd( select_buf,
2746                                                                 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
2747                                                                 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
2748                         } else {
2749                                             buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
2750                         }
2751                     } else {
2752                                         buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
2753                     }
2754                                         
2755                                 // ... but it could be an object, in which case we check for a Field Transform
2756                                 } else if (selfield->type == JSON_HASH) {
2757
2758                                         char* col_name = jsonObjectToSimpleString( jsonObjectGetKeyConst( selfield, "column" ) );
2759
2760                                         // Get the field definition from the IDL
2761                                         osrfHash* field_def = osrfHashGet( class_field_set, col_name );
2762                                         if ( !field_def ) continue;         // No such field defined in IDL.  Skip it.
2763
2764                                         // Decide what to use as a column alias
2765                                         char* _alias;
2766                                         if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
2767                                                 _alias = jsonObjectToSimpleString( tmp_const );
2768                                         } else {         // Use field name as the alias
2769                                                 _alias = col_name;
2770                                         }
2771
2772                                         if (jsonObjectGetKeyConst( selfield, "transform" )) {
2773                                                 char* transform_str = searchFieldTransform(cname, field_def, selfield);
2774                                                 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
2775                                                 free(transform_str);
2776                                         } else {
2777
2778                         if (locale) {
2779                                     const char* i18n;
2780                                         if (flags & DISABLE_I18N)
2781                                 i18n = NULL;
2782                                                         else
2783                                                                 i18n = osrfHashGet(field_def, "i18n");
2784
2785                                                         if( str_is_true( i18n ) ) {
2786                                 buffer_fadd( select_buf,
2787                                                                         " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
2788                                                                         class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
2789                             } else {
2790                                                     buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
2791                             }
2792                         } else {
2793                                                 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
2794                         }
2795                                     }
2796
2797                                         if( _alias != col_name )
2798                                             free(_alias);
2799                                         free( col_name );
2800                             }
2801                                 else {
2802                                         osrfLogError(
2803                                                 OSRF_LOG_MARK,
2804                                                 "%s: Selected item is unexpected JSON type: %s",
2805                                                 MODULENAME,
2806                                                 json_type( selfield->type )
2807                                         );
2808                                         if( ctx )
2809                                                 osrfAppSessionStatus(
2810                                                         ctx->session,
2811                                                         OSRF_STATUS_INTERNALSERVERERROR,
2812                                                         "osrfMethodException",
2813                                                         ctx->request,
2814                                                         "Ill-formed SELECT item in JSON query"
2815                                                 );
2816                                         jsonIteratorFree( select_itr );
2817                                         jsonIteratorFree( selclass_itr );
2818                                         jsonObjectFree( is_agg );
2819                                         buffer_free( sql_buf );
2820                                         buffer_free( select_buf );
2821                                         buffer_free( order_buf );
2822                                         buffer_free( group_buf );
2823                                         buffer_free( having_buf );
2824                                         free( core_class );
2825                                         return NULL;
2826                                 }
2827
2828                             if (is_agg->size || (flags & SELECT_DISTINCT)) {
2829
2830                                         const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
2831                                     if ( ! obj_is_true( aggregate_obj ) ) {
2832                                             if (gfirst) {
2833                                                     gfirst = 0;
2834                                             } else {
2835                                                         OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
2836                                             }
2837
2838                                             buffer_fadd(group_buf, " %d", sel_pos);
2839                                         /*
2840                                     } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
2841                                             if (gfirst) {
2842                                                     gfirst = 0;
2843                                             } else {
2844                                                         OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
2845                                             }
2846
2847                                             _column = searchFieldTransform(cname, field, selfield);
2848                                                 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
2849                                                 OSRF_BUFFER_ADD(group_buf, _column);
2850                                             _column = searchFieldTransform(cname, field, selfield);
2851                                         */
2852                                     }
2853                             }
2854
2855                             sel_pos++;
2856                     } // end while -- iterating across SELECT columns
2857
2858             jsonIteratorFree(select_itr);
2859             } // end while -- iterating across classes
2860
2861         jsonIteratorFree(selclass_itr);
2862
2863             if (is_agg) jsonObjectFree(is_agg);
2864     }
2865
2866
2867         char* col_list = buffer_release(select_buf);
2868         char* table = NULL;
2869         if (from_function) table = searchValueTransform(join_hash);
2870         else table = getSourceDefinition(core_meta);
2871         
2872         if( !table ) {
2873                 if (ctx)
2874                         osrfAppSessionStatus(
2875                                 ctx->session,
2876                                 OSRF_STATUS_INTERNALSERVERERROR,
2877                                 "osrfMethodException",
2878                                 ctx->request,
2879                                 "Unable to identify table for core class"
2880                         );
2881                 free( col_list );
2882                 buffer_free( sql_buf );
2883                 buffer_free( order_buf );
2884                 buffer_free( group_buf );
2885                 buffer_free( having_buf );
2886                 if( defaultselhash ) jsonObjectFree( defaultselhash );
2887                 free( core_class );
2888                 return NULL;    
2889         }
2890         
2891         // Put it all together
2892         buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
2893         free(col_list);
2894         free(table);
2895
2896     if (!from_function) {
2897             // Now, walk the join tree and add that clause
2898             if ( join_hash ) {
2899                     char* join_clause = searchJOIN( join_hash, core_meta );
2900                         if( join_clause ) {
2901                                 buffer_add(sql_buf, join_clause);
2902                         free(join_clause);
2903                         } else {
2904                                 if (ctx)
2905                                         osrfAppSessionStatus(
2906                                                 ctx->session,
2907                                                 OSRF_STATUS_INTERNALSERVERERROR,
2908                                                 "osrfMethodException",
2909                                                 ctx->request,
2910                                                 "Unable to construct JOIN clause(s)"
2911                                         );
2912                                 buffer_free( sql_buf );
2913                                 buffer_free( order_buf );
2914                                 buffer_free( group_buf );
2915                                 buffer_free( having_buf );
2916                                 if( defaultselhash ) jsonObjectFree( defaultselhash );
2917                                 free( core_class );
2918                                 return NULL;
2919                         }
2920             }
2921
2922                 // Build a WHERE clause, if there is one
2923             if ( search_hash ) {
2924                     buffer_add(sql_buf, " WHERE ");
2925
2926                     // and it's on the WHERE clause
2927                     char* pred = searchWHERE( search_hash, core_meta, AND_OP_JOIN, ctx );
2928
2929                     if (pred) {
2930                                 buffer_add(sql_buf, pred);
2931                                 free(pred);
2932                         } else {
2933                                 if (ctx) {
2934                                 osrfAppSessionStatus(
2935                                         ctx->session,
2936                                         OSRF_STATUS_INTERNALSERVERERROR,
2937                                         "osrfMethodException",
2938                                         ctx->request,
2939                                         "Severe query error in WHERE predicate -- see error log for more details"
2940                                 );
2941                             }
2942                             free(core_class);
2943                             buffer_free(having_buf);
2944                             buffer_free(group_buf);
2945                             buffer_free(order_buf);
2946                             buffer_free(sql_buf);
2947                             if (defaultselhash) jsonObjectFree(defaultselhash);
2948                             return NULL;
2949                     }
2950         }
2951
2952                 // Build a HAVING clause, if there is one
2953             if ( having_hash ) {
2954                     buffer_add(sql_buf, " HAVING ");
2955
2956                     // and it's on the the WHERE clause
2957                     char* pred = searchWHERE( having_hash, core_meta, AND_OP_JOIN, ctx );
2958
2959                     if (pred) {
2960                                 buffer_add(sql_buf, pred);
2961                                 free(pred);
2962                         } else {
2963                                 if (ctx) {
2964                                 osrfAppSessionStatus(
2965                                         ctx->session,
2966                                         OSRF_STATUS_INTERNALSERVERERROR,
2967                                         "osrfMethodException",
2968                                         ctx->request,
2969                                         "Severe query error in HAVING predicate -- see error log for more details"
2970                                 );
2971                             }
2972                             free(core_class);
2973                             buffer_free(having_buf);
2974                             buffer_free(group_buf);
2975                             buffer_free(order_buf);
2976                             buffer_free(sql_buf);
2977                             if (defaultselhash) jsonObjectFree(defaultselhash);
2978                             return NULL;
2979                     }
2980             }
2981
2982                 // Build an ORDER BY clause, if there is one
2983             first = 1;
2984             jsonIterator* class_itr = jsonNewIterator( order_hash );
2985             while ( (snode = jsonIteratorNext( class_itr )) ) {
2986
2987                     if (!jsonObjectGetKeyConst(selhash,class_itr->key))
2988                             continue;
2989
2990                     if ( snode->type == JSON_HASH ) {
2991
2992                         jsonIterator* order_itr = jsonNewIterator( snode );
2993                             while ( (onode = jsonIteratorNext( order_itr )) ) {
2994
2995                                     if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ))
2996                                             continue;
2997
2998                                     char* direction = NULL;
2999                                     if ( onode->type == JSON_HASH ) {
3000                                             if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3001                                                     string = searchFieldTransform(
3002                                                             class_itr->key,
3003                                                             oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ),
3004                                                             onode
3005                                                     );
3006                                             } else {
3007                                                     growing_buffer* field_buf = buffer_init(16);
3008                                                     buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3009                                                     string = buffer_release(field_buf);
3010                                             }
3011
3012                                             if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
3013                                                     direction = jsonObjectToSimpleString(tmp_const);
3014                                                     if (!strncasecmp(direction, "d", 1)) {
3015                                                             free(direction);
3016                                                             direction = " DESC";
3017                                                     } else {
3018                                                             free(direction);
3019                                                             direction = " ASC";
3020                                                     }
3021                                             }
3022
3023                                     } else {
3024                                             string = strdup(order_itr->key);
3025                                             direction = jsonObjectToSimpleString(onode);
3026                                             if (!strncasecmp(direction, "d", 1)) {
3027                                                     free(direction);
3028                                                     direction = " DESC";
3029                                             } else {
3030                                                     free(direction);
3031                                                     direction = " ASC";
3032                                             }
3033                                     }
3034
3035                                     if (first) {
3036                                             first = 0;
3037                                     } else {
3038                                             buffer_add(order_buf, ", ");
3039                                     }
3040
3041                                     buffer_add(order_buf, string);
3042                                     free(string);
3043
3044                                     if (direction) {
3045                                             buffer_add(order_buf, direction);
3046                                     }
3047
3048                             } // end while
3049                 // jsonIteratorFree(order_itr);
3050
3051                     } else if ( snode->type == JSON_ARRAY ) {
3052
3053                         jsonIterator* order_itr = jsonNewIterator( snode );
3054                             while ( (onode = jsonIteratorNext( order_itr )) ) {
3055
3056                                     char* _f = jsonObjectToSimpleString( onode );
3057
3058                                     if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, _f))
3059                                             continue;
3060
3061                                     if (first) {
3062                                             first = 0;
3063                                     } else {
3064                                             buffer_add(order_buf, ", ");
3065                                     }
3066
3067                                     buffer_add(order_buf, _f);
3068                                     free(_f);
3069
3070                             } // end while
3071                 // jsonIteratorFree(order_itr);
3072
3073
3074                     // IT'S THE OOOOOOOOOOOLD STYLE!
3075                     } else {
3076                             osrfLogError(OSRF_LOG_MARK, "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
3077                             if (ctx) {
3078                                 osrfAppSessionStatus(
3079                                         ctx->session,
3080                                         OSRF_STATUS_INTERNALSERVERERROR,
3081                                         "osrfMethodException",
3082                                         ctx->request,
3083                                         "Severe query error -- see error log for more details"
3084                                 );
3085                             }
3086
3087                             free(core_class);
3088                             buffer_free(having_buf);
3089                             buffer_free(group_buf);
3090                             buffer_free(order_buf);
3091                             buffer_free(sql_buf);
3092                             if (defaultselhash) jsonObjectFree(defaultselhash);
3093                             jsonIteratorFree(class_itr);
3094                             return NULL;
3095                     }
3096
3097             } // end while
3098                 // jsonIteratorFree(class_itr);
3099         }
3100
3101
3102         string = buffer_release(group_buf);
3103
3104         if ( *string ) {
3105                 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
3106                 OSRF_BUFFER_ADD( sql_buf, string );
3107         }
3108
3109         free(string);
3110
3111         string = buffer_release(having_buf);
3112  
3113         if ( *string ) {
3114                 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
3115                 OSRF_BUFFER_ADD( sql_buf, string );
3116         }
3117
3118         free(string);
3119
3120         string = buffer_release(order_buf);
3121
3122         if ( *string ) {
3123                 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3124                 OSRF_BUFFER_ADD( sql_buf, string );
3125         }
3126
3127         free(string);
3128
3129         if ( limit ){
3130                 string = jsonObjectToSimpleString(limit);
3131                 buffer_fadd( sql_buf, " LIMIT %d", atoi(string) );
3132                 free(string);
3133         }
3134
3135         if (offset) {
3136                 string = jsonObjectToSimpleString(offset);
3137                 buffer_fadd( sql_buf, " OFFSET %d", atoi(string) );
3138                 free(string);
3139         }
3140
3141         if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3142
3143         free(core_class);
3144         if (defaultselhash) jsonObjectFree(defaultselhash);
3145
3146         return buffer_release(sql_buf);
3147
3148 }
3149
3150 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
3151
3152         const char* locale = osrf_message_get_last_locale();
3153
3154         osrfHash* fields = osrfHashGet(meta, "fields");
3155         char* core_class = osrfHashGet(meta, "classname");
3156
3157         const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
3158
3159         jsonObject* node = NULL;
3160         jsonObject* snode = NULL;
3161         jsonObject* onode = NULL;
3162         const jsonObject* _tmp = NULL;
3163         jsonObject* selhash = NULL;
3164         jsonObject* defaultselhash = NULL;
3165
3166         growing_buffer* sql_buf = buffer_init(128);
3167         growing_buffer* select_buf = buffer_init(128);
3168
3169         if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
3170                 defaultselhash = jsonNewObjectType(JSON_HASH);
3171                 selhash = defaultselhash;
3172         }
3173         
3174         if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
3175                 jsonObjectSetKey( selhash, core_class, jsonNewObjectType(JSON_ARRAY) );
3176                 jsonObject* flist = jsonObjectGetKey( selhash, core_class );
3177                 
3178                 int i = 0;
3179                 char* field;
3180
3181                 osrfStringArray* keys = osrfHashKeys( fields );
3182                 while ( (field = osrfStringArrayGetString(keys, i++)) ) {
3183                         if( ! str_is_true( osrfHashGet( osrfHashGet( fields, field ), "virtual" ) ) )
3184                                 jsonObjectPush( flist, jsonNewObject( field ) );
3185                 }
3186                 osrfStringArrayFree(keys);
3187         }
3188
3189         int first = 1;
3190         jsonIterator* class_itr = jsonNewIterator( selhash );
3191         while ( (snode = jsonIteratorNext( class_itr )) ) {
3192
3193                 char* cname = class_itr->key;
3194                 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
3195                 if (!idlClass) continue;
3196
3197                 if (strcmp(core_class,class_itr->key)) {
3198                         if (!join_hash) continue;
3199
3200                         jsonObject* found =  jsonObjectFindPath(join_hash, "//%s", class_itr->key);
3201                         if (!found->size) {
3202                                 jsonObjectFree(found);
3203                                 continue;
3204                         }
3205
3206                         jsonObjectFree(found);
3207                 }
3208
3209                 jsonIterator* select_itr = jsonNewIterator( snode );
3210                 while ( (node = jsonIteratorNext( select_itr )) ) {
3211                         char* item_str = jsonObjectToSimpleString(node);
3212                         osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
3213                         free(item_str);
3214                         char* fname = osrfHashGet(field, "name");
3215
3216                         if (!field) continue;
3217
3218                         if (first) {
3219                                 first = 0;
3220                         } else {
3221                                 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
3222                         }
3223
3224             if (locale) {
3225                         const char* i18n;
3226                                 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
3227                                 if ( obj_is_true( no_i18n_obj ) )    // Suppress internationalization?
3228                                         i18n = NULL;
3229                                 else
3230                                         i18n = osrfHashGet(field, "i18n");
3231
3232                                 if( str_is_true( i18n ) ) {
3233                         char* pkey = osrfHashGet(idlClass, "primarykey");
3234                         char* tname = osrfHashGet(idlClass, "tablename");
3235
3236                     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);
3237                 } else {
3238                                 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3239                 }
3240             } else {
3241                             buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
3242             }
3243                 }
3244
3245         jsonIteratorFree(select_itr);
3246         }
3247
3248     jsonIteratorFree(class_itr);
3249
3250         char* col_list = buffer_release(select_buf);
3251         char* table = getSourceDefinition(meta);
3252         if( !table )
3253                 table = strdup( "(null)" );
3254
3255         buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
3256         free(col_list);
3257         free(table);
3258
3259         if ( join_hash ) {
3260                 char* join_clause = searchJOIN( join_hash, meta );
3261                 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
3262                 OSRF_BUFFER_ADD(sql_buf, join_clause);
3263                 free(join_clause);
3264         }
3265
3266         osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL =  %s",
3267                                  MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
3268
3269         buffer_add(sql_buf, " WHERE ");
3270
3271         char* pred = searchWHERE( search_hash, meta, AND_OP_JOIN, ctx );
3272         if (!pred) {
3273                 osrfAppSessionStatus(
3274                         ctx->session,
3275                         OSRF_STATUS_INTERNALSERVERERROR,
3276                                 "osrfMethodException",
3277                                 ctx->request,
3278                                 "Severe query error -- see error log for more details"
3279                         );
3280                 buffer_free(sql_buf);
3281                 if(defaultselhash) jsonObjectFree(defaultselhash);
3282                 return NULL;
3283         } else {
3284                 buffer_add(sql_buf, pred);
3285                 free(pred);
3286         }
3287
3288         if (order_hash) {
3289                 char* string = NULL;
3290                 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
3291
3292                         growing_buffer* order_buf = buffer_init(128);
3293
3294                         first = 1;
3295                         jsonIterator* class_itr = jsonNewIterator( _tmp );
3296                         while ( (snode = jsonIteratorNext( class_itr )) ) {
3297
3298                                 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
3299                                         continue;
3300
3301                                 if ( snode->type == JSON_HASH ) {
3302
3303                                         jsonIterator* order_itr = jsonNewIterator( snode );
3304                                         while ( (onode = jsonIteratorNext( order_itr )) ) {
3305
3306                                                 if (!oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ))
3307                                                         continue;
3308
3309                                                 char* direction = NULL;
3310                                                 if ( onode->type == JSON_HASH ) {
3311                                                         if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
3312                                                                 string = searchFieldTransform(
3313                                                                         class_itr->key,
3314                                                                         oilsIDLFindPath( "/%s/fields/%s", class_itr->key, order_itr->key ),
3315                                                                         onode
3316                                                                 );
3317                                                         } else {
3318                                                                 growing_buffer* field_buf = buffer_init(16);
3319                                                                 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
3320                                                                 string = buffer_release(field_buf);
3321                                                         }
3322
3323                                                         if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
3324                                                                 direction = jsonObjectToSimpleString(_tmp);
3325                                                                 if (!strncasecmp(direction, "d", 1)) {
3326                                                                         free(direction);
3327                                                                         direction = " DESC";
3328                                                                 } else {
3329                                                                         free(direction);
3330                                                                         direction = " ASC";
3331                                                                 }
3332                                                         }
3333
3334                                                 } else {
3335                                                         string = strdup(order_itr->key);
3336                                                         direction = jsonObjectToSimpleString(onode);
3337                                                         if (!strncasecmp(direction, "d", 1)) {
3338                                                                 free(direction);
3339                                                                 direction = " DESC";
3340                                                         } else {
3341                                                                 free(direction);
3342                                                                 direction = " ASC";
3343                                                         }
3344                                                 }
3345
3346                                                 if (first) {
3347                                                         first = 0;
3348                                                 } else {
3349                                                         buffer_add(order_buf, ", ");
3350                                                 }
3351
3352                                                 buffer_add(order_buf, string);
3353                                                 free(string);
3354
3355                                                 if (direction) {
3356                                                         buffer_add(order_buf, direction);
3357                                                 }
3358
3359                                         }
3360
3361                     jsonIteratorFree(order_itr);
3362
3363                                 } else {
3364                                         string = jsonObjectToSimpleString(snode);
3365                                         buffer_add(order_buf, string);
3366                                         free(string);
3367                                         break;
3368                                 }
3369
3370                         }
3371
3372             jsonIteratorFree(class_itr);
3373
3374                         string = buffer_release(order_buf);
3375
3376                         if ( *string ) {
3377                                 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
3378                                 OSRF_BUFFER_ADD( sql_buf, string );
3379                         }
3380
3381                         free(string);
3382                 }
3383
3384                 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
3385                         string = jsonObjectToSimpleString(_tmp);
3386                         buffer_fadd(
3387                                 sql_buf,
3388                                 " LIMIT %d",
3389                                 atoi(string)
3390                         );
3391                         free(string);
3392                 }
3393
3394                 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
3395                 if (_tmp) {
3396                         string = jsonObjectToSimpleString(_tmp);
3397                         buffer_fadd(
3398                                 sql_buf,
3399                                 " OFFSET %d",
3400                                 atoi(string)
3401                         );
3402                         free(string);
3403                 }
3404         }
3405
3406         if (defaultselhash) jsonObjectFree(defaultselhash);
3407
3408         OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
3409         return buffer_release(sql_buf);
3410 }
3411
3412 int doJSONSearch ( osrfMethodContext* ctx ) {
3413         if(osrfMethodVerifyContext( ctx )) {
3414                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
3415                 return -1;
3416         }
3417
3418         osrfLogDebug(OSRF_LOG_MARK, "Recieved query request");
3419
3420         int err = 0;
3421
3422         // XXX for now...
3423         dbhandle = writehandle;
3424
3425         jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
3426
3427         int flags = 0;
3428
3429         if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
3430                 flags |= SELECT_DISTINCT;
3431
3432         if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
3433                 flags |= DISABLE_I18N;
3434
3435         osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
3436         char* sql = SELECT(
3437                         ctx,
3438                         jsonObjectGetKey( hash, "select" ),
3439                         jsonObjectGetKey( hash, "from" ),
3440                         jsonObjectGetKey( hash, "where" ),
3441                         jsonObjectGetKey( hash, "having" ),
3442                         jsonObjectGetKey( hash, "order_by" ),
3443                         jsonObjectGetKey( hash, "limit" ),
3444                         jsonObjectGetKey( hash, "offset" ),
3445                         flags
3446         );
3447
3448         if (!sql) {
3449                 err = -1;
3450                 return err;
3451         }
3452         
3453         osrfLogDebug(OSRF_LOG_MARK, "%s SQL =  %s", MODULENAME, sql);
3454         dbi_result result = dbi_conn_query(dbhandle, sql);
3455
3456         if(result) {
3457                 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
3458
3459                 if (dbi_result_first_row(result)) {
3460                         /* JSONify the result */
3461                         osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
3462
3463                         do {
3464                                 jsonObject* return_val = oilsMakeJSONFromResult( result );
3465                                 osrfAppRespond( ctx, return_val );
3466                 jsonObjectFree( return_val );
3467                         } while (dbi_result_next_row(result));
3468
3469                 } else {
3470                         osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
3471                 }
3472
3473                 osrfAppRespondComplete( ctx, NULL );
3474
3475                 /* clean up the query */
3476                 dbi_result_free(result); 
3477
3478         } else {
3479                 err = -1;
3480                 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
3481                 osrfAppSessionStatus(
3482                         ctx->session,
3483                         OSRF_STATUS_INTERNALSERVERERROR,
3484                         "osrfMethodException",
3485                         ctx->request,
3486                         "Severe query error -- see error log for more details"
3487                 );
3488         }
3489
3490         free(sql);
3491         return err;
3492 }
3493
3494 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
3495                 const jsonObject* params, int* err ) {
3496
3497         // XXX for now...
3498         dbhandle = writehandle;
3499
3500         osrfHash* links = osrfHashGet(meta, "links");
3501         osrfHash* fields = osrfHashGet(meta, "fields");
3502         char* core_class = osrfHashGet(meta, "classname");
3503         char* pkey = osrfHashGet(meta, "primarykey");
3504
3505         const jsonObject* _tmp;
3506         jsonObject* obj;
3507         jsonObject* search_hash = jsonObjectGetIndex(params, 0);
3508         jsonObject* order_hash = jsonObjectGetIndex(params, 1);
3509
3510         char* sql = buildSELECT( search_hash, order_hash, meta, ctx );
3511         if (!sql) {
3512                 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
3513                 *err = -1;
3514                 return NULL;
3515         }
3516         
3517         osrfLogDebug(OSRF_LOG_MARK, "%s SQL =  %s", MODULENAME, sql);
3518
3519         dbi_result result = dbi_conn_query(dbhandle, sql);
3520         if( NULL == result ) {
3521                 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
3522                         MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
3523                 osrfAppSessionStatus(
3524                         ctx->session,
3525                         OSRF_STATUS_INTERNALSERVERERROR,
3526                         "osrfMethodException",
3527                         ctx->request,
3528                         "Severe query error -- see error log for more details"
3529                 );
3530                 *err = -1;
3531                 free(sql);
3532                 return jsonNULL;
3533
3534         } else {
3535                 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
3536         }
3537
3538         jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
3539         osrfHash* dedup = osrfNewHash();
3540
3541         if (dbi_result_first_row(result)) {
3542                 /* JSONify the result */
3543                 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
3544                 do {
3545                         obj = oilsMakeFieldmapperFromResult( result, meta );
3546                         char* pkey_val = oilsFMGetString( obj, pkey );
3547                         if ( osrfHashGet( dedup, pkey_val ) ) {
3548                                 jsonObjectFree(obj);
3549                                 free(pkey_val);
3550                         } else {
3551                                 osrfHashSet( dedup, pkey_val, pkey_val );
3552                                 jsonObjectPush(res_list, obj);
3553                         }
3554                 } while (dbi_result_next_row(result));
3555         } else {
3556                 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
3557                         MODULENAME, sql );
3558         }
3559
3560         osrfHashFree(dedup);
3561         /* clean up the query */
3562         dbi_result_free(result);
3563         free(sql);
3564
3565         if (res_list->size && order_hash) {
3566                 _tmp = jsonObjectGetKeyConst( order_hash, "flesh" );
3567                 if (_tmp) {
3568                         int x = (int)jsonObjectGetNumber(_tmp);
3569                         if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
3570
3571                         const jsonObject* temp_blob;
3572                         if ((temp_blob = jsonObjectGetKeyConst( order_hash, "flesh_fields" )) && x > 0) {
3573
3574                                 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
3575                                 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
3576
3577                                 osrfStringArray* link_fields = NULL;
3578
3579                                 if (flesh_fields) {
3580                                         if (flesh_fields->size == 1) {
3581                                                 char* _t = jsonObjectToSimpleString( jsonObjectGetIndex( flesh_fields, 0 ) );
3582                                                 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
3583                                                 free(_t);
3584                                         }
3585
3586                                         if (!link_fields) {
3587                                                 jsonObject* _f;
3588                                                 link_fields = osrfNewStringArray(1);
3589                                                 jsonIterator* _i = jsonNewIterator( flesh_fields );
3590                                                 while ((_f = jsonIteratorNext( _i ))) {
3591                                                         osrfStringArrayAdd( link_fields, jsonObjectToSimpleString( _f ) );
3592                                                 }
3593                         jsonIteratorFree(_i);
3594                                         }
3595                                 }
3596
3597                                 jsonObject* cur;
3598                                 jsonIterator* itr = jsonNewIterator( res_list );
3599                                 while ((cur = jsonIteratorNext( itr ))) {
3600
3601                                         int i = 0;
3602                                         char* link_field;
3603                                         
3604                                         while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
3605
3606                                                 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
3607
3608                                                 osrfHash* kid_link = osrfHashGet(links, link_field);
3609                                                 if (!kid_link) continue;
3610
3611                                                 osrfHash* field = osrfHashGet(fields, link_field);
3612                                                 if (!field) continue;
3613
3614                                                 osrfHash* value_field = field;
3615
3616                                                 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
3617                                                 if (!kid_idl) continue;
3618
3619                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
3620                                                         value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
3621                                                 }
3622                                                         
3623                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
3624                                                         value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
3625                                                 }
3626
3627                                                 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
3628
3629                                                 if (link_map->size > 0) {
3630                                                         jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
3631                                                         jsonObjectPush(
3632                                                                 _kid_key,
3633                                                                 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
3634                                                         );
3635
3636                                                         jsonObjectSetKey(
3637                                                                 flesh_blob,
3638                                                                 osrfHashGet(kid_link, "class"),
3639                                                                 _kid_key
3640                                                         );
3641                                                 };
3642
3643                                                 osrfLogDebug(
3644                                                         OSRF_LOG_MARK,
3645                                                         "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
3646                                                         osrfHashGet(kid_link, "field"),
3647                                                         osrfHashGet(kid_link, "class"),
3648                                                         osrfHashGet(kid_link, "key"),
3649                                                         osrfHashGet(kid_link, "reltype")
3650                                                 );
3651
3652                                                 jsonObject* fake_params = jsonNewObjectType(JSON_ARRAY);
3653                                                 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH)); // search hash
3654                                                 jsonObjectPush(fake_params, jsonNewObjectType(JSON_HASH)); // order/flesh hash
3655
3656                                                 osrfLogDebug(OSRF_LOG_MARK, "Creating dummy params object...");
3657
3658                                                 char* search_key =
3659                                                 jsonObjectToSimpleString(
3660                                                         jsonObjectGetIndex(
3661                                                                 cur,
3662                                                                 atoi( osrfHashGet(value_field, "array_position") )
3663                                                         )
3664                                                 );
3665
3666                                                 if (!search_key) {
3667                                                         osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
3668                                                         continue;
3669                                                 }
3670                                                         
3671                                                 jsonObjectSetKey(
3672                                                         jsonObjectGetIndex(fake_params, 0),
3673                                                         osrfHashGet(kid_link, "key"),
3674                                                         jsonNewObject( search_key )
3675                                                 );
3676
3677                                                 free(search_key);
3678
3679
3680                                                 jsonObjectSetKey(
3681                                                         jsonObjectGetIndex(fake_params, 1),
3682                                                         "flesh",
3683                                                         jsonNewNumberObject( (double)(x - 1 + link_map->size) )
3684                                                 );
3685
3686                                                 if (flesh_blob)
3687                                                         jsonObjectSetKey( jsonObjectGetIndex(fake_params, 1), "flesh_fields", jsonObjectClone(flesh_blob) );
3688
3689                                                 if (jsonObjectGetKeyConst(order_hash, "order_by")) {
3690                                                         jsonObjectSetKey(
3691                                                                 jsonObjectGetIndex(fake_params, 1),
3692                                                                 "order_by",
3693                                                                 jsonObjectClone(jsonObjectGetKeyConst(order_hash, "order_by"))
3694                                                         );
3695                                                 }
3696
3697                                                 if (jsonObjectGetKeyConst(order_hash, "select")) {
3698                                                         jsonObjectSetKey(
3699                                                                 jsonObjectGetIndex(fake_params, 1),
3700                                                                 "select",
3701                                                                 jsonObjectClone(jsonObjectGetKeyConst(order_hash, "select"))
3702                                                         );
3703                                                 }
3704
3705                                                 jsonObject* kids = doFieldmapperSearch(ctx, kid_idl, fake_params, err);
3706
3707                                                 if(*err) {
3708                                                         jsonObjectFree( fake_params );
3709                                                         osrfStringArrayFree(link_fields);
3710                                                         jsonIteratorFree(itr);
3711                                                         jsonObjectFree(res_list);
3712                                                         jsonObjectFree(flesh_blob);
3713                                                         return jsonNULL;
3714                                                 }
3715
3716                                                 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
3717
3718                                                 jsonObject* X = NULL;
3719                                                 if ( link_map->size > 0 && kids->size > 0 ) {
3720                                                         X = kids;
3721                                                         kids = jsonNewObjectType(JSON_ARRAY);
3722
3723                                                         jsonObject* _k_node;
3724                                                         jsonIterator* _k = jsonNewIterator( X );
3725                                                         while ((_k_node = jsonIteratorNext( _k ))) {
3726                                                                 jsonObjectPush(
3727                                                                         kids,
3728                                                                         jsonObjectClone(
3729                                                                                 jsonObjectGetIndex(
3730                                                                                         _k_node,
3731                                                                                         (unsigned long)atoi(
3732                                                                                                 osrfHashGet(
3733                                                                                                         osrfHashGet(
3734                                                                                                                 osrfHashGet(
3735                                                                                                                         osrfHashGet(
3736                                                                                                                                 oilsIDL(),
3737                                                                                                                                 osrfHashGet(kid_link, "class")
3738                                                                                                                         ),
3739                                                                                                                         "fields"
3740                                                                                                                 ),
3741                                                                                                                 osrfStringArrayGetString( link_map, 0 )
3742                                                                                                         ),
3743                                                                                                         "array_position"
3744                                                                                                 )
3745                                                                                         )
3746                                                                                 )
3747                                                                         )
3748                                                                 );
3749                                                         }
3750                                                         jsonIteratorFree(_k);
3751                                                 }
3752
3753                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
3754                                                         osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
3755                                                         jsonObjectSetIndex(
3756                                                                 cur,
3757                                                                 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
3758                                                                 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
3759                                                         );
3760                                                 }
3761
3762                                                 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
3763                                                         osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
3764                                                         jsonObjectSetIndex(
3765                                                                 cur,
3766                                                                 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
3767                                                                 jsonObjectClone( kids )
3768                                                         );
3769                                                 }
3770
3771                                                 if (X) {
3772                                                         jsonObjectFree(kids);
3773                                                         kids = X;
3774                                                 }
3775
3776                                                 jsonObjectFree( kids );
3777                                                 jsonObjectFree( fake_params );
3778
3779                                                 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
3780                                                 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
3781
3782                                         }
3783                                 }
3784                                 jsonObjectFree( flesh_blob );
3785                                 osrfStringArrayFree(link_fields);
3786                                 jsonIteratorFree(itr);
3787                         }
3788                 }
3789         }
3790
3791         return res_list;
3792 }
3793
3794
3795 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
3796
3797         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
3798 #ifdef PCRUD
3799         jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
3800 #else
3801         jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
3802 #endif
3803
3804         if (!verifyObjectClass(ctx, target)) {
3805                 *err = -1;
3806                 return jsonNULL;
3807         }
3808
3809         if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
3810                 osrfAppSessionStatus(
3811                         ctx->session,
3812                         OSRF_STATUS_BADREQUEST,
3813                         "osrfMethodException",
3814                         ctx->request,
3815                         "No active transaction -- required for UPDATE"
3816                 );
3817                 *err = -1;
3818                 return jsonNULL;
3819         }
3820
3821         // The following test is harmless but redundant.  If a class is
3822         // readonly, we don't register an update method for it.
3823         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
3824                 osrfAppSessionStatus(
3825                         ctx->session,
3826                         OSRF_STATUS_BADREQUEST,
3827                         "osrfMethodException",
3828                         ctx->request,
3829                         "Cannot UPDATE readonly class"
3830                 );
3831                 *err = -1;
3832                 return jsonNULL;
3833         }
3834
3835         dbhandle = writehandle;
3836
3837         char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
3838
3839         // Set the last_xact_id
3840         int index = oilsIDL_ntop( target->classname, "last_xact_id" );
3841         if (index > -1) {
3842                 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
3843                 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
3844         }       
3845
3846         char* pkey = osrfHashGet(meta, "primarykey");
3847         osrfHash* fields = osrfHashGet(meta, "fields");
3848
3849         char* id = oilsFMGetString( target, pkey );
3850
3851         osrfLogDebug(
3852                 OSRF_LOG_MARK,
3853                 "%s updating %s object with %s = %s",
3854                 MODULENAME,
3855                 osrfHashGet(meta, "fieldmapper"),
3856                 pkey,
3857                 id
3858         );
3859
3860         growing_buffer* sql = buffer_init(128);
3861         buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
3862
3863         int i = 0;
3864         int first = 1;
3865         char* field_name;
3866         osrfStringArray* field_list = osrfHashKeys( fields );
3867         while ( (field_name = osrfStringArrayGetString(field_list, i++)) ) {
3868
3869                 osrfHash* field = osrfHashGet( fields, field_name );
3870
3871                 if(!( strcmp( field_name, pkey ) )) continue;
3872                 if( str_is_true( osrfHashGet(osrfHashGet(fields,field_name), "virtual") ) )
3873                         continue;
3874
3875                 const jsonObject* field_object = oilsFMGetObject( target, field_name );
3876
3877                 char* value;
3878                 if (field_object && field_object->classname) {
3879                         value = oilsFMGetString(
3880                                 field_object,
3881                                 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
3882             );
3883                 } else {
3884                         value = jsonObjectToSimpleString( field_object );
3885                 }
3886
3887                 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s", osrfHashGet(meta, "fieldmapper"), field_name, value);
3888
3889                 if (!field_object || field_object->type == JSON_NULL) {
3890                         if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) ) && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
3891                                 if (first) first = 0;
3892                                 else OSRF_BUFFER_ADD_CHAR(sql, ',');
3893                                 buffer_fadd( sql, " %s = NULL", field_name );
3894                         }
3895                         
3896                 } else if ( !strcmp(osrfHashGet(field, "primitive"), "number") ) {
3897                         if (first) first = 0;
3898                         else OSRF_BUFFER_ADD_CHAR(sql, ',');
3899
3900                         if ( !strncmp(osrfHashGet(field, "datatype"), "INT", (size_t)3) ) {
3901                                 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
3902                         } else if ( !strcmp(osrfHashGet(field, "datatype"), "NUMERIC") ) {
3903                                 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
3904                         }
3905
3906                         osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, osrfHashGet(field, "datatype"));
3907
3908                 } else {
3909                         if ( dbi_conn_quote_string(dbhandle, &value) ) {
3910                                 if (first) first = 0;
3911                                 else OSRF_BUFFER_ADD_CHAR(sql, ',');
3912                                 buffer_fadd( sql, " %s = %s", field_name, value );
3913
3914                         } else {
3915                                 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
3916                                 osrfAppSessionStatus(
3917                                         ctx->session,
3918                                         OSRF_STATUS_INTERNALSERVERERROR,
3919                                         "osrfMethodException",
3920                                         ctx->request,
3921                                         "Error quoting string -- please see the error log for more details"
3922                                 );
3923                                 free(value);
3924                                 free(id);
3925                                 buffer_free(sql);
3926                                 *err = -1;
3927                                 return jsonNULL;
3928                         }
3929                 }
3930
3931                 free(value);
3932                 
3933         }
3934
3935         jsonObject* obj = jsonNewObject(id);
3936
3937         if ( strcmp( osrfHashGet( osrfHashGet( osrfHashGet(meta, "fields"), pkey ), "primitive" ), "number" ) )
3938                 dbi_conn_quote_string(dbhandle, &id);
3939
3940         buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
3941
3942         char* query = buffer_release(sql);
3943         osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
3944
3945         dbi_result result = dbi_conn_query(dbhandle, query);
3946         free(query);
3947
3948         if (!result) {
3949                 jsonObjectFree(obj);
3950                 obj = jsonNewObject(NULL);
3951                 osrfLogError(
3952                         OSRF_LOG_MARK,
3953                         "%s ERROR updating %s object with %s = %s",
3954                         MODULENAME,
3955                         osrfHashGet(meta, "fieldmapper"),
3956                         pkey,
3957                         id
3958                 );
3959         }
3960
3961         free(id);
3962
3963         return obj;
3964 }
3965
3966 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
3967
3968         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
3969
3970         if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
3971                 osrfAppSessionStatus(
3972                         ctx->session,
3973                         OSRF_STATUS_BADREQUEST,
3974                         "osrfMethodException",
3975                         ctx->request,
3976                         "No active transaction -- required for DELETE"
3977                 );
3978                 *err = -1;
3979                 return jsonNULL;
3980         }
3981
3982         // The following test is harmless but redundant.  If a class is
3983         // readonly, we don't register a delete method for it.
3984         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
3985                 osrfAppSessionStatus(
3986                         ctx->session,
3987                         OSRF_STATUS_BADREQUEST,
3988                         "osrfMethodException",
3989                         ctx->request,
3990                         "Cannot DELETE readonly class"
3991                 );
3992                 *err = -1;
3993                 return jsonNULL;
3994         }
3995
3996         dbhandle = writehandle;
3997
3998         jsonObject* obj;
3999
4000         char* pkey = osrfHashGet(meta, "primarykey");
4001
4002         int _obj_pos = 0;
4003 #ifdef PCRUD
4004                 _obj_pos = 1;
4005 #endif
4006
4007         char* id;
4008         if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
4009                 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
4010                         *err = -1;
4011                         return jsonNULL;
4012                 }
4013
4014                 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
4015         } else {
4016 #ifdef PCRUD
4017         if (!verifyObjectPCRUD( ctx, NULL )) {
4018                         *err = -1;
4019                         return jsonNULL;
4020         }
4021 #endif
4022                 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
4023         }
4024
4025         osrfLogDebug(
4026                 OSRF_LOG_MARK,
4027                 "%s deleting %s object with %s = %s",
4028                 MODULENAME,
4029                 osrfHashGet(meta, "fieldmapper"),
4030                 pkey,
4031                 id
4032         );
4033
4034         obj = jsonNewObject(id);
4035
4036         if ( strcmp( osrfHashGet( osrfHashGet( osrfHashGet(meta, "fields"), pkey ), "primitive" ), "number" ) )
4037                 dbi_conn_quote_string(writehandle, &id);
4038
4039         dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
4040
4041         if (!result) {
4042                 jsonObjectFree(obj);
4043                 obj = jsonNewObject(NULL);
4044                 osrfLogError(
4045                         OSRF_LOG_MARK,
4046                         "%s ERROR deleting %s object with %s = %s",
4047                         MODULENAME,
4048                         osrfHashGet(meta, "fieldmapper"),
4049                         pkey,
4050                         id
4051                 );
4052         }
4053
4054         free(id);
4055
4056         return obj;
4057
4058 }
4059
4060
4061 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
4062         if(!(result && meta)) return jsonNULL;
4063
4064         jsonObject* object = jsonNewObject(NULL);
4065         jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
4066
4067         osrfHash* fields = osrfHashGet(meta, "fields");
4068
4069         osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
4070
4071         osrfHash* _f;
4072         time_t _tmp_dt;
4073         char dt_string[256];
4074         struct tm gmdt;
4075
4076         int fmIndex;
4077         int columnIndex = 1;
4078         int attr;
4079         unsigned short type;
4080         const char* columnName;
4081
4082         /* cycle through the column list */
4083         while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4084
4085                 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4086
4087                 fmIndex = -1; // reset the position
4088                 
4089                 /* determine the field type and storage attributes */
4090                 type = dbi_result_get_field_type(result, columnName);
4091                 attr = dbi_result_get_field_attribs(result, columnName);
4092
4093                 /* fetch the fieldmapper index */
4094                 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
4095                         
4096                         if ( str_is_true( osrfHashGet(_f, "virtual") ) )
4097                                 continue;
4098                         
4099                         const char* pos = (char*)osrfHashGet(_f, "array_position");
4100                         if ( !pos ) continue;
4101
4102                         fmIndex = atoi( pos );
4103                         osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
4104                 } else {
4105                         continue;
4106                 }
4107
4108                 if (dbi_result_field_is_null(result, columnName)) {
4109                         jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
4110                 } else {
4111
4112                         switch( type ) {
4113
4114                                 case DBI_TYPE_INTEGER :
4115
4116                                         if( attr & DBI_INTEGER_SIZE8 ) 
4117                                                 jsonObjectSetIndex( object, fmIndex, 
4118                                                         jsonNewNumberObject(dbi_result_get_longlong(result, columnName)));
4119                                         else 
4120                                                 jsonObjectSetIndex( object, fmIndex, 
4121                                                         jsonNewNumberObject(dbi_result_get_int(result, columnName)));
4122
4123                                         break;
4124
4125                                 case DBI_TYPE_DECIMAL :
4126                                         jsonObjectSetIndex( object, fmIndex, 
4127                                                         jsonNewNumberObject(dbi_result_get_double(result, columnName)));
4128                                         break;
4129
4130                                 case DBI_TYPE_STRING :
4131
4132
4133                                         jsonObjectSetIndex(
4134                                                 object,
4135                                                 fmIndex,
4136                                                 jsonNewObject( dbi_result_get_string(result, columnName) )
4137                                         );
4138
4139                                         break;
4140
4141                                 case DBI_TYPE_DATETIME :
4142
4143                                         memset(dt_string, '\0', sizeof(dt_string));
4144                                         memset(&gmdt, '\0', sizeof(gmdt));
4145
4146                                         _tmp_dt = dbi_result_get_datetime(result, columnName);
4147
4148
4149                                         if (!(attr & DBI_DATETIME_DATE)) {
4150                                                 gmtime_r( &_tmp_dt, &gmdt );
4151                                                 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4152                                         } else if (!(attr & DBI_DATETIME_TIME)) {
4153                                                 localtime_r( &_tmp_dt, &gmdt );
4154                                                 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4155                                         } else {
4156                                                 localtime_r( &_tmp_dt, &gmdt );
4157                                                 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4158                                         }
4159
4160                                         jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
4161
4162                                         break;
4163
4164                                 case DBI_TYPE_BINARY :
4165                                         osrfLogError( OSRF_LOG_MARK, 
4166                                                 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4167                         }
4168                 }
4169         }
4170
4171         return object;
4172 }
4173
4174 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
4175         if(!result) return jsonNULL;
4176
4177         jsonObject* object = jsonNewObject(NULL);
4178
4179         time_t _tmp_dt;
4180         char dt_string[256];
4181         struct tm gmdt;
4182
4183         int fmIndex;
4184         int columnIndex = 1;
4185         int attr;
4186         unsigned short type;
4187         const char* columnName;
4188
4189         /* cycle through the column list */
4190         while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
4191
4192                 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
4193
4194                 fmIndex = -1; // reset the position
4195                 
4196                 /* determine the field type and storage attributes */
4197                 type = dbi_result_get_field_type(result, columnName);
4198                 attr = dbi_result_get_field_attribs(result, columnName);
4199
4200                 if (dbi_result_field_is_null(result, columnName)) {
4201                         jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
4202                 } else {
4203
4204                         switch( type ) {
4205
4206                                 case DBI_TYPE_INTEGER :
4207
4208                                         if( attr & DBI_INTEGER_SIZE8 ) 
4209                                                 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_longlong(result, columnName)) );
4210                                         else 
4211                                                 jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_int(result, columnName)) );
4212                                         break;
4213
4214                                 case DBI_TYPE_DECIMAL :
4215                                         jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_double(result, columnName)) );
4216                                         break;
4217
4218                                 case DBI_TYPE_STRING :
4219                                         jsonObjectSetKey( object, columnName, jsonNewObject(dbi_result_get_string(result, columnName)) );
4220                                         break;
4221
4222                                 case DBI_TYPE_DATETIME :
4223
4224                                         memset(dt_string, '\0', sizeof(dt_string));
4225                                         memset(&gmdt, '\0', sizeof(gmdt));
4226
4227                                         _tmp_dt = dbi_result_get_datetime(result, columnName);
4228
4229
4230                                         if (!(attr & DBI_DATETIME_DATE)) {
4231                                                 gmtime_r( &_tmp_dt, &gmdt );
4232                                                 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
4233                                         } else if (!(attr & DBI_DATETIME_TIME)) {
4234                                                 localtime_r( &_tmp_dt, &gmdt );
4235                                                 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
4236                                         } else {
4237                                                 localtime_r( &_tmp_dt, &gmdt );
4238                                                 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
4239                                         }
4240
4241                                         jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
4242                                         break;
4243
4244                                 case DBI_TYPE_BINARY :
4245                                         osrfLogError( OSRF_LOG_MARK, 
4246                                                 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);
4247                         }
4248                 }
4249         }
4250
4251         return object;
4252 }
4253
4254 // Interpret a string as true or false
4255 static int str_is_true( const char* str ) {
4256         if( NULL == str || strcasecmp( str, "true" ) )
4257                 return 0;
4258         else
4259                 return 1;
4260 }
4261
4262 // Interpret a jsonObject as true or false
4263 static int obj_is_true( const jsonObject* obj ) {
4264         if( !obj )
4265                 return 0;
4266         else switch( obj->type )
4267         {
4268                 case JSON_BOOL :
4269                         if( obj->value.b )
4270                                 return 1;
4271                         else
4272                                 return 0;
4273                 case JSON_STRING :
4274                         if( strcasecmp( obj->value.s, "true" ) )
4275                                 return 0;
4276                         else
4277                                 return 1;
4278                 case JSON_NUMBER :          // Support 1/0 for perl's sake
4279                         if( jsonObjectGetNumber( obj ) == 1.0 )
4280                                 return 1;
4281                         else
4282                                 return 0;
4283                 default :
4284                         return 0;
4285         }
4286 }
4287
4288 // Translate a numeric code into a text string identifying a type of
4289 // jsonObject.  To be used for building error messages.
4290 static const char* json_type( int code ) {
4291         switch ( code )
4292         {
4293                 case 0 :
4294                         return "JSON_HASH";
4295                 case 1 :
4296                         return "JSON_ARRAY";
4297                 case 2 :
4298                         return "JSON_STRING";
4299                 case 3 :
4300                         return "JSON_NUMBER";
4301                 case 4 :
4302                         return "JSON_NULL";
4303                 case 5 :
4304                         return "JSON_BOOL";
4305                 default :
4306                         return "(unrecognized)";
4307         }
4308 }