3 @brief As a server, perform database operations at the request of clients.
7 #include "opensrf/osrf_application.h"
8 #include "opensrf/osrf_settings.h"
9 #include "opensrf/osrf_message.h"
10 #include "opensrf/utils.h"
11 #include "opensrf/osrf_json.h"
12 #include "opensrf/log.h"
13 #include "openils/oils_utils.h"
22 # define MODULENAME "open-ils.reporter-store"
25 # define MODULENAME "open-ils.pcrud"
27 # define MODULENAME "open-ils.cstore"
31 // The next four macros are OR'd together as needed to form a set
32 // of bitflags. SUBCOMBO enables an extra pair of parentheses when
33 // nesting one UNION, INTERSECT or EXCEPT inside another.
34 // SUBSELECT tells us we're in a subquery, so don't add the
35 // terminal semicolon yet.
38 #define DISABLE_I18N 2
39 #define SELECT_DISTINCT 1
44 struct ClassInfoStruct;
45 typedef struct ClassInfoStruct ClassInfo;
47 #define ALIAS_STORE_SIZE 16
48 #define CLASS_NAME_STORE_SIZE 16
50 struct ClassInfoStruct {
54 osrfHash* class_def; // Points into IDL
55 osrfHash* fields; // Points into IDL
56 osrfHash* links; // Points into IDL
58 // The remaining members are private and internal. Client code should not
59 // access them directly.
61 ClassInfo* next; // Supports linked list of joined classes
62 int in_use; // boolean
64 // We usually store the alias and class name in the following arrays, and
65 // point the corresponding pointers at them. When the string is too big
66 // for the array (which will probably never happen in practice), we strdup it.
68 char alias_store[ ALIAS_STORE_SIZE + 1 ];
69 char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
72 struct QueryFrameStruct;
73 typedef struct QueryFrameStruct QueryFrame;
75 struct QueryFrameStruct {
77 ClassInfo* join_list; // linked list of classes joined to the core class
78 QueryFrame* next; // implements stack as linked list
79 int in_use; // boolean
82 int osrfAppChildInit();
83 int osrfAppInitialize();
84 void osrfAppChildExit();
86 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
88 static void setXactId( osrfMethodContext* ctx );
89 static inline const char* getXactId( osrfMethodContext* ctx );
90 static inline void clearXactId( osrfMethodContext* ctx );
92 int beginTransaction ( osrfMethodContext* );
93 int commitTransaction ( osrfMethodContext* );
94 int rollbackTransaction ( osrfMethodContext* );
96 int setSavepoint ( osrfMethodContext* );
97 int releaseSavepoint ( osrfMethodContext* );
98 int rollbackSavepoint ( osrfMethodContext* );
100 int doJSONSearch ( osrfMethodContext* );
102 int dispatchCRUDMethod ( osrfMethodContext* );
103 static jsonObject* doCreate ( osrfMethodContext*, int* );
104 static jsonObject* doRetrieve ( osrfMethodContext*, int* );
105 static jsonObject* doUpdate ( osrfMethodContext*, int* );
106 static jsonObject* doDelete ( osrfMethodContext*, int* );
107 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
108 jsonObject* where_hash, jsonObject* query_hash, int* err );
109 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
110 static jsonObject* oilsMakeJSONFromResult( dbi_result );
112 static char* searchSimplePredicate ( const char* op, const char* class_alias,
113 osrfHash* field, const jsonObject* node );
114 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
115 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject*);
116 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*, const char* );
117 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
118 static char* searchINPredicate ( const char*, osrfHash*,
119 jsonObject*, const char*, osrfMethodContext* );
120 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
121 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
122 static char* searchWHERE ( const jsonObject*, const ClassInfo*, int, osrfMethodContext* );
123 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
124 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
126 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
128 void userDataFree( void* );
129 static void sessionDataFree( char*, void* );
130 static char* getRelation( osrfHash* );
131 static int str_is_true( const char* str );
132 static int obj_is_true( const jsonObject* obj );
133 static const char* json_type( int code );
134 static const char* get_primitive( osrfHash* field );
135 static const char* get_datatype( osrfHash* field );
136 static int is_identifier( const char* s);
137 static int is_good_operator( const char* op );
138 static void pop_query_frame( void );
139 static void push_query_frame( void );
140 static int add_query_core( const char* alias, const char* class_name );
141 static inline ClassInfo* search_alias( const char* target );
142 static ClassInfo* search_all_alias( const char* target );
143 static ClassInfo* add_joined_class( const char* alias, const char* classname );
144 static void clear_query_stack( void );
147 static jsonObject* verifyUserPCRUD( osrfMethodContext* );
148 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
149 static char* org_tree_root( osrfMethodContext* ctx );
150 static jsonObject* single_hash( const char* key, const char* value );
153 static int child_initialized = 0; /* boolean */
155 static dbi_conn writehandle; /* our MASTER db connection */
156 static dbi_conn dbhandle; /* our CURRENT db connection */
157 //static osrfHash * readHandles;
158 static jsonObject* const jsonNULL = NULL; //
159 static int max_flesh_depth = 100;
161 // The following points to the top of a stack of QueryFrames. It's a little
162 // confusing because the top level of the query is at the bottom of the stack.
163 static QueryFrame* curr_query = NULL;
166 @brief Disconnect from the database.
168 This function is called when the server drone is about to terminate.
170 void osrfAppChildExit() {
171 osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
174 if (writehandle == dbhandle) same = 1;
176 dbi_conn_query(writehandle, "ROLLBACK;");
177 dbi_conn_close(writehandle);
180 if (dbhandle && !same)
181 dbi_conn_close(dbhandle);
183 // XXX add cleanup of readHandles whenever that gets used
189 @brief Initialize the application.
190 @return Zero if successful, or non-zero if not.
192 Load the IDL file into an internal data structure for future reference. Each non-virtual
193 class in the IDL corresponds to a table or view in the database, or to a subquery defined
194 in the IDL. Ignore all virtual tables and virtual fields.
196 Register a number of methods, some of them general-purpose and others specific for
199 The name of the application is given by the MODULENAME macro, whose value depends on
200 conditional compilation. The method names also incorporate MODULENAME, followed by a
201 dot, as a prefix. Some methods are registered or not registered depending on whether
202 the PCRUD macro is defined, and on whether the IDL includes permacrud entries for the
205 The general-purpose methods are as follows (minus their MODULENAME prefixes):
207 - json_query (not registered for PCRUD)
210 - transaction.rollback
215 For each non-virtual class, create up to eight class-specific methods:
217 - create (not for readonly classes)
219 - update (not for readonly classes)
220 - delete (not for readonly classes
221 - search (atomic and non-atomic versions)
222 - id_list (atomic and non-atomic versions)
224 For PCRUD, the full method names follow the pattern "MODULENAME.method_type.classname".
225 Otherwise they follow the pattern "MODULENAME.direct.XXX.method_type", where XXX is the
226 fieldmapper name from the IDL, with every run of one or more consecutive colons replaced
227 by a period. In addition, the names of atomic methods have a suffix of ".atomic".
229 This function is called when the registering the application, and is executed by the
230 listener before spawning the drones.
232 int osrfAppInitialize() {
234 osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
235 osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
237 if (!oilsIDLInit( osrf_settings_host_value("/IDL") ))
238 return 1; /* return non-zero to indicate error */
240 growing_buffer* method_name = buffer_init(64);
242 // Generic search thingy
243 buffer_add(method_name, MODULENAME);
244 buffer_add(method_name, ".json_query");
245 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
246 "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
249 // first we register all the transaction and savepoint methods
250 buffer_reset(method_name);
251 OSRF_BUFFER_ADD(method_name, MODULENAME);
252 OSRF_BUFFER_ADD(method_name, ".transaction.begin");
253 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
254 "beginTransaction", "", 0, 0 );
256 buffer_reset(method_name);
257 OSRF_BUFFER_ADD(method_name, MODULENAME);
258 OSRF_BUFFER_ADD(method_name, ".transaction.commit");
259 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
260 "commitTransaction", "", 0, 0 );
262 buffer_reset(method_name);
263 OSRF_BUFFER_ADD(method_name, MODULENAME);
264 OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
265 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
266 "rollbackTransaction", "", 0, 0 );
268 buffer_reset(method_name);
269 OSRF_BUFFER_ADD(method_name, MODULENAME);
270 OSRF_BUFFER_ADD(method_name, ".savepoint.set");
271 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
272 "setSavepoint", "", 1, 0 );
274 buffer_reset(method_name);
275 OSRF_BUFFER_ADD(method_name, MODULENAME);
276 OSRF_BUFFER_ADD(method_name, ".savepoint.release");
277 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
278 "releaseSavepoint", "", 1, 0 );
280 buffer_reset(method_name);
281 OSRF_BUFFER_ADD(method_name, MODULENAME);
282 OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
283 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
284 "rollbackSavepoint", "", 1, 0 );
286 static const char* global_method[] = {
294 const int global_method_count
295 = sizeof( global_method ) / sizeof ( global_method[0] );
297 unsigned long class_count = osrfHashGetCount( oilsIDL() );
298 osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
299 osrfLogDebug(OSRF_LOG_MARK,
300 "At most %lu methods will be generated",
301 (unsigned long) (class_count * global_method_count) );
303 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
304 osrfHash* idlClass = NULL;
306 // For each class in the IDL...
307 while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
309 const char* classname = osrfHashIteratorKey( class_itr );
310 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
312 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), MODULENAME )) {
313 osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on",
314 MODULENAME, classname);
318 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
319 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
323 // Look up some other attributes of the current class
324 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
325 if( !idlClass_fieldmapper ) {
326 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL",
332 // For PCRUD, ignore classes with no permacrud attribute
333 osrfHash* idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
334 if (!idlClass_permacrud) {
335 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no permacrud in IDL", classname );
339 const char* readonly = osrfHashGet(idlClass, "readonly");
342 for( i = 0; i < global_method_count; ++i ) { // for each global method
343 const char* method_type = global_method[ i ];
344 osrfLogDebug(OSRF_LOG_MARK,
345 "Using files to build %s class methods for %s", method_type, classname);
348 // Treat "id_list" or "search" as forms of "retrieve"
349 const char* tmp_method = method_type;
350 if ( *tmp_method == 'i' || *tmp_method == 's') { // "id_list" or "search"
351 tmp_method = "retrieve";
353 // Skip this method if there is no permacrud entry for it
354 if (!osrfHashGet( idlClass_permacrud, tmp_method ))
358 // No create, update, or delete methods for a readonly class
359 if ( str_is_true( readonly )
360 && ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd') )
363 buffer_reset( method_name );
365 // Build the method name
367 // For PCRUD: MODULENAME.method_type.classname
368 buffer_fadd(method_name, "%s.%s.%s", MODULENAME, method_type, classname);
370 // For non-PCRUD: MODULENAME.MODULENAME.direct.XXX.method_type
371 // where XXX is the fieldmapper name from the IDL, with every run of
372 // one or more consecutive colons replaced by a period.
375 char* _fm = strdup( idlClass_fieldmapper );
376 part = strtok_r(_fm, ":", &st_tmp);
378 buffer_fadd(method_name, "%s.direct.%s", MODULENAME, part);
380 while ((part = strtok_r(NULL, ":", &st_tmp))) {
381 OSRF_BUFFER_ADD_CHAR(method_name, '.');
382 OSRF_BUFFER_ADD(method_name, part);
384 OSRF_BUFFER_ADD_CHAR(method_name, '.');
385 OSRF_BUFFER_ADD(method_name, method_type);
389 // For an id_list or search method we specify the OSRF_METHOD_STREAMING option.
390 // The consequence is that we implicitly create an atomic method in addition to
391 // the usual non-atomic method.
393 if (*method_type == 'i' || *method_type == 's') { // id_list or search
394 flags = flags | OSRF_METHOD_STREAMING;
397 osrfHash* method_meta = osrfNewHash();
398 osrfHashSet( method_meta, idlClass, "class");
399 osrfHashSet( method_meta, buffer_data( method_name ), "methodname" );
400 osrfHashSet( method_meta, strdup(method_type), "methodtype" );
402 // Register the method, with a pointer to an osrfHash to tell the method
403 // its name, type, and class.
404 osrfAppRegisterExtendedMethod(
406 OSRF_BUFFER_C_STR( method_name ),
407 "dispatchCRUDMethod",
414 } // end for each global method
415 } // end for each class in IDL
417 buffer_free( method_name );
418 osrfHashIteratorFree( class_itr );
424 @brief Get a table name, view name, or subquery for use in a FROM clause.
425 @param class Pointer to the IDL class entry.
426 @return A table name, a view name, or a subquery in parentheses.
428 In some cases the IDL defines a class, not with a table name or a view name, but with
429 a SELECT statement, which may be used as a subquery.
431 static char* getRelation( osrfHash* class ) {
433 char* source_def = NULL;
434 const char* tabledef = osrfHashGet(class, "tablename");
437 source_def = strdup( tabledef ); // Return the name of a table or view
439 tabledef = osrfHashGet( class, "source_definition" );
441 // Return a subquery, enclosed in parentheses
442 source_def = safe_malloc( strlen( tabledef ) + 3 );
443 source_def[ 0 ] = '(';
444 strcpy( source_def + 1, tabledef );
445 strcat( source_def, ")" );
447 // Not found: return an error
448 const char* classname = osrfHashGet( class, "classname" );
453 "%s ERROR No tablename or source_definition for class \"%s\"",
464 @brief Initialize a server drone.
465 @return Zero if successful, -1 if not.
467 Connect to the database. For each non-virtual class in the IDL, execute a dummy "SELECT * "
468 query to get the datatype of each column. Record the datatypes in the loaded IDL.
470 This function is called by a server drone shortly after it is spawned by the listener.
472 int osrfAppChildInit() {
474 osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
475 dbi_initialize(NULL);
476 osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
478 char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
479 char* user = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
480 char* host = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
481 char* port = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
482 char* db = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
483 char* pw = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
484 char* md = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion",
487 osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
488 writehandle = dbi_conn_new(driver);
491 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
494 osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
496 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
497 "port=%s, user=%s, pw=%s, db=%s", MODULENAME, host, port, user, pw, db );
499 if(host) dbi_conn_set_option(writehandle, "host", host );
500 if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
501 if(user) dbi_conn_set_option(writehandle, "username", user);
502 if(pw) dbi_conn_set_option(writehandle, "password", pw );
503 if(db) dbi_conn_set_option(writehandle, "dbname", db );
505 if(md) max_flesh_depth = atoi(md);
506 if(max_flesh_depth < 0) max_flesh_depth = 1;
507 if(max_flesh_depth > 1000) max_flesh_depth = 1000;
516 if (dbi_conn_connect(writehandle) < 0) {
518 if (dbi_conn_connect(writehandle) < 0) {
519 dbi_conn_error(writehandle, &err);
520 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
525 osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
527 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
528 osrfHash* class = NULL;
529 growing_buffer* query_buf = buffer_init( 64 );
531 // For each class in the IDL...
532 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
533 const char* classname = osrfHashIteratorKey( class_itr );
534 osrfHash* fields = osrfHashGet( class, "fields" );
536 // If the class is virtual, ignore it
537 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
538 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
542 char* tabledef = getRelation(class);
544 continue; // No such relation -- a query of it would be doomed to failure
546 buffer_reset( query_buf );
547 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
551 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
552 MODULENAME, OSRF_BUFFER_C_STR( query_buf ) );
554 dbi_result result = dbi_conn_query( writehandle, OSRF_BUFFER_C_STR( query_buf ) );
558 const char* columnName;
560 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
562 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
565 /* fetch the fieldmapper index */
566 if( (_f = osrfHashGet(fields, columnName)) ) {
568 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
570 /* determine the field type and storage attributes */
572 switch( dbi_result_get_field_type_idx(result, columnIndex) ) {
574 case DBI_TYPE_INTEGER : {
576 if ( !osrfHashGet(_f, "primitive") )
577 osrfHashSet(_f, "number", "primitive");
579 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
580 if( attr & DBI_INTEGER_SIZE8 )
581 osrfHashSet(_f, "INT8", "datatype");
583 osrfHashSet(_f, "INT", "datatype");
586 case DBI_TYPE_DECIMAL :
587 if ( !osrfHashGet(_f, "primitive") )
588 osrfHashSet(_f, "number", "primitive");
590 osrfHashSet(_f,"NUMERIC", "datatype");
593 case DBI_TYPE_STRING :
594 if ( !osrfHashGet(_f, "primitive") )
595 osrfHashSet(_f,"string", "primitive");
597 osrfHashSet(_f,"TEXT", "datatype");
600 case DBI_TYPE_DATETIME :
601 if ( !osrfHashGet(_f, "primitive") )
602 osrfHashSet(_f,"string", "primitive");
604 osrfHashSet(_f,"TIMESTAMP", "datatype");
607 case DBI_TYPE_BINARY :
608 if ( !osrfHashGet(_f, "primitive") )
609 osrfHashSet(_f,"string", "primitive");
611 osrfHashSet(_f,"BYTEA", "datatype");
616 "Setting [%s] to primitive [%s] and datatype [%s]...",
618 osrfHashGet(_f, "primitive"),
619 osrfHashGet(_f, "datatype")
623 } // end while loop for traversing columns of result
624 dbi_result_free(result);
626 osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", classname);
628 } // end for each class in IDL
630 buffer_free( query_buf );
631 osrfHashIteratorFree( class_itr );
632 child_initialized = 1;
637 @brief Install a database driver.
638 @param conn Pointer to a database driver.
640 The driver is used to process quoted strings correctly.
642 This function is a sleazy hack, intended @em only for testing and debugging without
643 actually connecting to a database. Any real server process should initialize the
644 database connection by calling osrfAppChildInit().
646 void set_cstore_dbi_conn( dbi_conn conn ) {
647 dbhandle = writehandle = conn;
651 @brief Free an osrfHash that stores a transaction ID.
652 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
654 This function is a callback, to be called by the application session when it ends.
655 The application session stores the osrfHash via an opaque pointer.
657 If the osrfHash contains an entry for the key "xact_id", it means that an
658 uncommitted transaction is pending. Roll it back.
660 void userDataFree( void* blob ) {
661 osrfHash* hash = (osrfHash*) blob;
662 if( osrfHashGet( hash, "xact_id" ) && writehandle )
663 dbi_conn_query( writehandle, "ROLLBACK;" );
665 osrfHashFree( hash );
669 @name Managing session data
670 @brief Maintain data stored via the userData pointer of the application session.
672 Currently, session-level data is stored in an osrfHash. Other arrangements are
673 possible, and some would be more efficient. The application session calls a
674 callback function to free userData before terminating.
676 Currently, the only data we store at the session level is the transaction id. By this
677 means we can ensure that any pending transactions are rolled back before the application
683 @brief Free an item in the application session's userData.
684 @param key The name of a key for an osrfHash.
685 @param item An opaque pointer to the item associated with the key.
687 We store an osrfHash as userData with the application session, and arrange (by
688 installing userDataFree() as a different callback) for the session to free that
689 osrfHash before terminating.
691 This function is a callback for freeing items in the osrfHash. If the item has a key
692 of "xact_id", the item is a transaction id for a transaction that is still pending.
693 It is just a character string, so we free it.
695 Currently the transaction ID is the only thing that we store in this osrfHash. If we
696 ever store anything else in it, we will need to revisit this function so that it will
697 free whatever else needs freeing (which may or may not be just a character string).
699 static void sessionDataFree( char* key, void* item ) {
700 if ( !strcmp(key,"xact_id") ) {
706 @brief Save a transaction id.
707 @param ctx Pointer to the method context.
709 Save the session_id of the current application session as a transaction id.
711 static void setXactId( osrfMethodContext* ctx ) {
712 if( ctx && ctx->session ) {
713 osrfAppSession* session = ctx->session;
715 // If the session doesn't already have a hash, create one. Make sure
716 // that the application session frees the hash when it terminates.
717 if( NULL == session->userData ) {
718 session->userData = osrfNewHash();
719 osrfHashSetCallback( (osrfHash*) session->userData, &sessionDataFree );
720 ctx->session->userDataFree = &userDataFree;
723 // Save the transaction id in the hash, with the key "xact_id"
724 osrfHashSet( (osrfHash*) session->userData, strdup( session->session_id ),
730 @brief Get the transaction ID for the current transaction, if any.
731 @param ctx Pointer to the method context.
732 @return Pointer to the transaction ID.
734 The return value points to an internal buffer, and will become invalid upon issuing
735 a commit or rollback.
737 static inline const char* getXactId( osrfMethodContext* ctx ) {
738 if( ctx && ctx->session && ctx->session->userData )
739 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
745 @brief Clear the current transaction id.
746 @param ctx Pointer to the method context.
748 static inline void clearXactId( osrfMethodContext* ctx ) {
749 if( ctx && ctx->session && ctx->session->userData )
750 osrfHashRemove( ctx->session->userData, "xact_id" );
755 @brief Implement the transaction.begin method.
756 @param ctx Pointer to the method context.
757 @return Zero if successful, or -1 upon error.
759 Start a transaction. Save a transaction ID for future reference.
762 - authkey (PCRUD only)
764 Return to client: Transaction ID
766 int beginTransaction ( osrfMethodContext* ctx ) {
767 if(osrfMethodVerifyContext( ctx )) {
768 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
773 jsonObject* user = verifyUserPCRUD( ctx );
776 jsonObjectFree(user);
779 dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
781 osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
782 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
783 "osrfMethodException", ctx->request, "Error starting transaction" );
787 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
788 osrfAppRespondComplete( ctx, ret );
795 @brief Implement the savepoint.set method.
796 @param ctx Pointer to the method context.
797 @return Zero if successful, or -1 if not.
799 Issue a SAVEPOINT to the database server.
802 - authkey (PCRUD only)
805 Return to client: Savepoint name
807 int setSavepoint ( osrfMethodContext* ctx ) {
808 if(osrfMethodVerifyContext( ctx )) {
809 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
816 jsonObject* user = verifyUserPCRUD( ctx );
819 jsonObjectFree(user);
822 // Verify that a transaction is pending
823 const char* trans_id = getXactId( ctx );
824 if( NULL == trans_id ) {
825 osrfAppSessionStatus(
827 OSRF_STATUS_INTERNALSERVERERROR,
828 "osrfMethodException",
830 "No active transaction -- required for savepoints"
835 // Get the savepoint name from the method params
836 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
838 dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
842 "%s: Error creating savepoint %s in transaction %s",
847 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
848 "osrfMethodException", ctx->request, "Error creating savepoint" );
851 jsonObject* ret = jsonNewObject(spName);
852 osrfAppRespondComplete( ctx, ret );
859 @brief Implement the savepoint.release method.
860 @param ctx Pointer to the method context.
861 @return Zero if successful, or -1 if not.
863 Issue a RELEASE SAVEPOINT to the database server.
866 - authkey (PCRUD only)
869 Return to client: Savepoint name
871 int releaseSavepoint ( osrfMethodContext* ctx ) {
872 if(osrfMethodVerifyContext( ctx )) {
873 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
880 jsonObject* user = verifyUserPCRUD( ctx );
883 jsonObjectFree(user);
886 // Verify that a transaction is pending
887 const char* trans_id = getXactId( ctx );
888 if( NULL == trans_id ) {
889 osrfAppSessionStatus(
891 OSRF_STATUS_INTERNALSERVERERROR,
892 "osrfMethodException",
894 "No active transaction -- required for savepoints"
899 // Get the savepoint name from the method params
900 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
902 dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
906 "%s: Error releasing savepoint %s in transaction %s",
911 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
912 "osrfMethodException", ctx->request, "Error releasing savepoint" );
915 jsonObject* ret = jsonNewObject(spName);
916 osrfAppRespondComplete( ctx, ret );
923 @brief Implement the savepoint.rollback method.
924 @param ctx Pointer to the method context.
925 @return Zero if successful, or -1 if not.
927 Issue a ROLLBACK TO SAVEPOINT to the database server.
930 - authkey (PCRUD only)
933 Return to client: Savepoint name
935 int rollbackSavepoint ( osrfMethodContext* ctx ) {
936 if(osrfMethodVerifyContext( ctx )) {
937 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
944 jsonObject* user = verifyUserPCRUD( ctx );
947 jsonObjectFree(user);
950 // Verify that a transaction is pending
951 const char* trans_id = getXactId( ctx );
952 if( NULL == trans_id ) {
953 osrfAppSessionStatus(
955 OSRF_STATUS_INTERNALSERVERERROR,
956 "osrfMethodException",
958 "No active transaction -- required for savepoints"
963 // Get the savepoint name from the method params
964 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
966 dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
970 "%s: Error rolling back savepoint %s in transaction %s",
975 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
976 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
979 jsonObject* ret = jsonNewObject(spName);
980 osrfAppRespondComplete( ctx, ret );
987 @brief Implement the transaction.commit method.
988 @param ctx Pointer to the method context.
989 @return Zero if successful, or -1 if not.
991 Issue a COMMIT to the database server.
994 - authkey (PCRUD only)
996 Return to client: Transaction ID.
998 int commitTransaction ( osrfMethodContext* ctx ) {
999 if(osrfMethodVerifyContext( ctx )) {
1000 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1005 jsonObject* user = verifyUserPCRUD( ctx );
1008 jsonObjectFree(user);
1011 // Verify that a transaction is pending
1012 const char* trans_id = getXactId( ctx );
1013 if( NULL == trans_id ) {
1014 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1015 "osrfMethodException", ctx->request, "No active transaction to commit" );
1019 dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
1021 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
1022 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1023 "osrfMethodException", ctx->request, "Error committing transaction" );
1026 jsonObject* ret = jsonNewObject( trans_id );
1027 osrfAppRespondComplete( ctx, ret );
1028 jsonObjectFree(ret);
1035 @brief Implement the transaction.rollback method.
1036 @param ctx Pointer to the method context.
1037 @return Zero if successful, or -1 if not.
1039 Issue a ROLLBACK to the database server.
1042 - authkey (PCRUD only)
1044 Return to client: Transaction ID
1046 int rollbackTransaction ( osrfMethodContext* ctx ) {
1047 if(osrfMethodVerifyContext( ctx )) {
1048 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1053 jsonObject* user = verifyUserPCRUD( ctx );
1056 jsonObjectFree(user);
1059 // Verify that a transaction is pending
1060 const char* trans_id = getXactId( ctx );
1061 if( NULL == trans_id ) {
1062 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1063 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1067 dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
1069 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
1070 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1071 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1074 jsonObject* ret = jsonNewObject( trans_id );
1075 osrfAppRespondComplete( ctx, ret );
1076 jsonObjectFree(ret);
1083 @brief Implement the class-specific methods.
1084 @param ctx Pointer to the method context.
1085 @return Zero if successful, or -1 if not.
1087 Branch on the method type: create, retrieve, update, delete, search, and id_list.
1089 The method parameters and the type of value returned to the client depend on the method
1090 type. However, for PCRUD methods, the first method parameter should always be an
1093 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
1094 if(osrfMethodVerifyContext( ctx )) {
1095 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1099 int err = 0; // to be returned to caller
1100 jsonObject * obj = NULL; // to be returned to client
1102 // Get the method type so that we can branch on it
1103 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1104 const char* methodtype = osrfHashGet( method_meta, "methodtype" );
1106 if (!strcmp(methodtype, "create")) {
1107 obj = doCreate(ctx, &err);
1108 osrfAppRespondComplete( ctx, obj );
1110 else if (!strcmp(methodtype, "retrieve")) {
1111 obj = doRetrieve(ctx, &err);
1112 osrfAppRespondComplete( ctx, obj );
1114 else if (!strcmp(methodtype, "update")) {
1115 obj = doUpdate(ctx, &err);
1116 osrfAppRespondComplete( ctx, obj );
1118 else if (!strcmp(methodtype, "delete")) {
1119 obj = doDelete(ctx, &err);
1120 osrfAppRespondComplete( ctx, obj );
1122 else if (!strcmp(methodtype, "search")) {
1124 // Implement search method: return rows of the specified class that satisfy
1125 // a specified WHERE clause.
1127 // Method parameters:
1128 // - authkey (PCRUD only)
1129 // - WHERE clause, as jsonObject
1130 // - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1132 jsonObject* where_clause;
1133 jsonObject* rest_of_query;
1136 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1137 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1139 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1140 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1144 osrfHash* class_obj = osrfHashGet( method_meta, "class" );
1145 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
1150 // Return each row to the client (except that some may be suppressed by PCRUD)
1151 jsonObject* cur = 0;
1152 unsigned long res_idx = 0;
1153 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1155 if(!verifyObjectPCRUD(ctx, cur))
1158 osrfAppRespond( ctx, cur );
1160 osrfAppRespondComplete( ctx, NULL );
1162 } else if (!strcmp(methodtype, "id_list")) {
1164 // Implement the id_list method. Return the primary key values for all rows of the
1165 // relevant class that satisfy a specified WHERE clause. This method relies on the
1166 // assumption that every class has a primary key consisting of a single column.
1168 // Method parameters:
1169 // - authkey (PCRUD only)
1170 // - WHERE clause, as jsonObject
1171 // - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1173 jsonObject* where_clause;
1174 jsonObject* rest_of_query;
1176 // We use the where clause without change. But we need to massage the rest of the
1177 // query, so we work with a copy of it instead of modifying the original.
1180 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1181 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1183 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1184 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1187 // Eliminate certain SQL clauses, if present
1188 if ( rest_of_query ) {
1189 jsonObjectRemoveKey( rest_of_query, "select" );
1190 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1191 jsonObjectRemoveKey( rest_of_query, "flesh" );
1192 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
1194 rest_of_query = jsonNewObjectType( JSON_HASH );
1197 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1199 // Get the class metadata
1200 osrfHash* class_obj = osrfHashGet( method_meta, "class" );
1202 // Build a SELECT list containing just the primary key,
1203 // i.e. like { "classname":["keyname"] }
1204 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1205 jsonObjectPush( col_list_obj, // Load array with name of primary key
1206 jsonNewObject( osrfHashGet( class_obj, "primarykey" ) ) );
1207 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1208 jsonObjectSetKey( select_clause, osrfHashGet( class_obj, "classname" ), col_list_obj );
1210 jsonObjectSetKey( rest_of_query, "select", select_clause );
1213 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
1215 jsonObjectFree( rest_of_query );
1219 // Return each primary key value to the client
1221 unsigned long res_idx = 0;
1222 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1224 if(!verifyObjectPCRUD(ctx, cur))
1227 osrfAppRespond( ctx,
1228 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
1231 osrfAppRespondComplete( ctx, NULL );
1234 osrfAppRespondComplete( ctx, obj ); // should be unreachable...
1237 jsonObjectFree(obj);
1243 @brief Verify that we have a valid class reference.
1244 @param ctx Pointer to the method context.
1245 @param param Pointer to the method parameters.
1246 @return 1 if the class reference is valid, or zero if it isn't.
1248 The class of the method params must match the class to which the method id devoted.
1249 For PCRUD there are additional restrictions.
1251 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1253 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1254 osrfHash* class = osrfHashGet( method_meta, "class" );
1256 // Compare the method's class to the parameters' class
1257 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1259 // Oops -- they don't match. Complain.
1260 growing_buffer* msg = buffer_init(128);
1263 "%s: %s method for type %s was passed a %s",
1265 osrfHashGet(method_meta, "methodtype"),
1266 osrfHashGet(class, "classname"),
1267 param->classname ? param->classname : "(null)"
1270 char* m = buffer_release(msg);
1271 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1280 ret = verifyObjectPCRUD( ctx, param );
1289 @brief (PCRUD only) Verify that the user is properly logged in.
1290 @param ctx Pointer to the method context.
1291 @return If the user is logged in, a pointer to the user object from the authentication
1292 server; otherwise NULL.
1294 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1296 // Get the authkey (the first method parameter)
1297 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1298 jsonObject* auth_object = jsonNewObject(auth);
1300 // Fetch the user object from the authentication server
1301 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1303 jsonObjectFree(auth_object);
1305 if (!user->classname || strcmp(user->classname, "au")) {
1307 growing_buffer* msg = buffer_init(128);
1310 "%s: permacrud received a bad auth token: %s",
1315 char* m = buffer_release(msg);
1316 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1320 jsonObjectFree(user);
1327 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1329 dbhandle = writehandle;
1331 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1332 osrfHash* class = osrfHashGet( method_metadata, "class" );
1333 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1336 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
1337 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1338 } else if ( *method_type == 'u' || *method_type == 'd' ) {
1339 fetch = 1; // MUST go to the db for the object for update and delete
1342 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
1345 // No permacrud for this method type on this class
1347 growing_buffer* msg = buffer_init(128);
1350 "%s: %s on class %s has no permacrud IDL entry",
1352 osrfHashGet(method_metadata, "methodtype"),
1353 osrfHashGet(class, "classname")
1356 char* m = buffer_release(msg);
1357 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1358 "osrfMethodException", ctx->request, m );
1365 jsonObject* user = verifyUserPCRUD( ctx );
1369 int userid = atoi( oilsFMGetString( user, "id" ) );
1370 jsonObjectFree(user);
1372 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
1373 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
1374 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
1376 osrfStringArray* context_org_array = osrfNewStringArray(1);
1379 char* pkey_value = NULL;
1380 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1381 osrfLogDebug( OSRF_LOG_MARK,
1382 "global-level permissions required, fetching top of the org tree" );
1384 // check for perm at top of org tree
1385 char* org_tree_root_id = org_tree_root( ctx );
1386 if( org_tree_root_id ) {
1387 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1388 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1390 osrfStringArrayFree( context_org_array );
1395 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1396 "fetching context org ids" );
1397 const char* pkey = osrfHashGet(class, "primarykey");
1398 jsonObject *param = NULL;
1400 if (obj->classname) {
1401 pkey_value = oilsFMGetString( obj, pkey );
1403 param = jsonObjectClone(obj);
1404 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1407 pkey_value = jsonObjectToSimpleString( obj );
1409 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1410 "of %s and retrieving from the database", pkey_value );
1414 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1415 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1416 jsonObjectFree(_tmp_params);
1418 param = jsonObjectExtractIndex(_list, 0);
1419 jsonObjectFree(_list);
1423 osrfLogDebug( OSRF_LOG_MARK,
1424 "Object not found in the database with primary key %s of %s",
1427 growing_buffer* msg = buffer_init(128);
1430 "%s: no object found with primary key %s of %s",
1436 char* m = buffer_release(msg);
1437 osrfAppSessionStatus(
1439 OSRF_STATUS_INTERNALSERVERERROR,
1440 "osrfMethodException",
1446 if (pkey_value) free(pkey_value);
1451 if (local_context->size > 0) {
1452 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1453 local_context->size);
1455 const char* lcontext = NULL;
1456 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1457 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1460 "adding class-local field %s (value: %s) to the context org list",
1462 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1467 if (foreign_context) {
1468 unsigned long class_count = osrfHashGetCount( foreign_context );
1469 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count);
1471 if (class_count > 0) {
1473 osrfHash* fcontext = NULL;
1474 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1475 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1476 const char* class_name = osrfHashIteratorKey( class_itr );
1477 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1481 "%d foreign context fields(s) specified for class %s",
1482 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1486 char* foreign_pkey = osrfHashGet(fcontext, "field");
1487 char* foreign_pkey_value =
1488 oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1490 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1492 jsonObject* _list = doFieldmapperSearch(
1493 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1495 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1496 jsonObjectFree(_tmp_params);
1497 jsonObjectFree(_list);
1499 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1501 if (_fparam && jump_list) {
1502 const char* flink = NULL;
1504 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1505 free(foreign_pkey_value);
1507 osrfHash* foreign_link_hash =
1508 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1510 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1511 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1513 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1515 _list = doFieldmapperSearch(
1517 osrfHashGet( oilsIDL(),
1518 osrfHashGet( foreign_link_hash, "class" ) ),
1524 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1525 jsonObjectFree(_tmp_params);
1526 jsonObjectFree(_list);
1532 growing_buffer* msg = buffer_init(128);
1535 "%s: no object found with primary key %s of %s",
1541 char* m = buffer_release(msg);
1542 osrfAppSessionStatus(
1544 OSRF_STATUS_INTERNALSERVERERROR,
1545 "osrfMethodException",
1551 osrfHashIteratorFree(class_itr);
1552 free(foreign_pkey_value);
1553 jsonObjectFree(param);
1558 free(foreign_pkey_value);
1561 const char* foreign_field = NULL;
1562 while ( (foreign_field = osrfStringArrayGetString(
1563 osrfHashGet(fcontext,"context" ), j++)) ) {
1564 osrfStringArrayAdd( context_org_array,
1565 oilsFMGetString( _fparam, foreign_field ) );
1568 "adding foreign class %s field %s (value: %s) to the context org list",
1571 osrfStringArrayGetString(
1572 context_org_array, context_org_array->size - 1)
1576 jsonObjectFree(_fparam);
1579 osrfHashIteratorFree( class_itr );
1583 jsonObjectFree(param);
1586 const char* context_org = NULL;
1587 const char* perm = NULL;
1590 if (permission->size == 0) {
1591 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1596 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1598 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1604 "Checking object permission [%s] for user %d "
1605 "on object %s (class %s) at org %d",
1609 osrfHashGet(class, "classname"),
1613 result = dbi_conn_queryf(
1615 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1618 osrfHashGet(class, "classname"),
1626 "Received a result for object permission [%s] "
1627 "for user %d on object %s (class %s) at org %d",
1631 osrfHashGet(class, "classname"),
1635 if (dbi_result_first_row(result)) {
1636 jsonObject* return_val = oilsMakeJSONFromResult( result );
1637 const char* has_perm = jsonObjectGetString(
1638 jsonObjectGetKeyConst(return_val, "has_perm") );
1642 "Status of object permission [%s] for user %d "
1643 "on object %s (class %s) at org %d is %s",
1647 osrfHashGet(class, "classname"),
1652 if ( *has_perm == 't' ) OK = 1;
1653 jsonObjectFree(return_val);
1656 dbi_result_free(result);
1662 osrfLogDebug( OSRF_LOG_MARK,
1663 "Checking non-object permission [%s] for user %d at org %d",
1664 perm, userid, atoi(context_org) );
1665 result = dbi_conn_queryf(
1667 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1674 osrfLogDebug( OSRF_LOG_MARK,
1675 "Received a result for permission [%s] for user %d at org %d",
1676 perm, userid, atoi(context_org) );
1677 if ( dbi_result_first_row(result) ) {
1678 jsonObject* return_val = oilsMakeJSONFromResult( result );
1679 const char* has_perm = jsonObjectGetString(
1680 jsonObjectGetKeyConst(return_val, "has_perm") );
1681 osrfLogDebug( OSRF_LOG_MARK,
1682 "Status of permission [%s] for user %d at org %d is [%s]",
1683 perm, userid, atoi(context_org), has_perm );
1684 if ( *has_perm == 't' )
1686 jsonObjectFree(return_val);
1689 dbi_result_free(result);
1698 if (pkey_value) free(pkey_value);
1699 osrfStringArrayFree(context_org_array);
1705 @brief Look up the root of the org_unit tree.
1706 @param ctx Pointer to the method context.
1707 @return The id of the root org unit, as a character string.
1709 Query actor.org_unit where parent_ou is null, and return the id as a string.
1711 This function assumes that there is only one root org unit, i.e. that we
1712 have a single tree, not a forest.
1714 The calling code is responsible for freeing the returned string.
1716 static char* org_tree_root( osrfMethodContext* ctx ) {
1718 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1719 static time_t last_lookup_time = 0;
1720 time_t current_time = time( NULL );
1722 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1723 // We successfully looked this up less than an hour ago.
1724 // It's not likely to have changed since then.
1725 return strdup( cached_root_id );
1727 last_lookup_time = current_time;
1730 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1731 jsonObject* result = doFieldmapperSearch(
1732 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1733 jsonObjectFree( where_clause );
1735 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1738 jsonObjectFree( result );
1740 growing_buffer* msg = buffer_init(128);
1741 OSRF_BUFFER_ADD( msg, MODULENAME );
1742 OSRF_BUFFER_ADD( msg,
1743 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1745 char* m = buffer_release(msg);
1746 osrfAppSessionStatus( ctx->session,
1747 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1750 cached_root_id[ 0 ] = '\0';
1754 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1755 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1757 jsonObjectFree( result );
1759 strcpy( cached_root_id, root_org_unit_id );
1760 return root_org_unit_id;
1764 @brief Create a JSON_HASH with a single key/value pair.
1765 @param key The key of the key/value pair.
1766 @param value the value of the key/value pair.
1767 @return Pointer to a newly created jsonObject of type JSON_HASH.
1769 The value of the key/value is either a string or (if @a value is NULL) a null.
1771 static jsonObject* single_hash( const char* key, const char* value ) {
1773 if( ! key ) key = "";
1775 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1776 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1782 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1784 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1786 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1787 jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1789 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1790 jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1793 if (!verifyObjectClass(ctx, target)) {
1798 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1800 const char* trans_id = getXactId( ctx );
1802 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1804 osrfAppSessionStatus(
1806 OSRF_STATUS_BADREQUEST,
1807 "osrfMethodException",
1809 "No active transaction -- required for CREATE"
1815 // The following test is harmless but redundant. If a class is
1816 // readonly, we don't register a create method for it.
1817 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1818 osrfAppSessionStatus(
1820 OSRF_STATUS_BADREQUEST,
1821 "osrfMethodException",
1823 "Cannot INSERT readonly class"
1829 // Set the last_xact_id
1830 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1832 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1833 trans_id, target->classname, index);
1834 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1837 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1839 dbhandle = writehandle;
1841 osrfHash* fields = osrfHashGet(meta, "fields");
1842 char* pkey = osrfHashGet(meta, "primarykey");
1843 char* seq = osrfHashGet(meta, "sequence");
1845 growing_buffer* table_buf = buffer_init(128);
1846 growing_buffer* col_buf = buffer_init(128);
1847 growing_buffer* val_buf = buffer_init(128);
1849 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1850 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1851 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1852 buffer_add(val_buf,"VALUES (");
1856 osrfHash* field = NULL;
1857 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1858 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1860 const char* field_name = osrfHashIteratorKey( field_itr );
1862 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1865 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1868 if (field_object && field_object->classname) {
1869 value = oilsFMGetString(
1871 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1873 } else if( field_object && JSON_BOOL == field_object->type ) {
1874 if( jsonBoolIsTrue( field_object ) )
1875 value = strdup( "t" );
1877 value = strdup( "f" );
1879 value = jsonObjectToSimpleString( field_object );
1885 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1886 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1889 buffer_add(col_buf, field_name);
1891 if (!field_object || field_object->type == JSON_NULL) {
1892 buffer_add( val_buf, "DEFAULT" );
1894 } else if ( !strcmp(get_primitive( field ), "number") ) {
1895 const char* numtype = get_datatype( field );
1896 if ( !strcmp( numtype, "INT8") ) {
1897 buffer_fadd( val_buf, "%lld", atoll(value) );
1899 } else if ( !strcmp( numtype, "INT") ) {
1900 buffer_fadd( val_buf, "%d", atoi(value) );
1902 } else if ( !strcmp( numtype, "NUMERIC") ) {
1903 buffer_fadd( val_buf, "%f", atof(value) );
1906 if ( dbi_conn_quote_string(writehandle, &value) ) {
1907 OSRF_BUFFER_ADD( val_buf, value );
1910 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1911 osrfAppSessionStatus(
1913 OSRF_STATUS_INTERNALSERVERERROR,
1914 "osrfMethodException",
1916 "Error quoting string -- please see the error log for more details"
1919 buffer_free(table_buf);
1920 buffer_free(col_buf);
1921 buffer_free(val_buf);
1931 osrfHashIteratorFree( field_itr );
1933 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1934 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1936 char* table_str = buffer_release(table_buf);
1937 char* col_str = buffer_release(col_buf);
1938 char* val_str = buffer_release(val_buf);
1939 growing_buffer* sql = buffer_init(128);
1940 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1945 char* query = buffer_release(sql);
1947 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1950 dbi_result result = dbi_conn_query(writehandle, query);
1952 jsonObject* obj = NULL;
1955 obj = jsonNewObject(NULL);
1958 "%s ERROR inserting %s object using query [%s]",
1960 osrfHashGet(meta, "fieldmapper"),
1963 osrfAppSessionStatus(
1965 OSRF_STATUS_INTERNALSERVERERROR,
1966 "osrfMethodException",
1968 "INSERT error -- please see the error log for more details"
1973 char* id = oilsFMGetString(target, pkey);
1975 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1976 growing_buffer* _id = buffer_init(10);
1977 buffer_fadd(_id, "%lld", new_id);
1978 id = buffer_release(_id);
1981 // Find quietness specification, if present
1982 const char* quiet_str = NULL;
1984 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1986 quiet_str = jsonObjectGetString( quiet_obj );
1989 if( str_is_true( quiet_str ) ) { // if quietness is specified
1990 obj = jsonNewObject(id);
1994 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1995 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1997 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1999 jsonObjectFree( where_clause );
2004 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
2007 jsonObjectFree( list );
2020 @brief Implement the retrieve method.
2021 @param ctx Pointer to the method context.
2022 @param err Pointer through which to return an error code.
2023 @return If successful, a pointer to the result to be returned to the client;
2026 From the method's class, fetch a row with a specified value in the primary key. This
2027 method relies on the database design convention that a primary key consists of a single
2031 - authkey (PCRUD only)
2032 - value of the primary key for the desired row, for building the WHERE clause
2033 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2035 Return to client: One row from the query.
2037 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
2047 // Get the class metadata
2048 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2050 // Get the value of the primary key, from a method parameter
2051 const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos);
2055 "%s retrieving %s object with primary key value of %s",
2057 osrfHashGet( class_def, "fieldmapper" ),
2058 jsonObjectGetString( id_obj )
2061 // Build a WHERE clause based on the key value
2062 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2065 osrfHashGet( class_def, "primarykey" ), // name of key column
2066 jsonObjectClone( id_obj ) // value of key column
2069 jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
2072 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
2074 jsonObjectFree( where_clause );
2078 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2079 jsonObjectFree( list );
2082 if(!verifyObjectPCRUD(ctx, obj)) {
2083 jsonObjectFree(obj);
2086 growing_buffer* msg = buffer_init(128);
2087 OSRF_BUFFER_ADD( msg, MODULENAME );
2088 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2090 char* m = buffer_release(msg);
2091 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2103 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
2104 growing_buffer* val_buf = buffer_init(32);
2105 const char* numtype = get_datatype( field );
2107 if ( !strncmp( numtype, "INT", 3 ) ) {
2108 if (value->type == JSON_NUMBER)
2109 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2110 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2112 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2115 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
2116 if (value->type == JSON_NUMBER)
2117 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2119 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2123 // Presumably this was really intended ot be a string, so quote it
2124 char* str = jsonObjectToSimpleString( value );
2125 if ( dbi_conn_quote_string(dbhandle, &str) ) {
2126 OSRF_BUFFER_ADD( val_buf, str );
2129 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
2131 buffer_free(val_buf);
2136 return buffer_release(val_buf);
2139 static char* searchINPredicate (const char* class_alias, osrfHash* field,
2140 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2141 growing_buffer* sql_buf = buffer_init(32);
2147 osrfHashGet(field, "name")
2151 buffer_add(sql_buf, "IN (");
2152 } else if (!(strcasecmp(op,"not in"))) {
2153 buffer_add(sql_buf, "NOT IN (");
2155 buffer_add(sql_buf, "IN (");
2158 if (node->type == JSON_HASH) {
2159 // subquery predicate
2160 char* subpred = buildQuery( ctx, node, SUBSELECT );
2162 buffer_free( sql_buf );
2166 buffer_add(sql_buf, subpred);
2169 } else if (node->type == JSON_ARRAY) {
2170 // literal value list
2171 int in_item_index = 0;
2172 int in_item_first = 1;
2173 const jsonObject* in_item;
2174 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
2179 buffer_add(sql_buf, ", ");
2182 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2183 osrfLogError( OSRF_LOG_MARK,
2184 "%s: Expected string or number within IN list; found %s",
2185 MODULENAME, json_type( in_item->type ) );
2186 buffer_free(sql_buf);
2190 // Append the literal value -- quoted if not a number
2191 if ( JSON_NUMBER == in_item->type ) {
2192 char* val = jsonNumberToDBString( field, in_item );
2193 OSRF_BUFFER_ADD( sql_buf, val );
2196 } else if ( !strcmp( get_primitive( field ), "number") ) {
2197 char* val = jsonNumberToDBString( field, in_item );
2198 OSRF_BUFFER_ADD( sql_buf, val );
2202 char* key_string = jsonObjectToSimpleString(in_item);
2203 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
2204 OSRF_BUFFER_ADD( sql_buf, key_string );
2207 osrfLogError(OSRF_LOG_MARK,
2208 "%s: Error quoting key string [%s]", MODULENAME, key_string);
2210 buffer_free(sql_buf);
2216 if( in_item_first ) {
2217 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
2218 buffer_free( sql_buf );
2222 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2223 MODULENAME, json_type( node->type ) );
2224 buffer_free(sql_buf);
2228 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2230 return buffer_release(sql_buf);
2233 // Receive a JSON_ARRAY representing a function call. The first
2234 // entry in the array is the function name. The rest are parameters.
2235 static char* searchValueTransform( const jsonObject* array ) {
2237 if( array->size < 1 ) {
2238 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
2242 // Get the function name
2243 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2244 if( func_item->type != JSON_STRING ) {
2245 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2246 MODULENAME, json_type( func_item->type ) );
2250 growing_buffer* sql_buf = buffer_init(32);
2252 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2253 OSRF_BUFFER_ADD( sql_buf, "( " );
2255 // Get the parameters
2256 int func_item_index = 1; // We already grabbed the zeroth entry
2257 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2259 // Add a separator comma, if we need one
2260 if( func_item_index > 2 )
2261 buffer_add( sql_buf, ", " );
2263 // Add the current parameter
2264 if (func_item->type == JSON_NULL) {
2265 buffer_add( sql_buf, "NULL" );
2267 char* val = jsonObjectToSimpleString(func_item);
2268 if ( dbi_conn_quote_string(dbhandle, &val) ) {
2269 OSRF_BUFFER_ADD( sql_buf, val );
2272 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
2273 buffer_free(sql_buf);
2280 buffer_add( sql_buf, " )" );
2282 return buffer_release(sql_buf);
2285 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
2286 const jsonObject* node, const char* op) {
2288 if( ! is_good_operator( op ) ) {
2289 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2293 char* val = searchValueTransform(node);
2297 growing_buffer* sql_buf = buffer_init(32);
2302 osrfHashGet(field, "name"),
2309 return buffer_release(sql_buf);
2312 // class_alias is a class name or other table alias
2313 // field is a field definition as stored in the IDL
2314 // node comes from the method parameter, and may represent an entry in the SELECT list
2315 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
2316 growing_buffer* sql_buf = buffer_init(32);
2318 const char* field_transform = jsonObjectGetString(
2319 jsonObjectGetKeyConst( node, "transform" ) );
2320 const char* transform_subcolumn = jsonObjectGetString(
2321 jsonObjectGetKeyConst( node, "result_field" ) );
2323 if(transform_subcolumn) {
2324 if( ! is_identifier( transform_subcolumn ) ) {
2325 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2326 MODULENAME, transform_subcolumn );
2327 buffer_free( sql_buf );
2330 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2333 if (field_transform) {
2335 if( ! is_identifier( field_transform ) ) {
2336 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2337 MODULENAME, field_transform );
2338 buffer_free( sql_buf );
2342 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2343 field_transform, class_alias, osrfHashGet(field, "name"));
2344 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2347 if( array->type != JSON_ARRAY ) {
2348 osrfLogError( OSRF_LOG_MARK,
2349 "%s: Expected JSON_ARRAY for function params; found %s",
2350 MODULENAME, json_type( array->type ) );
2351 buffer_free( sql_buf );
2354 int func_item_index = 0;
2355 jsonObject* func_item;
2356 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2358 char* val = jsonObjectToSimpleString(func_item);
2361 buffer_add( sql_buf, ",NULL" );
2362 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
2363 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2364 OSRF_BUFFER_ADD( sql_buf, val );
2366 osrfLogError( OSRF_LOG_MARK,
2367 "%s: Error quoting key string [%s]", MODULENAME, val);
2369 buffer_free(sql_buf);
2376 buffer_add( sql_buf, " )" );
2379 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
2382 if (transform_subcolumn)
2383 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2385 return buffer_release(sql_buf);
2388 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2389 const jsonObject* node, const char* op ) {
2391 if( ! is_good_operator( op ) ) {
2392 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2396 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2397 if( ! field_transform )
2400 int extra_parens = 0; // boolean
2402 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2403 if ( ! value_obj ) {
2404 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2406 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2408 free(field_transform);
2412 } else if ( value_obj->type == JSON_ARRAY ) {
2413 value = searchValueTransform( value_obj );
2415 osrfLogError(OSRF_LOG_MARK,
2416 "%s: Error building value transform for field transform", MODULENAME);
2417 free( field_transform );
2420 } else if ( value_obj->type == JSON_HASH ) {
2421 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2423 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2425 free(field_transform);
2429 } else if ( value_obj->type == JSON_NUMBER ) {
2430 value = jsonNumberToDBString( field, value_obj );
2431 } else if ( value_obj->type == JSON_NULL ) {
2432 osrfLogError( OSRF_LOG_MARK,
2433 "%s: Error building predicate for field transform: null value", MODULENAME );
2434 free(field_transform);
2436 } else if ( value_obj->type == JSON_BOOL ) {
2437 osrfLogError( OSRF_LOG_MARK,
2438 "%s: Error building predicate for field transform: boolean value", MODULENAME );
2439 free(field_transform);
2442 if ( !strcmp( get_primitive( field ), "number") ) {
2443 value = jsonNumberToDBString( field, value_obj );
2445 value = jsonObjectToSimpleString( value_obj );
2446 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2447 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2450 free(field_transform);
2456 const char* left_parens = "";
2457 const char* right_parens = "";
2459 if( extra_parens ) {
2464 growing_buffer* sql_buf = buffer_init(32);
2468 "%s%s %s %s %s %s%s",
2479 free(field_transform);
2481 return buffer_release(sql_buf);
2484 static char* searchSimplePredicate (const char* op, const char* class_alias,
2485 osrfHash* field, const jsonObject* node) {
2487 if( ! is_good_operator( op ) ) {
2488 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2494 // Get the value to which we are comparing the specified column
2495 if (node->type != JSON_NULL) {
2496 if ( node->type == JSON_NUMBER ) {
2497 val = jsonNumberToDBString( field, node );
2498 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2499 val = jsonNumberToDBString( field, node );
2501 val = jsonObjectToSimpleString(node);
2506 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2507 // Value is not numeric; enclose it in quotes
2508 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2509 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2516 // Compare to a null value
2517 val = strdup( "NULL" );
2518 if (strcmp( op, "=" ))
2524 growing_buffer* sql_buf = buffer_init(32);
2525 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2526 char* pred = buffer_release( sql_buf );
2533 static char* searchBETWEENPredicate (const char* class_alias,
2534 osrfHash* field, const jsonObject* node) {
2536 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2537 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2539 if( NULL == y_node ) {
2540 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2543 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2544 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2551 if ( !strcmp( get_primitive( field ), "number") ) {
2552 x_string = jsonNumberToDBString(field, x_node);
2553 y_string = jsonNumberToDBString(field, y_node);
2556 x_string = jsonObjectToSimpleString(x_node);
2557 y_string = jsonObjectToSimpleString(y_node);
2558 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2559 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2560 MODULENAME, x_string, y_string);
2567 growing_buffer* sql_buf = buffer_init(32);
2568 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2569 class_alias, osrfHashGet(field, "name"), x_string, y_string );
2573 return buffer_release(sql_buf);
2576 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2577 jsonObject* node, osrfMethodContext* ctx ) {
2580 if (node->type == JSON_ARRAY) { // equality IN search
2581 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2582 } else if (node->type == JSON_HASH) { // other search
2583 jsonIterator* pred_itr = jsonNewIterator( node );
2584 if( !jsonIteratorHasNext( pred_itr ) ) {
2585 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2586 MODULENAME, osrfHashGet(field, "name") );
2588 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2590 // Verify that there are no additional predicates
2591 if( jsonIteratorHasNext( pred_itr ) ) {
2592 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2593 MODULENAME, osrfHashGet(field, "name") );
2594 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2595 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2596 else if ( !(strcasecmp( pred_itr->key,"in" ))
2597 || !(strcasecmp( pred_itr->key,"not in" )) )
2598 pred = searchINPredicate(
2599 class_info->alias, field, pred_node, pred_itr->key, ctx );
2600 else if ( pred_node->type == JSON_ARRAY )
2601 pred = searchFunctionPredicate(
2602 class_info->alias, field, pred_node, pred_itr->key );
2603 else if ( pred_node->type == JSON_HASH )
2604 pred = searchFieldTransformPredicate(
2605 class_info, field, pred_node, pred_itr->key );
2607 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2609 jsonIteratorFree(pred_itr);
2611 } else if (node->type == JSON_NULL) { // IS NULL search
2612 growing_buffer* _p = buffer_init(64);
2615 "\"%s\".%s IS NULL",
2616 class_info->class_name,
2617 osrfHashGet(field, "name")
2619 pred = buffer_release(_p);
2620 } else { // equality search
2621 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2640 field : call_number,
2656 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2658 const jsonObject* working_hash;
2659 jsonObject* freeable_hash = NULL;
2661 if (join_hash->type == JSON_HASH) {
2662 working_hash = join_hash;
2663 } else if (join_hash->type == JSON_STRING) {
2664 // turn it into a JSON_HASH by creating a wrapper
2665 // around a copy of the original
2666 const char* _tmp = jsonObjectGetString( join_hash );
2667 freeable_hash = jsonNewObjectType(JSON_HASH);
2668 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2669 working_hash = freeable_hash;
2673 "%s: JOIN failed; expected JSON object type not found",
2679 growing_buffer* join_buf = buffer_init(128);
2680 const char* leftclass = left_info->class_name;
2682 jsonObject* snode = NULL;
2683 jsonIterator* search_itr = jsonNewIterator( working_hash );
2685 while ( (snode = jsonIteratorNext( search_itr )) ) {
2686 const char* right_alias = search_itr->key;
2688 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2690 class = right_alias;
2692 const ClassInfo* right_info = add_joined_class( right_alias, class );
2696 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2700 jsonIteratorFree( search_itr );
2701 buffer_free( join_buf );
2703 jsonObjectFree( freeable_hash );
2706 osrfHash* links = right_info->links;
2707 const char* table = right_info->source_def;
2709 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2710 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2712 if (field && !fkey) {
2713 // Look up the corresponding join column in the IDL.
2714 // The link must be defined in the child table,
2715 // and point to the right parent table.
2716 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2717 const char* reltype = NULL;
2718 const char* other_class = NULL;
2719 reltype = osrfHashGet( idl_link, "reltype" );
2720 if( reltype && strcmp( reltype, "has_many" ) )
2721 other_class = osrfHashGet( idl_link, "class" );
2722 if( other_class && !strcmp( other_class, leftclass ) )
2723 fkey = osrfHashGet( idl_link, "key" );
2727 "%s: JOIN failed. No link defined from %s.%s to %s",
2733 buffer_free(join_buf);
2735 jsonObjectFree(freeable_hash);
2736 jsonIteratorFree(search_itr);
2740 } else if (!field && fkey) {
2741 // Look up the corresponding join column in the IDL.
2742 // The link must be defined in the child table,
2743 // and point to the right parent table.
2744 osrfHash* left_links = left_info->links;
2745 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2746 const char* reltype = NULL;
2747 const char* other_class = NULL;
2748 reltype = osrfHashGet( idl_link, "reltype" );
2749 if( reltype && strcmp( reltype, "has_many" ) )
2750 other_class = osrfHashGet( idl_link, "class" );
2751 if( other_class && !strcmp( other_class, class ) )
2752 field = osrfHashGet( idl_link, "key" );
2756 "%s: JOIN failed. No link defined from %s.%s to %s",
2762 buffer_free(join_buf);
2764 jsonObjectFree(freeable_hash);
2765 jsonIteratorFree(search_itr);
2769 } else if (!field && !fkey) {
2770 osrfHash* left_links = left_info->links;
2772 // For each link defined for the left class:
2773 // see if the link references the joined class
2774 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2775 osrfHash* curr_link = NULL;
2776 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2777 const char* other_class = osrfHashGet( curr_link, "class" );
2778 if( other_class && !strcmp( other_class, class ) ) {
2780 // In the IDL, the parent class doesn't always know then names of the child
2781 // columns that are pointing to it, so don't use that end of the link
2782 const char* reltype = osrfHashGet( curr_link, "reltype" );
2783 if( reltype && strcmp( reltype, "has_many" ) ) {
2784 // Found a link between the classes
2785 fkey = osrfHashIteratorKey( itr );
2786 field = osrfHashGet( curr_link, "key" );
2791 osrfHashIteratorFree( itr );
2793 if (!field || !fkey) {
2794 // Do another such search, with the classes reversed
2796 // For each link defined for the joined class:
2797 // see if the link references the left class
2798 osrfHashIterator* itr = osrfNewHashIterator( links );
2799 osrfHash* curr_link = NULL;
2800 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2801 const char* other_class = osrfHashGet( curr_link, "class" );
2802 if( other_class && !strcmp( other_class, leftclass ) ) {
2804 // In the IDL, the parent class doesn't know then names of the child
2805 // columns that are pointing to it, so don't use that end of the link
2806 const char* reltype = osrfHashGet( curr_link, "reltype" );
2807 if( reltype && strcmp( reltype, "has_many" ) ) {
2808 // Found a link between the classes
2809 field = osrfHashIteratorKey( itr );
2810 fkey = osrfHashGet( curr_link, "key" );
2815 osrfHashIteratorFree( itr );
2818 if (!field || !fkey) {
2821 "%s: JOIN failed. No link defined between %s and %s",
2826 buffer_free(join_buf);
2828 jsonObjectFree(freeable_hash);
2829 jsonIteratorFree(search_itr);
2834 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2836 if ( !strcasecmp(type,"left") ) {
2837 buffer_add(join_buf, " LEFT JOIN");
2838 } else if ( !strcasecmp(type,"right") ) {
2839 buffer_add(join_buf, " RIGHT JOIN");
2840 } else if ( !strcasecmp(type,"full") ) {
2841 buffer_add(join_buf, " FULL JOIN");
2843 buffer_add(join_buf, " INNER JOIN");
2846 buffer_add(join_buf, " INNER JOIN");
2849 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2850 table, right_alias, right_alias, field, left_info->alias, fkey);
2852 // Add any other join conditions as specified by "filter"
2853 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2855 const char* filter_op = jsonObjectGetString(
2856 jsonObjectGetKeyConst( snode, "filter_op" ) );
2857 if ( filter_op && !strcasecmp("or",filter_op) ) {
2858 buffer_add( join_buf, " OR " );
2860 buffer_add( join_buf, " AND " );
2863 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2865 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2866 OSRF_BUFFER_ADD( join_buf, jpred );
2871 "%s: JOIN failed. Invalid conditional expression.",