3 @brief Utility routines for translating JSON into SQL.
11 #include "opensrf/utils.h"
12 #include "opensrf/log.h"
13 #include "opensrf/osrf_application.h"
14 #include "openils/oils_utils.h"
15 #include "openils/oils_sql.h"
17 // The next four macros are OR'd together as needed to form a set
18 // of bitflags. SUBCOMBO enables an extra pair of parentheses when
19 // nesting one UNION, INTERSECT or EXCEPT inside another.
20 // SUBSELECT tells us we're in a subquery, so don't add the
21 // terminal semicolon yet.
24 #define DISABLE_I18N 2
25 #define SELECT_DISTINCT 1
30 struct ClassInfoStruct;
31 typedef struct ClassInfoStruct ClassInfo;
33 #define ALIAS_STORE_SIZE 16
34 #define CLASS_NAME_STORE_SIZE 16
36 struct ClassInfoStruct {
40 osrfHash* class_def; // Points into IDL
41 osrfHash* fields; // Points into IDL
42 osrfHash* links; // Points into IDL
44 // The remaining members are private and internal. Client code should not
45 // access them directly.
47 ClassInfo* next; // Supports linked list of joined classes
48 int in_use; // boolean
50 // We usually store the alias and class name in the following arrays, and
51 // point the corresponding pointers at them. When the string is too big
52 // for the array (which will probably never happen in practice), we strdup it.
54 char alias_store[ ALIAS_STORE_SIZE + 1 ];
55 char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
58 struct QueryFrameStruct;
59 typedef struct QueryFrameStruct QueryFrame;
61 struct QueryFrameStruct {
63 ClassInfo* join_list; // linked list of classes joined to the core class
64 QueryFrame* next; // implements stack as linked list
65 int in_use; // boolean
68 static int timeout_needs_resetting;
69 static time_t time_next_reset;
71 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
73 static void setXactId( osrfMethodContext* ctx );
74 static inline const char* getXactId( osrfMethodContext* ctx );
75 static inline void clearXactId( osrfMethodContext* ctx );
77 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
78 jsonObject* where_hash, jsonObject* query_hash, int* err );
79 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
80 static jsonObject* oilsMakeJSONFromResult( dbi_result );
82 static char* searchSimplePredicate ( const char* op, const char* class_alias,
83 osrfHash* field, const jsonObject* node );
84 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
85 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject* );
86 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*,
88 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
89 static char* searchINPredicate ( const char*, osrfHash*,
90 jsonObject*, const char*, osrfMethodContext* );
91 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
92 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
93 static char* searchWHERE ( const jsonObject*, const ClassInfo*, int, osrfMethodContext* );
94 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
95 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
97 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
99 void userDataFree( void* );
100 static void sessionDataFree( char*, void* );
101 static int obj_is_true( const jsonObject* obj );
102 static const char* json_type( int code );
103 static const char* get_primitive( osrfHash* field );
104 static const char* get_datatype( osrfHash* field );
105 static int is_identifier( const char* s );
106 static int is_good_operator( const char* op );
107 static void pop_query_frame( void );
108 static void push_query_frame( void );
109 static int add_query_core( const char* alias, const char* class_name );
110 static inline ClassInfo* search_alias( const char* target );
111 static ClassInfo* search_all_alias( const char* target );
112 static ClassInfo* add_joined_class( const char* alias, const char* classname );
113 static void clear_query_stack( void );
115 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
116 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
117 static const char* org_tree_root( osrfMethodContext* ctx );
118 static jsonObject* single_hash( const char* key, const char* value );
120 static int child_initialized = 0; /* boolean */
122 static dbi_conn writehandle; /* our MASTER db connection */
123 static dbi_conn dbhandle; /* our CURRENT db connection */
124 //static osrfHash * readHandles;
126 // The following points to the top of a stack of QueryFrames. It's a little
127 // confusing because the top level of the query is at the bottom of the stack.
128 static QueryFrame* curr_query = NULL;
130 static dbi_conn writehandle; /* our MASTER db connection */
131 static dbi_conn dbhandle; /* our CURRENT db connection */
132 //static osrfHash * readHandles;
134 static int max_flesh_depth = 100;
136 static int enforce_pcrud = 0; // Boolean
137 static char* modulename = NULL;
140 @brief Connect to the database.
141 @return A database connection if successful, or NULL if not.
143 dbi_conn oilsConnectDB( const char* mod_name ) {
145 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
146 if( dbi_initialize( NULL ) == -1 ) {
147 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
150 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
152 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
153 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
154 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
155 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
156 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
157 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
159 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
160 dbi_conn handle = dbi_conn_new( driver );
163 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
166 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
168 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
169 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
171 if( host ) dbi_conn_set_option( handle, "host", host );
172 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
173 if( user ) dbi_conn_set_option( handle, "username", user );
174 if( pw ) dbi_conn_set_option( handle, "password", pw );
175 if( db ) dbi_conn_set_option( handle, "dbname", db );
183 if( dbi_conn_connect( handle ) < 0 ) {
185 if( dbi_conn_connect( handle ) < 0 ) {
187 dbi_conn_error( handle, &errmsg );
188 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", errmsg );
193 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
199 @brief Select some options.
200 @param module_name: Name of the server.
201 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
203 This source file is used (at this writing) to implement three different servers:
204 - open-ils.reporter-store
208 These servers behave mostly the same, but they implement different combinations of
209 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
211 Here we use the server name in messages to identify which kind of server issued them.
212 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
214 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
216 module_name = "open-ils.cstore"; // bulletproofing with a default
221 modulename = strdup( module_name );
222 enforce_pcrud = do_pcrud;
223 max_flesh_depth = flesh_depth;
227 @brief Install a database connection.
228 @param conn Pointer to a database connection.
230 In some contexts, @a conn may merely provide a driver so that we can process strings
231 properly, without providing an open database connection.
233 void oilsSetDBConnection( dbi_conn conn ) {
234 dbhandle = writehandle = conn;
238 @brief Get a table name, view name, or subquery for use in a FROM clause.
239 @param class Pointer to the IDL class entry.
240 @return A table name, a view name, or a subquery in parentheses.
242 In some cases the IDL defines a class, not with a table name or a view name, but with
243 a SELECT statement, which may be used as a subquery.
245 char* oilsGetRelation( osrfHash* classdef ) {
247 char* source_def = NULL;
248 const char* tabledef = osrfHashGet( classdef, "tablename" );
251 source_def = strdup( tabledef ); // Return the name of a table or view
253 tabledef = osrfHashGet( classdef, "source_definition" );
255 // Return a subquery, enclosed in parentheses
256 source_def = safe_malloc( strlen( tabledef ) + 3 );
257 source_def[ 0 ] = '(';
258 strcpy( source_def + 1, tabledef );
259 strcat( source_def, ")" );
261 // Not found: return an error
262 const char* classname = osrfHashGet( classdef, "classname" );
267 "%s ERROR No tablename or source_definition for class \"%s\"",
278 @brief Add datatypes from the database to the fields in the IDL.
279 @param handle Handle for a database connection
280 @return Zero if successful, or 1 upon error.
282 For each relevant class in the IDL: ask the database for the datatype of every field.
283 In particular, determine which fields are text fields and which fields are numeric
284 fields, so that we know whether to enclose their values in quotes.
286 int oilsExtendIDL( dbi_conn handle ) {
287 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
288 osrfHash* class = NULL;
289 growing_buffer* query_buf = buffer_init( 64 );
290 int results_found = 0; // boolean
292 // For each class in the IDL...
293 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
294 const char* classname = osrfHashIteratorKey( class_itr );
295 osrfHash* fields = osrfHashGet( class, "fields" );
297 // If the class is virtual, ignore it
298 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
299 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
303 char* tabledef = oilsGetRelation( class );
305 continue; // No such relation -- a query of it would be doomed to failure
307 buffer_reset( query_buf );
308 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
312 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
313 modulename, OSRF_BUFFER_C_STR( query_buf ) );
315 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
320 const char* columnName;
321 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
323 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
326 /* fetch the fieldmapper index */
327 osrfHash* _f = osrfHashGet(fields, columnName);
330 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
332 /* determine the field type and storage attributes */
334 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
336 case DBI_TYPE_INTEGER : {
338 if( !osrfHashGet(_f, "primitive") )
339 osrfHashSet(_f, "number", "primitive");
341 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
342 if( attr & DBI_INTEGER_SIZE8 )
343 osrfHashSet( _f, "INT8", "datatype" );
345 osrfHashSet( _f, "INT", "datatype" );
348 case DBI_TYPE_DECIMAL :
349 if( !osrfHashGet( _f, "primitive" ))
350 osrfHashSet( _f, "number", "primitive" );
352 osrfHashSet( _f, "NUMERIC", "datatype" );
355 case DBI_TYPE_STRING :
356 if( !osrfHashGet( _f, "primitive" ))
357 osrfHashSet( _f, "string", "primitive" );
359 osrfHashSet( _f,"TEXT", "datatype" );
362 case DBI_TYPE_DATETIME :
363 if( !osrfHashGet( _f, "primitive" ))
364 osrfHashSet( _f, "string", "primitive" );
366 osrfHashSet( _f, "TIMESTAMP", "datatype" );
369 case DBI_TYPE_BINARY :
370 if( !osrfHashGet( _f, "primitive" ))
371 osrfHashSet( _f, "string", "primitive" );
373 osrfHashSet( _f, "BYTEA", "datatype" );
378 "Setting [%s] to primitive [%s] and datatype [%s]...",
380 osrfHashGet( _f, "primitive" ),
381 osrfHashGet( _f, "datatype" )
385 } // end while loop for traversing columns of result
386 dbi_result_free( result );
388 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]...", classname );
390 } // end for each class in IDL
392 buffer_free( query_buf );
393 osrfHashIteratorFree( class_itr );
394 child_initialized = 1;
396 if( !results_found ) {
397 osrfLogError( OSRF_LOG_MARK,
398 "No results found for any class -- bad database connection?" );
406 @brief Free an osrfHash that stores a transaction ID.
407 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
409 This function is a callback, to be called by the application session when it ends.
410 The application session stores the osrfHash via an opaque pointer.
412 If the osrfHash contains an entry for the key "xact_id", it means that an
413 uncommitted transaction is pending. Roll it back.
415 void userDataFree( void* blob ) {
416 osrfHash* hash = (osrfHash*) blob;
417 if( osrfHashGet( hash, "xact_id" ) && writehandle )
418 dbi_conn_query( writehandle, "ROLLBACK;" );
420 osrfHashFree( hash );
424 @name Managing session data
425 @brief Maintain data stored via the userData pointer of the application session.
427 Currently, session-level data is stored in an osrfHash. Other arrangements are
428 possible, and some would be more efficient. The application session calls a
429 callback function to free userData before terminating.
431 Currently, the only data we store at the session level is the transaction id. By this
432 means we can ensure that any pending transactions are rolled back before the application
438 @brief Free an item in the application session's userData.
439 @param key The name of a key for an osrfHash.
440 @param item An opaque pointer to the item associated with the key.
442 We store an osrfHash as userData with the application session, and arrange (by
443 installing userDataFree() as a different callback) for the session to free that
444 osrfHash before terminating.
446 This function is a callback for freeing items in the osrfHash. Currently we store
448 - Transaction id of a pending transaction; a character string. Key: "xact_id".
449 - Authkey; a character string. Key: "authkey".
450 - User object from the authentication server; a jsonObject. Key: "user_login".
452 If we ever store anything else in userData, we will need to revisit this function so
453 that it will free whatever else needs freeing.
455 static void sessionDataFree( char* key, void* item ) {
456 if( !strcmp( key, "xact_id" )
457 || !strcmp( key, "authkey" ) ) {
459 } else if( !strcmp( key, "user_login" ) )
460 jsonObjectFree( (jsonObject*) item );
464 @brief Save a transaction id.
465 @param ctx Pointer to the method context.
467 Save the session_id of the current application session as a transaction id.
469 static void setXactId( osrfMethodContext* ctx ) {
470 if( ctx && ctx->session ) {
471 osrfAppSession* session = ctx->session;
473 osrfHash* cache = session->userData;
475 // If the session doesn't already have a hash, create one. Make sure
476 // that the application session frees the hash when it terminates.
477 if( NULL == cache ) {
478 session->userData = cache = osrfNewHash();
479 osrfHashSetCallback( cache, &sessionDataFree );
480 ctx->session->userDataFree = &userDataFree;
483 // Save the transaction id in the hash, with the key "xact_id"
484 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
489 @brief Get the transaction ID for the current transaction, if any.
490 @param ctx Pointer to the method context.
491 @return Pointer to the transaction ID.
493 The return value points to an internal buffer, and will become invalid upon issuing
494 a commit or rollback.
496 static inline const char* getXactId( osrfMethodContext* ctx ) {
497 if( ctx && ctx->session && ctx->session->userData )
498 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
504 @brief Clear the current transaction id.
505 @param ctx Pointer to the method context.
507 static inline void clearXactId( osrfMethodContext* ctx ) {
508 if( ctx && ctx->session && ctx->session->userData )
509 osrfHashRemove( ctx->session->userData, "xact_id" );
514 @brief Save the user's login in the userData for the current application session.
515 @param ctx Pointer to the method context.
516 @param user_login Pointer to the user login object to be cached (we cache the original,
519 If @a user_login is NULL, remove the user login if one is already cached.
521 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
522 if( ctx && ctx->session ) {
523 osrfAppSession* session = ctx->session;
525 osrfHash* cache = session->userData;
527 // If the session doesn't already have a hash, create one. Make sure
528 // that the application session frees the hash when it terminates.
529 if( NULL == cache ) {
530 session->userData = cache = osrfNewHash();
531 osrfHashSetCallback( cache, &sessionDataFree );
532 ctx->session->userDataFree = &userDataFree;
536 osrfHashSet( cache, user_login, "user_login" );
538 osrfHashRemove( cache, "user_login" );
543 @brief Get the user login object for the current application session, if any.
544 @param ctx Pointer to the method context.
545 @return Pointer to the user login object if found; otherwise NULL.
547 The user login object was returned from the authentication server, and then cached so
548 we don't have to call the authentication server again for the same user.
550 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
551 if( ctx && ctx->session && ctx->session->userData )
552 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
558 @brief Save a copy of an authkey in the userData of the current application session.
559 @param ctx Pointer to the method context.
560 @param authkey The authkey to be saved.
562 If @a authkey is NULL, remove the authkey if one is already cached.
564 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
565 if( ctx && ctx->session && authkey ) {
566 osrfAppSession* session = ctx->session;
567 osrfHash* cache = session->userData;
569 // If the session doesn't already have a hash, create one. Make sure
570 // that the application session frees the hash when it terminates.
571 if( NULL == cache ) {
572 session->userData = cache = osrfNewHash();
573 osrfHashSetCallback( cache, &sessionDataFree );
574 ctx->session->userDataFree = &userDataFree;
577 // Save the transaction id in the hash, with the key "xact_id"
578 if( authkey && *authkey )
579 osrfHashSet( cache, strdup( authkey ), "authkey" );
581 osrfHashRemove( cache, "authkey" );
586 @brief Reset the login timeout.
587 @param authkey The authentication key for the current login session.
588 @param now The current time.
589 @return Zero if successful, or 1 if not.
591 Tell the authentication server to reset the timeout so that the login session won't
592 expire for a while longer.
594 We could dispense with the @a now parameter by calling time(). But we just called
595 time() in order to decide whether to reset the timeout, so we might as well reuse
596 the result instead of calling time() again.
598 static int reset_timeout( const char* authkey, time_t now ) {
599 jsonObject* auth_object = jsonNewObject( authkey );
601 // Ask the authentication server to reset the timeout. It returns an event
602 // indicating success or failure.
603 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
604 "open-ils.auth.session.reset_timeout", auth_object );
605 jsonObjectFree( auth_object );
607 if( !result || result->type != JSON_HASH ) {
608 osrfLogError( OSRF_LOG_MARK,
609 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
610 jsonObjectFree( result );
611 return 1; // Not the right sort of object returned
614 const jsonObject* ilsevent = jsonObjectGetKey( result, "ilsevent" );
615 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
616 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
617 jsonObjectFree( result );
618 return 1; // Return code from method not available
621 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
622 const char* desc = jsonObjectGetString( jsonObjectGetKey( result, "desc" ) );
624 desc = "(No reason available)"; // failsafe; shouldn't happen
625 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
626 jsonObjectFree( result );
630 // Revise our local proxy for the timeout deadline
631 // by a smallish fraction of the timeout interval
632 const char* timeout = jsonObjectGetString( jsonObjectGetKey( result, "payload" ) );
634 timeout = "1"; // failsafe; shouldn't happen
635 time_next_reset = now + atoi( timeout ) / 15;
637 jsonObjectFree( result );
638 return 0; // Successfully reset timeout
642 @brief Get the authkey string for the current application session, if any.
643 @param ctx Pointer to the method context.
644 @return Pointer to the cached authkey if found; otherwise NULL.
646 If present, the authkey string was cached from a previous method call.
648 static const char* getAuthkey( osrfMethodContext* ctx ) {
649 if( ctx && ctx->session && ctx->session->userData ) {
650 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
652 // Possibly reset the authentication timeout to keep the login alive. We do so
653 // no more than once per method call, and not at all if it has been only a short
654 // time since the last reset.
656 // Here we reset explicitly, if at all. We also implicitly reset the timeout
657 // whenever we call the "open-ils.auth.session.retrieve" method.
658 if( timeout_needs_resetting ) {
659 time_t now = time( NULL );
660 if( now >= time_next_reset && reset_timeout( authkey, now ) )
661 authkey = NULL; // timeout has apparently expired already
664 timeout_needs_resetting = 0;
672 @brief Implement the transaction.begin method.
673 @param ctx Pointer to the method context.
674 @return Zero if successful, or -1 upon error.
676 Start a transaction. Save a transaction ID for future reference.
679 - authkey (PCRUD only)
681 Return to client: Transaction ID
683 int beginTransaction( osrfMethodContext* ctx ) {
684 if(osrfMethodVerifyContext( ctx )) {
685 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
689 if( enforce_pcrud ) {
690 timeout_needs_resetting = 1;
691 const jsonObject* user = verifyUserPCRUD( ctx );
696 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
698 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction", modulename );
699 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
700 "osrfMethodException", ctx->request, "Error starting transaction" );
704 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
705 osrfAppRespondComplete( ctx, ret );
706 jsonObjectFree( ret );
712 @brief Implement the savepoint.set method.
713 @param ctx Pointer to the method context.
714 @return Zero if successful, or -1 if not.
716 Issue a SAVEPOINT to the database server.
719 - authkey (PCRUD only)
722 Return to client: Savepoint name
724 int setSavepoint( osrfMethodContext* ctx ) {
725 if(osrfMethodVerifyContext( ctx )) {
726 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
731 if( enforce_pcrud ) {
733 timeout_needs_resetting = 1;
734 const jsonObject* user = verifyUserPCRUD( ctx );
739 // Verify that a transaction is pending
740 const char* trans_id = getXactId( ctx );
741 if( NULL == trans_id ) {
742 osrfAppSessionStatus(
744 OSRF_STATUS_INTERNALSERVERERROR,
745 "osrfMethodException",
747 "No active transaction -- required for savepoints"
752 // Get the savepoint name from the method params
753 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
755 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", spName );
759 "%s: Error creating savepoint %s in transaction %s",
764 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
765 "osrfMethodException", ctx->request, "Error creating savepoint" );
768 jsonObject* ret = jsonNewObject( spName );
769 osrfAppRespondComplete( ctx, ret );
770 jsonObjectFree( ret );
776 @brief Implement the savepoint.release method.
777 @param ctx Pointer to the method context.
778 @return Zero if successful, or -1 if not.
780 Issue a RELEASE SAVEPOINT to the database server.
783 - authkey (PCRUD only)
786 Return to client: Savepoint name
788 int releaseSavepoint( osrfMethodContext* ctx ) {
789 if(osrfMethodVerifyContext( ctx )) {
790 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
795 if( enforce_pcrud ) {
797 timeout_needs_resetting = 1;
798 const jsonObject* user = verifyUserPCRUD( ctx );
803 // Verify that a transaction is pending
804 const char* trans_id = getXactId( ctx );
805 if( NULL == trans_id ) {
806 osrfAppSessionStatus(
808 OSRF_STATUS_INTERNALSERVERERROR,
809 "osrfMethodException",
811 "No active transaction -- required for savepoints"
816 // Get the savepoint name from the method params
817 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
819 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", spName );
823 "%s: Error releasing savepoint %s in transaction %s",
828 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
829 "osrfMethodException", ctx->request, "Error releasing savepoint" );
832 jsonObject* ret = jsonNewObject( spName );
833 osrfAppRespondComplete( ctx, ret );
834 jsonObjectFree( ret );
840 @brief Implement the savepoint.rollback method.
841 @param ctx Pointer to the method context.
842 @return Zero if successful, or -1 if not.
844 Issue a ROLLBACK TO SAVEPOINT to the database server.
847 - authkey (PCRUD only)
850 Return to client: Savepoint name
852 int rollbackSavepoint( osrfMethodContext* ctx ) {
853 if(osrfMethodVerifyContext( ctx )) {
854 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
859 if( enforce_pcrud ) {
861 timeout_needs_resetting = 1;
862 const jsonObject* user = verifyUserPCRUD( ctx );
867 // Verify that a transaction is pending
868 const char* trans_id = getXactId( ctx );
869 if( NULL == trans_id ) {
870 osrfAppSessionStatus(
872 OSRF_STATUS_INTERNALSERVERERROR,
873 "osrfMethodException",
875 "No active transaction -- required for savepoints"
880 // Get the savepoint name from the method params
881 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
883 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName );
887 "%s: Error rolling back savepoint %s in transaction %s",
892 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
893 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
896 jsonObject* ret = jsonNewObject( spName );
897 osrfAppRespondComplete( ctx, ret );
898 jsonObjectFree( ret );
904 @brief Implement the transaction.commit method.
905 @param ctx Pointer to the method context.
906 @return Zero if successful, or -1 if not.
908 Issue a COMMIT to the database server.
911 - authkey (PCRUD only)
913 Return to client: Transaction ID.
915 int commitTransaction( osrfMethodContext* ctx ) {
916 if(osrfMethodVerifyContext( ctx )) {
917 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
921 if( enforce_pcrud ) {
922 timeout_needs_resetting = 1;
923 const jsonObject* user = verifyUserPCRUD( ctx );
928 // Verify that a transaction is pending
929 const char* trans_id = getXactId( ctx );
930 if( NULL == trans_id ) {
931 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
932 "osrfMethodException", ctx->request, "No active transaction to commit" );
936 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
938 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction", modulename );
939 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
940 "osrfMethodException", ctx->request, "Error committing transaction" );
943 jsonObject* ret = jsonNewObject( trans_id );
944 osrfAppRespondComplete( ctx, ret );
945 jsonObjectFree( ret );
952 @brief Implement the transaction.rollback method.
953 @param ctx Pointer to the method context.
954 @return Zero if successful, or -1 if not.
956 Issue a ROLLBACK to the database server.
959 - authkey (PCRUD only)
961 Return to client: Transaction ID
963 int rollbackTransaction( osrfMethodContext* ctx ) {
964 if( osrfMethodVerifyContext( ctx )) {
965 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
969 if( enforce_pcrud ) {
970 timeout_needs_resetting = 1;
971 const jsonObject* user = verifyUserPCRUD( ctx );
976 // Verify that a transaction is pending
977 const char* trans_id = getXactId( ctx );
978 if( NULL == trans_id ) {
979 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
980 "osrfMethodException", ctx->request, "No active transaction to roll back" );
984 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
986 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction", modulename );
987 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
988 "osrfMethodException", ctx->request, "Error rolling back transaction" );
991 jsonObject* ret = jsonNewObject( trans_id );
992 osrfAppRespondComplete( ctx, ret );
993 jsonObjectFree( ret );
1000 @brief Implement the "search" method.
1001 @param ctx Pointer to the method context.
1002 @return Zero if successful, or -1 if not.
1005 - authkey (PCRUD only)
1006 - WHERE clause, as jsonObject
1007 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1009 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1010 Optionally flesh linked fields.
1012 int doSearch( osrfMethodContext* ctx ) {
1013 if( osrfMethodVerifyContext( ctx )) {
1014 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1019 timeout_needs_resetting = 1;
1021 jsonObject* where_clause;
1022 jsonObject* rest_of_query;
1024 if( enforce_pcrud ) {
1025 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1026 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1028 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1029 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1032 // Get the class metadata
1033 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1034 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1038 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1040 osrfAppRespondComplete( ctx, NULL );
1044 // Return each row to the client (except that some may be suppressed by PCRUD)
1045 jsonObject* cur = 0;
1046 unsigned long res_idx = 0;
1047 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1048 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1050 osrfAppRespond( ctx, cur );
1052 jsonObjectFree( obj );
1054 osrfAppRespondComplete( ctx, NULL );
1059 @brief Implement the "id_list" method.
1060 @param ctx Pointer to the method context.
1061 @param err Pointer through which to return an error code.
1062 @return Zero if successful, or -1 if not.
1065 - authkey (PCRUD only)
1066 - WHERE clause, as jsonObject
1067 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1069 Return to client: The primary key values for all rows of the relevant class that
1070 satisfy a specified WHERE clause.
1072 This method relies on the assumption that every class has a primary key consisting of
1075 int doIdList( osrfMethodContext* ctx ) {
1076 if( osrfMethodVerifyContext( ctx )) {
1077 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1082 timeout_needs_resetting = 1;
1084 jsonObject* where_clause;
1085 jsonObject* rest_of_query;
1087 // We use the where clause without change. But we need to massage the rest of the
1088 // query, so we work with a copy of it instead of modifying the original.
1090 if( enforce_pcrud ) {
1091 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1092 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1094 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1095 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1098 // Eliminate certain SQL clauses, if present.
1099 if( rest_of_query ) {
1100 jsonObjectRemoveKey( rest_of_query, "select" );
1101 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1102 jsonObjectRemoveKey( rest_of_query, "flesh" );
1103 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
1105 rest_of_query = jsonNewObjectType( JSON_HASH );
1108 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1110 // Get the class metadata
1111 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1112 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1114 // Build a SELECT list containing just the primary key,
1115 // i.e. like { "classname":["keyname"] }
1116 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1118 // Load array with name of primary key
1119 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1120 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1121 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1123 jsonObjectSetKey( rest_of_query, "select", select_clause );
1128 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1130 jsonObjectFree( rest_of_query );
1132 osrfAppRespondComplete( ctx, NULL );
1136 // Return each primary key value to the client
1138 unsigned long res_idx = 0;
1139 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1140 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1141 continue; // Suppress due to lack of permission
1143 osrfAppRespond( ctx,
1144 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1147 jsonObjectFree( obj );
1148 osrfAppRespondComplete( ctx, NULL );
1153 @brief Verify that we have a valid class reference.
1154 @param ctx Pointer to the method context.
1155 @param param Pointer to the method parameters.
1156 @return 1 if the class reference is valid, or zero if it isn't.
1158 The class of the method params must match the class to which the method id devoted.
1159 For PCRUD there are additional restrictions.
1161 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1163 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1164 osrfHash* class = osrfHashGet( method_meta, "class" );
1166 // Compare the method's class to the parameters' class
1167 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1169 // Oops -- they don't match. Complain.
1170 growing_buffer* msg = buffer_init( 128 );
1173 "%s: %s method for type %s was passed a %s",
1175 osrfHashGet( method_meta, "methodtype" ),
1176 osrfHashGet( class, "classname" ),
1177 param->classname ? param->classname : "(null)"
1180 char* m = buffer_release( msg );
1181 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1189 return verifyObjectPCRUD( ctx, param );
1195 @brief (PCRUD only) Verify that the user is properly logged in.
1196 @param ctx Pointer to the method context.
1197 @return If the user is logged in, a pointer to the user object from the authentication
1198 server; otherwise NULL.
1200 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1202 // Get the authkey (the first method parameter)
1203 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1205 // See if we have the same authkey, and a user object,
1206 // locally cached from a previous call
1207 const char* cached_authkey = getAuthkey( ctx );
1208 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1209 const jsonObject* cached_user = getUserLogin( ctx );
1214 // We have no matching authentication data in the cache. Authenticate from scratch.
1215 jsonObject* auth_object = jsonNewObject( auth );
1217 // Fetch the user object from the authentication server
1218 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1220 jsonObjectFree( auth_object );
1222 if( !user->classname || strcmp(user->classname, "au" )) {
1224 growing_buffer* msg = buffer_init( 128 );
1227 "%s: permacrud received a bad auth token: %s",
1232 char* m = buffer_release( msg );
1233 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1237 jsonObjectFree( user );
1241 setUserLogin( ctx, user );
1242 setAuthkey( ctx, auth );
1244 // Allow ourselves up to a second before we have to reset the login timeout.
1245 // It would be nice to use some fraction of the timeout interval enforced by the
1246 // authentication server, but that value is not readily available at this point.
1247 // Instead, we use a conservative default interval.
1248 time_next_reset = time( NULL ) + 1;
1254 @brief For PCRUD: Determine whether the current user may access the current row.
1255 @param ctx Pointer to the method context.
1256 @param obj Pointer to the row being potentially accessed.
1257 @return 1 if access is permitted, or 0 if it isn't.
1259 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1261 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1263 dbhandle = writehandle;
1265 // Figure out what class and method are involved
1266 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1267 osrfHash* class = osrfHashGet( method_metadata, "class" );
1268 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1270 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1271 // contexts we will do another lookup of the current row, even if we already have a
1272 // previously fetched row image, because the row image in hand may not include the
1273 // foreign key(s) that we need.
1275 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1276 // but they aren't implemented yet.
1279 if( *method_type == 's' || *method_type == 'i' ) {
1280 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1282 } else if( *method_type == 'u' || *method_type == 'd' ) {
1283 fetch = 1; // MUST go to the db for the object for update and delete
1286 // Get the appropriate permacrud entry from the IDL, depending on method type
1287 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1289 // No permacrud for this method type on this class
1291 growing_buffer* msg = buffer_init( 128 );
1294 "%s: %s on class %s has no permacrud IDL entry",
1296 osrfHashGet( method_metadata, "methodtype" ),
1297 osrfHashGet( class, "classname" )
1300 char* m = buffer_release( msg );
1301 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1302 "osrfMethodException", ctx->request, m );
1309 // Get the user id, and make sure the user is logged in
1310 const jsonObject* user = verifyUserPCRUD( ctx );
1312 return 0; // Not logged in? No access.
1314 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1316 // Get a list of permissions from the permacrud entry.
1317 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1319 // Build a list of org units that own the row. This is fairly convoluted because there
1320 // are several different ways that an org unit may own the row, as defined by the
1323 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1324 // identifying an owning org_unit..
1325 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1327 // Foreign context adds a layer of indirection. The row points to some other row that
1328 // an org unit may own. The "jump" attribute, if present, adds another layer of
1330 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1332 // The following string array stores the list of org units. (We don't have a thingie
1333 // for storing lists of integers, so we fake it with a list of strings.)
1334 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1337 const char* pkey_value = NULL;
1338 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1339 // If the global_required attribute is present and true, then the only owning
1340 // org unit is the root org unit, i.e. the one with no parent.
1341 osrfLogDebug( OSRF_LOG_MARK,
1342 "global-level permissions required, fetching top of the org tree" );
1344 // check for perm at top of org tree
1345 const char* org_tree_root_id = org_tree_root( ctx );
1346 if( org_tree_root_id ) {
1347 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1348 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1350 osrfStringArrayFree( context_org_array );
1355 // If the global_required attribute is absent or false, then we look for
1356 // local and/or foreign context. In order to find the relevant foreign
1357 // keys, we must either read the relevant row from the database, or look at
1358 // the image of the row that we already have in memory.
1360 // (Herein lies a bug. Even if we have an image of the row in memory, that
1361 // image may not include the foreign key column(s) that we need.)
1363 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1364 "fetching context org ids" );
1365 const char* pkey = osrfHashGet( class, "primarykey" );
1366 jsonObject *param = NULL;
1368 if( obj->classname ) {
1369 pkey_value = oilsFMGetStringConst( obj, pkey );
1371 param = jsonObjectClone( obj );
1372 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1375 pkey_value = jsonObjectGetString( obj );
1377 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1378 "of %s and retrieving from the database", pkey_value );
1382 // Fetch the row so that we can look at the foreign key(s)
1383 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1384 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1385 jsonObjectFree( _tmp_params );
1387 param = jsonObjectExtractIndex( _list, 0 );
1388 jsonObjectFree( _list );
1392 // The row doesn't exist. Complain, and deny access.
1393 osrfLogDebug( OSRF_LOG_MARK,
1394 "Object not found in the database with primary key %s of %s",
1397 growing_buffer* msg = buffer_init( 128 );
1400 "%s: no object found with primary key %s of %s",
1406 char* m = buffer_release( msg );
1407 osrfAppSessionStatus(
1409 OSRF_STATUS_INTERNALSERVERERROR,
1410 "osrfMethodException",
1419 if( local_context && local_context->size > 0 ) {
1420 // The IDL provides a list of column names for the foreign keys denoting
1421 // local context. Look up the value of each one, and if it isn't null,
1422 // add it to the list of org units.
1423 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1424 local_context->size );
1426 const char* lcontext = NULL;
1427 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1428 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1429 if( fkey_value ) { // if not null
1430 osrfStringArrayAdd( context_org_array, fkey_value );
1433 "adding class-local field %s (value: %s) to the context org list",
1435 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1441 if( foreign_context ) {
1442 unsigned long class_count = osrfHashGetCount( foreign_context );
1443 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1445 if( class_count > 0 ) {
1447 // The IDL provides a list of foreign key columns pointing to rows that
1448 // an org unit may own. Follow each link, identify the owning org unit,
1449 // and add it to the list.
1450 osrfHash* fcontext = NULL;
1451 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1452 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1453 // For each class to which a foreign key points:
1454 const char* class_name = osrfHashIteratorKey( class_itr );
1455 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1459 "%d foreign context fields(s) specified for class %s",
1460 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1464 // Get the name of the key field in the foreign table
1465 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1467 // Get the value of the foreign key pointing to the foreign table
1468 char* foreign_pkey_value =
1469 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1470 if( !foreign_pkey_value )
1471 continue; // Foreign key value is null; skip it
1473 // Look up the row to which the foreign key points
1474 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1475 jsonObject* _list = doFieldmapperSearch(
1476 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1478 jsonObject* _fparam = NULL;
1479 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1480 _fparam = jsonObjectExtractIndex( _list, 0 );
1482 jsonObjectFree( _tmp_params );
1483 jsonObjectFree( _list );
1485 // At this point _fparam either points to the row identified by the
1486 // foreign key, or it's NULL (no such row found).
1488 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1490 const char* bad_class = NULL; // For noting failed lookups
1492 bad_class = class_name; // Referenced row not found
1493 else if( jump_list ) {
1494 // Follow a chain of rows, linked by foreign keys, to find an owner
1495 const char* flink = NULL;
1497 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1498 // For each entry in the jump list. Each entry (i.e. flink) is
1499 // the name of a foreign key column in the current row.
1501 // From the IDL, get the linkage information for the next jump
1502 osrfHash* foreign_link_hash =
1503 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1505 // Get the class metadata for the class
1506 // to which the foreign key points
1507 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1508 osrfHashGet( foreign_link_hash, "class" ));
1510 // Get the name of the referenced key of that class
1511 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1513 // Get the value of the foreign key pointing to that class
1514 free( foreign_pkey_value );
1515 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1516 if( !foreign_pkey_value )
1517 break; // Foreign key is null; quit looking
1519 // Build a WHERE clause for the lookup
1520 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1523 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1524 _tmp_params, NULL, &err );
1526 // Get the resulting row
1527 jsonObjectFree( _fparam );
1528 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1529 _fparam = jsonObjectExtractIndex( _list, 0 );
1531 // Referenced row not found
1533 bad_class = osrfHashGet( foreign_link_hash, "class" );
1536 jsonObjectFree( _tmp_params );
1537 jsonObjectFree( _list );
1543 // We had a foreign key pointing to such-and-such a row, but then
1544 // we couldn't fetch that row. The data in the database are in an
1545 // inconsistent state; the database itself may even be corrupted.
1546 growing_buffer* msg = buffer_init( 128 );
1549 "%s: no object of class %s found with primary key %s of %s",
1553 foreign_pkey_value ? foreign_pkey_value : "(null)"
1556 char* m = buffer_release( msg );
1557 osrfAppSessionStatus(
1559 OSRF_STATUS_INTERNALSERVERERROR,
1560 "osrfMethodException",
1566 osrfHashIteratorFree( class_itr );
1567 free( foreign_pkey_value );
1568 jsonObjectFree( param );
1573 free( foreign_pkey_value );
1576 // Examine each context column of the foreign row,
1577 // and add its value to the list of org units.
1579 const char* foreign_field = NULL;
1580 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1581 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1582 osrfStringArrayAdd( context_org_array,
1583 oilsFMGetStringConst( _fparam, foreign_field ));
1584 osrfLogDebug( OSRF_LOG_MARK,
1585 "adding foreign class %s field %s (value: %s) "
1586 "to the context org list",
1589 osrfStringArrayGetString(
1590 context_org_array, context_org_array->size - 1 )
1594 jsonObjectFree( _fparam );
1598 osrfHashIteratorFree( class_itr );
1602 jsonObjectFree( param );
1605 const char* context_org = NULL;
1606 const char* perm = NULL;
1609 if( permission->size == 0 ) {
1610 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1614 // For every combination of permission and context org unit: call a stored procedure
1615 // to determine if the user has this permission in the context of this org unit.
1616 // If the answer is yes at any point, then we're done, and the user has permission.
1617 // In other words permissions are additive.
1619 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1621 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1627 "Checking object permission [%s] for user %d "
1628 "on object %s (class %s) at org %d",
1632 osrfHashGet( class, "classname" ),
1636 result = dbi_conn_queryf(
1638 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1641 osrfHashGet( class, "classname" ),
1649 "Received a result for object permission [%s] "
1650 "for user %d on object %s (class %s) at org %d",
1654 osrfHashGet( class, "classname" ),
1658 if( dbi_result_first_row( result )) {
1659 jsonObject* return_val = oilsMakeJSONFromResult( result );
1660 const char* has_perm = jsonObjectGetString(
1661 jsonObjectGetKeyConst( return_val, "has_perm" ));
1665 "Status of object permission [%s] for user %d "
1666 "on object %s (class %s) at org %d is %s",
1670 osrfHashGet(class, "classname"),
1675 if( *has_perm == 't' )
1677 jsonObjectFree( return_val );
1680 dbi_result_free( result );
1686 osrfLogDebug( OSRF_LOG_MARK,
1687 "Checking non-object permission [%s] for user %d at org %d",
1688 perm, userid, atoi(context_org) );
1689 result = dbi_conn_queryf(
1691 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1698 osrfLogDebug( OSRF_LOG_MARK,
1699 "Received a result for permission [%s] for user %d at org %d",
1700 perm, userid, atoi( context_org ));
1701 if( dbi_result_first_row( result )) {
1702 jsonObject* return_val = oilsMakeJSONFromResult( result );
1703 const char* has_perm = jsonObjectGetString(
1704 jsonObjectGetKeyConst( return_val, "has_perm" ));
1705 osrfLogDebug( OSRF_LOG_MARK,
1706 "Status of permission [%s] for user %d at org %d is [%s]",
1707 perm, userid, atoi( context_org ), has_perm );
1708 if( *has_perm == 't' )
1710 jsonObjectFree( return_val );
1713 dbi_result_free( result );
1723 osrfStringArrayFree( context_org_array );
1729 @brief Look up the root of the org_unit tree.
1730 @param ctx Pointer to the method context.
1731 @return The id of the root org unit, as a character string.
1733 Query actor.org_unit where parent_ou is null, and return the id as a string.
1735 This function assumes that there is only one root org unit, i.e. that we
1736 have a single tree, not a forest.
1738 The calling code is responsible for freeing the returned string.
1740 static const char* org_tree_root( osrfMethodContext* ctx ) {
1742 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1743 static time_t last_lookup_time = 0;
1744 time_t current_time = time( NULL );
1746 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1747 // We successfully looked this up less than an hour ago.
1748 // It's not likely to have changed since then.
1749 return strdup( cached_root_id );
1751 last_lookup_time = current_time;
1754 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1755 jsonObject* result = doFieldmapperSearch(
1756 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1757 jsonObjectFree( where_clause );
1759 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1762 jsonObjectFree( result );
1764 growing_buffer* msg = buffer_init( 128 );
1765 OSRF_BUFFER_ADD( msg, modulename );
1766 OSRF_BUFFER_ADD( msg,
1767 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1769 char* m = buffer_release( msg );
1770 osrfAppSessionStatus( ctx->session,
1771 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1774 cached_root_id[ 0 ] = '\0';
1778 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
1779 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1781 strcpy( cached_root_id, root_org_unit_id );
1782 jsonObjectFree( result );
1783 return cached_root_id;
1787 @brief Create a JSON_HASH with a single key/value pair.
1788 @param key The key of the key/value pair.
1789 @param value the value of the key/value pair.
1790 @return Pointer to a newly created jsonObject of type JSON_HASH.
1792 The value of the key/value is either a string or (if @a value is NULL) a null.
1794 static jsonObject* single_hash( const char* key, const char* value ) {
1796 if( ! key ) key = "";
1798 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1799 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1804 int doCreate( osrfMethodContext* ctx ) {
1805 if(osrfMethodVerifyContext( ctx )) {
1806 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1811 timeout_needs_resetting = 1;
1813 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1814 jsonObject* target = NULL;
1815 jsonObject* options = NULL;
1817 if( enforce_pcrud ) {
1818 target = jsonObjectGetIndex( ctx->params, 1 );
1819 options = jsonObjectGetIndex( ctx->params, 2 );
1821 target = jsonObjectGetIndex( ctx->params, 0 );
1822 options = jsonObjectGetIndex( ctx->params, 1 );
1825 if( !verifyObjectClass( ctx, target )) {
1826 osrfAppRespondComplete( ctx, NULL );
1830 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1832 const char* trans_id = getXactId( ctx );
1834 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1836 osrfAppSessionStatus(
1838 OSRF_STATUS_BADREQUEST,
1839 "osrfMethodException",
1841 "No active transaction -- required for CREATE"
1843 osrfAppRespondComplete( ctx, NULL );
1847 // The following test is harmless but redundant. If a class is
1848 // readonly, we don't register a create method for it.
1849 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1850 osrfAppSessionStatus(
1852 OSRF_STATUS_BADREQUEST,
1853 "osrfMethodException",
1855 "Cannot INSERT readonly class"
1857 osrfAppRespondComplete( ctx, NULL );
1861 // Set the last_xact_id
1862 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1864 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1865 trans_id, target->classname, index);
1866 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
1869 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1871 dbhandle = writehandle;
1873 osrfHash* fields = osrfHashGet( meta, "fields" );
1874 char* pkey = osrfHashGet( meta, "primarykey" );
1875 char* seq = osrfHashGet( meta, "sequence" );
1877 growing_buffer* table_buf = buffer_init( 128 );
1878 growing_buffer* col_buf = buffer_init( 128 );
1879 growing_buffer* val_buf = buffer_init( 128 );
1881 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
1882 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
1883 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1884 buffer_add( val_buf,"VALUES (" );
1888 osrfHash* field = NULL;
1889 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1890 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1892 const char* field_name = osrfHashIteratorKey( field_itr );
1894 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1897 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1900 if( field_object && field_object->classname ) {
1901 value = oilsFMGetString(
1903 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
1905 } else if( field_object && JSON_BOOL == field_object->type ) {
1906 if( jsonBoolIsTrue( field_object ) )
1907 value = strdup( "t" );
1909 value = strdup( "f" );
1911 value = jsonObjectToSimpleString( field_object );
1917 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1918 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1921 buffer_add( col_buf, field_name );
1923 if( !field_object || field_object->type == JSON_NULL ) {
1924 buffer_add( val_buf, "DEFAULT" );
1926 } else if( !strcmp( get_primitive( field ), "number" )) {
1927 const char* numtype = get_datatype( field );
1928 if( !strcmp( numtype, "INT8" )) {
1929 buffer_fadd( val_buf, "%lld", atoll( value ));
1931 } else if( !strcmp( numtype, "INT" )) {
1932 buffer_fadd( val_buf, "%d", atoi( value ));
1934 } else if( !strcmp( numtype, "NUMERIC" )) {
1935 buffer_fadd( val_buf, "%f", atof( value ));
1938 if( dbi_conn_quote_string( writehandle, &value )) {
1939 OSRF_BUFFER_ADD( val_buf, value );
1942 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
1943 osrfAppSessionStatus(
1945 OSRF_STATUS_INTERNALSERVERERROR,
1946 "osrfMethodException",
1948 "Error quoting string -- please see the error log for more details"
1951 buffer_free( table_buf );
1952 buffer_free( col_buf );
1953 buffer_free( val_buf );
1954 osrfAppRespondComplete( ctx, NULL );
1962 osrfHashIteratorFree( field_itr );
1964 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1965 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1967 char* table_str = buffer_release( table_buf );
1968 char* col_str = buffer_release( col_buf );
1969 char* val_str = buffer_release( val_buf );
1970 growing_buffer* sql = buffer_init( 128 );
1971 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1976 char* query = buffer_release( sql );
1978 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
1980 jsonObject* obj = NULL;
1983 dbi_result result = dbi_conn_query( writehandle, query );
1985 obj = jsonNewObject( NULL );
1988 "%s ERROR inserting %s object using query [%s]",
1990 osrfHashGet(meta, "fieldmapper"),
1993 osrfAppSessionStatus(
1995 OSRF_STATUS_INTERNALSERVERERROR,
1996 "osrfMethodException",
1998 "INSERT error -- please see the error log for more details"
2003 char* id = oilsFMGetString( target, pkey );
2005 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2006 growing_buffer* _id = buffer_init( 10 );
2007 buffer_fadd( _id, "%lld", new_id );
2008 id = buffer_release( _id );
2011 // Find quietness specification, if present
2012 const char* quiet_str = NULL;
2014 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2016 quiet_str = jsonObjectGetString( quiet_obj );
2019 if( str_is_true( quiet_str )) { // if quietness is specified
2020 obj = jsonNewObject( id );
2024 // Fetch the row that we just inserted, so that we can return it to the client
2025 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2026 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2029 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2033 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2035 jsonObjectFree( list );
2036 jsonObjectFree( where_clause );
2043 osrfAppRespondComplete( ctx, obj );
2044 jsonObjectFree( obj );
2049 @brief Implement the retrieve method.
2050 @param ctx Pointer to the method context.
2051 @param err Pointer through which to return an error code.
2052 @return If successful, a pointer to the result to be returned to the client;
2055 From the method's class, fetch a row with a specified value in the primary key. This
2056 method relies on the database design convention that a primary key consists of a single
2060 - authkey (PCRUD only)
2061 - value of the primary key for the desired row, for building the WHERE clause
2062 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2064 Return to client: One row from the query.
2066 int doRetrieve( osrfMethodContext* ctx ) {
2067 if(osrfMethodVerifyContext( ctx )) {
2068 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2073 timeout_needs_resetting = 1;
2078 if( enforce_pcrud ) {
2083 // Get the class metadata
2084 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2086 // Get the value of the primary key, from a method parameter
2087 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2091 "%s retrieving %s object with primary key value of %s",
2093 osrfHashGet( class_def, "fieldmapper" ),
2094 jsonObjectGetString( id_obj )
2097 // Build a WHERE clause based on the key value
2098 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2101 osrfHashGet( class_def, "primarykey" ), // name of key column
2102 jsonObjectClone( id_obj ) // value of key column
2105 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2109 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2111 jsonObjectFree( where_clause );
2113 osrfAppRespondComplete( ctx, NULL );
2117 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2118 jsonObjectFree( list );
2120 if( enforce_pcrud ) {
2121 if(!verifyObjectPCRUD( ctx, obj )) {
2122 jsonObjectFree( obj );
2124 growing_buffer* msg = buffer_init( 128 );
2125 OSRF_BUFFER_ADD( msg, modulename );
2126 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2128 char* m = buffer_release( msg );
2129 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2133 osrfAppRespondComplete( ctx, NULL );
2138 osrfAppRespondComplete( ctx, obj );
2139 jsonObjectFree( obj );
2144 @brief Translate a numeric value to a string representation for the database.
2145 @param field Pointer to the IDL field definition.
2146 @param value Pointer to a jsonObject holding the value of a field.
2147 @return Pointer to a newly allocated string.
2149 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2150 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2151 or (what is worse) valid SQL that is wrong.
2153 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2155 The calling code is responsible for freeing the resulting string by calling free().
2157 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2158 growing_buffer* val_buf = buffer_init( 32 );
2159 const char* numtype = get_datatype( field );
2161 // For historical reasons the following contains cruft that could be cleaned up.
2162 if( !strncmp( numtype, "INT", 3 ) ) {
2163 if( value->type == JSON_NUMBER )
2164 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2165 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2167 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2170 } else if( !strcmp( numtype, "NUMERIC" )) {
2171 if( value->type == JSON_NUMBER )
2172 buffer_fadd( val_buf, jsonObjectGetString( value ));
2174 buffer_fadd( val_buf, jsonObjectGetString( value ));
2178 // Presumably this was really intended to be a string, so quote it
2179 char* str = jsonObjectToSimpleString( value );
2180 if( dbi_conn_quote_string( dbhandle, &str )) {
2181 OSRF_BUFFER_ADD( val_buf, str );
2184 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2186 buffer_free( val_buf );
2191 return buffer_release( val_buf );
2194 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2195 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2196 growing_buffer* sql_buf = buffer_init( 32 );
2202 osrfHashGet( field, "name" )
2206 buffer_add( sql_buf, "IN (" );
2207 } else if( !strcasecmp( op,"not in" )) {
2208 buffer_add( sql_buf, "NOT IN (" );
2210 buffer_add( sql_buf, "IN (" );
2213 if( node->type == JSON_HASH ) {
2214 // subquery predicate
2215 char* subpred = buildQuery( ctx, node, SUBSELECT );
2217 buffer_free( sql_buf );
2221 buffer_add( sql_buf, subpred );
2224 } else if( node->type == JSON_ARRAY ) {
2225 // literal value list
2226 int in_item_index = 0;
2227 int in_item_first = 1;
2228 const jsonObject* in_item;
2229 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2234 buffer_add( sql_buf, ", " );
2237 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2238 osrfLogError( OSRF_LOG_MARK,
2239 "%s: Expected string or number within IN list; found %s",
2240 modulename, json_type( in_item->type ) );
2241 buffer_free( sql_buf );
2245 // Append the literal value -- quoted if not a number
2246 if( JSON_NUMBER == in_item->type ) {
2247 char* val = jsonNumberToDBString( field, in_item );
2248 OSRF_BUFFER_ADD( sql_buf, val );
2251 } else if( !strcmp( get_primitive( field ), "number" )) {
2252 char* val = jsonNumberToDBString( field, in_item );
2253 OSRF_BUFFER_ADD( sql_buf, val );
2257 char* key_string = jsonObjectToSimpleString( in_item );
2258 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2259 OSRF_BUFFER_ADD( sql_buf, key_string );
2262 osrfLogError( OSRF_LOG_MARK,
2263 "%s: Error quoting key string [%s]", modulename, key_string );
2265 buffer_free( sql_buf );
2271 if( in_item_first ) {
2272 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2273 buffer_free( sql_buf );
2277 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2278 modulename, json_type( node->type ));
2279 buffer_free( sql_buf );
2283 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2285 return buffer_release( sql_buf );
2288 // Receive a JSON_ARRAY representing a function call. The first
2289 // entry in the array is the function name. The rest are parameters.
2290 static char* searchValueTransform( const jsonObject* array ) {
2292 if( array->size < 1 ) {
2293 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2297 // Get the function name
2298 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2299 if( func_item->type != JSON_STRING ) {
2300 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2301 modulename, json_type( func_item->type ));
2305 growing_buffer* sql_buf = buffer_init( 32 );
2307 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2308 OSRF_BUFFER_ADD( sql_buf, "( " );
2310 // Get the parameters
2311 int func_item_index = 1; // We already grabbed the zeroth entry
2312 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2314 // Add a separator comma, if we need one
2315 if( func_item_index > 2 )
2316 buffer_add( sql_buf, ", " );
2318 // Add the current parameter
2319 if( func_item->type == JSON_NULL ) {
2320 buffer_add( sql_buf, "NULL" );
2322 char* val = jsonObjectToSimpleString( func_item );
2323 if( dbi_conn_quote_string( dbhandle, &val )) {
2324 OSRF_BUFFER_ADD( sql_buf, val );
2327 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2329 buffer_free( sql_buf );
2336 buffer_add( sql_buf, " )" );
2338 return buffer_release( sql_buf );
2341 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2342 const jsonObject* node, const char* op ) {
2344 if( ! is_good_operator( op ) ) {
2345 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2349 char* val = searchValueTransform( node );
2353 growing_buffer* sql_buf = buffer_init( 32 );
2358 osrfHashGet( field, "name" ),
2365 return buffer_release( sql_buf );
2368 // class_alias is a class name or other table alias
2369 // field is a field definition as stored in the IDL
2370 // node comes from the method parameter, and may represent an entry in the SELECT list
2371 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2372 const jsonObject* node ) {
2373 growing_buffer* sql_buf = buffer_init( 32 );
2375 const char* field_transform = jsonObjectGetString(
2376 jsonObjectGetKeyConst( node, "transform" ) );
2377 const char* transform_subcolumn = jsonObjectGetString(
2378 jsonObjectGetKeyConst( node, "result_field" ) );
2380 if( transform_subcolumn ) {
2381 if( ! is_identifier( transform_subcolumn ) ) {
2382 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2383 modulename, transform_subcolumn );
2384 buffer_free( sql_buf );
2387 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2390 if( field_transform ) {
2392 if( ! is_identifier( field_transform ) ) {
2393 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2394 modulename, field_transform );
2395 buffer_free( sql_buf );
2399 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2400 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2401 field_transform, class_alias, osrfHashGet( field, "name" ));
2403 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2404 field_transform, class_alias, osrfHashGet( field, "name" ));
2407 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2410 if( array->type != JSON_ARRAY ) {
2411 osrfLogError( OSRF_LOG_MARK,
2412 "%s: Expected JSON_ARRAY for function params; found %s",
2413 modulename, json_type( array->type ) );
2414 buffer_free( sql_buf );
2417 int func_item_index = 0;
2418 jsonObject* func_item;
2419 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2421 char* val = jsonObjectToSimpleString( func_item );
2424 buffer_add( sql_buf, ",NULL" );
2425 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2426 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2427 OSRF_BUFFER_ADD( sql_buf, val );
2429 osrfLogError( OSRF_LOG_MARK,
2430 "%s: Error quoting key string [%s]", modulename, val );
2432 buffer_free( sql_buf );
2439 buffer_add( sql_buf, " )" );
2442 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2445 if( transform_subcolumn )
2446 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2448 return buffer_release( sql_buf );
2451 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2452 const jsonObject* node, const char* op ) {
2454 if( ! is_good_operator( op ) ) {
2455 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2459 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2460 if( ! field_transform )
2463 int extra_parens = 0; // boolean
2465 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2467 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2469 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2471 free( field_transform );
2475 } else if( value_obj->type == JSON_ARRAY ) {
2476 value = searchValueTransform( value_obj );
2478 osrfLogError( OSRF_LOG_MARK,
2479 "%s: Error building value transform for field transform", modulename );
2480 free( field_transform );
2483 } else if( value_obj->type == JSON_HASH ) {
2484 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2486 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2488 free( field_transform );
2492 } else if( value_obj->type == JSON_NUMBER ) {
2493 value = jsonNumberToDBString( field, value_obj );
2494 } else if( value_obj->type == JSON_NULL ) {
2495 osrfLogError( OSRF_LOG_MARK,
2496 "%s: Error building predicate for field transform: null value", modulename );
2497 free( field_transform );
2499 } else if( value_obj->type == JSON_BOOL ) {
2500 osrfLogError( OSRF_LOG_MARK,
2501 "%s: Error building predicate for field transform: boolean value", modulename );
2502 free( field_transform );
2505 if( !strcmp( get_primitive( field ), "number") ) {
2506 value = jsonNumberToDBString( field, value_obj );
2508 value = jsonObjectToSimpleString( value_obj );
2509 if( !dbi_conn_quote_string( dbhandle, &value )) {
2510 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2511 modulename, value );
2513 free( field_transform );
2519 const char* left_parens = "";
2520 const char* right_parens = "";
2522 if( extra_parens ) {
2527 growing_buffer* sql_buf = buffer_init( 32 );
2531 "%s%s %s %s %s %s%s",
2542 free( field_transform );
2544 return buffer_release( sql_buf );
2547 static char* searchSimplePredicate( const char* op, const char* class_alias,
2548 osrfHash* field, const jsonObject* node ) {
2550 if( ! is_good_operator( op ) ) {
2551 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2557 // Get the value to which we are comparing the specified column
2558 if( node->type != JSON_NULL ) {
2559 if( node->type == JSON_NUMBER ) {
2560 val = jsonNumberToDBString( field, node );
2561 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2562 val = jsonNumberToDBString( field, node );
2564 val = jsonObjectToSimpleString( node );
2569 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2570 // Value is not numeric; enclose it in quotes
2571 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2572 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2579 // Compare to a null value
2580 val = strdup( "NULL" );
2581 if( strcmp( op, "=" ))
2587 growing_buffer* sql_buf = buffer_init( 32 );
2588 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2589 char* pred = buffer_release( sql_buf );
2596 static char* searchBETWEENPredicate( const char* class_alias,
2597 osrfHash* field, const jsonObject* node ) {
2599 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2600 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2602 if( NULL == y_node ) {
2603 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2606 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2607 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2614 if( !strcmp( get_primitive( field ), "number") ) {
2615 x_string = jsonNumberToDBString( field, x_node );
2616 y_string = jsonNumberToDBString( field, y_node );
2619 x_string = jsonObjectToSimpleString( x_node );
2620 y_string = jsonObjectToSimpleString( y_node );
2621 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2622 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2623 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2624 modulename, x_string, y_string );
2631 growing_buffer* sql_buf = buffer_init( 32 );
2632 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2633 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2637 return buffer_release( sql_buf );
2640 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2641 jsonObject* node, osrfMethodContext* ctx ) {
2644 if( node->type == JSON_ARRAY ) { // equality IN search
2645 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2646 } else if( node->type == JSON_HASH ) { // other search
2647 jsonIterator* pred_itr = jsonNewIterator( node );
2648 if( !jsonIteratorHasNext( pred_itr ) ) {
2649 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2650 modulename, osrfHashGet(field, "name" ));
2652 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2654 // Verify that there are no additional predicates
2655 if( jsonIteratorHasNext( pred_itr ) ) {
2656 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2657 modulename, osrfHashGet(field, "name" ));
2658 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2659 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2660 else if( !(strcasecmp( pred_itr->key,"in" ))
2661 || !(strcasecmp( pred_itr->key,"not in" )) )
2662 pred = searchINPredicate(
2663 class_info->alias, field, pred_node, pred_itr->key, ctx );
2664 else if( pred_node->type == JSON_ARRAY )
2665 pred = searchFunctionPredicate(
2666 class_info->alias, field, pred_node, pred_itr->key );
2667 else if( pred_node->type == JSON_HASH )
2668 pred = searchFieldTransformPredicate(
2669 class_info, field, pred_node, pred_itr->key );
2671 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2673 jsonIteratorFree( pred_itr );
2675 } else if( node->type == JSON_NULL ) { // IS NULL search
2676 growing_buffer* _p = buffer_init( 64 );
2679 "\"%s\".%s IS NULL",
2680 class_info->class_name,
2681 osrfHashGet( field, "name" )
2683 pred = buffer_release( _p );
2684 } else { // equality search
2685 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2704 field : call_number,
2720 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2722 const jsonObject* working_hash;
2723 jsonObject* freeable_hash = NULL;
2725 if( join_hash->type == JSON_HASH ) {
2726 working_hash = join_hash;
2727 } else if( join_hash->type == JSON_STRING ) {
2728 // turn it into a JSON_HASH by creating a wrapper
2729 // around a copy of the original
2730 const char* _tmp = jsonObjectGetString( join_hash );
2731 freeable_hash = jsonNewObjectType( JSON_HASH );
2732 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2733 working_hash = freeable_hash;
2737 "%s: JOIN failed; expected JSON object type not found",
2743 growing_buffer* join_buf = buffer_init( 128 );
2744 const char* leftclass = left_info->class_name;
2746 jsonObject* snode = NULL;
2747 jsonIterator* search_itr = jsonNewIterator( working_hash );
2749 while ( (snode = jsonIteratorNext( search_itr )) ) {
2750 const char* right_alias = search_itr->key;
2752 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2754 class = right_alias;
2756 const ClassInfo* right_info = add_joined_class( right_alias, class );
2760 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2764 jsonIteratorFree( search_itr );
2765 buffer_free( join_buf );
2767 jsonObjectFree( freeable_hash );
2770 osrfHash* links = right_info->links;
2771 const char* table = right_info->source_def;
2773 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2774 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2776 if( field && !fkey ) {
2777 // Look up the corresponding join column in the IDL.
2778 // The link must be defined in the child table,
2779 // and point to the right parent table.
2780 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2781 const char* reltype = NULL;
2782 const char* other_class = NULL;
2783 reltype = osrfHashGet( idl_link, "reltype" );
2784 if( reltype && strcmp( reltype, "has_many" ) )
2785 other_class = osrfHashGet( idl_link, "class" );
2786 if( other_class && !strcmp( other_class, leftclass ) )
2787 fkey = osrfHashGet( idl_link, "key" );
2791 "%s: JOIN failed. No link defined from %s.%s to %s",
2797 buffer_free( join_buf );
2799 jsonObjectFree( freeable_hash );
2800 jsonIteratorFree( search_itr );
2804 } else if( !field && fkey ) {
2805 // Look up the corresponding join column in the IDL.
2806 // The link must be defined in the child table,
2807 // and point to the right parent table.
2808 osrfHash* left_links = left_info->links;
2809 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2810 const char* reltype = NULL;
2811 const char* other_class = NULL;
2812 reltype = osrfHashGet( idl_link, "reltype" );
2813 if( reltype && strcmp( reltype, "has_many" ) )
2814 other_class = osrfHashGet( idl_link, "class" );
2815 if( other_class && !strcmp( other_class, class ) )
2816 field = osrfHashGet( idl_link, "key" );
2820 "%s: JOIN failed. No link defined from %s.%s to %s",
2826 buffer_free( join_buf );
2828 jsonObjectFree( freeable_hash );
2829 jsonIteratorFree( search_itr );
2833 } else if( !field && !fkey ) {
2834 osrfHash* left_links = left_info->links;
2836 // For each link defined for the left class:
2837 // see if the link references the joined class
2838 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2839 osrfHash* curr_link = NULL;
2840 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2841 const char* other_class = osrfHashGet( curr_link, "class" );
2842 if( other_class && !strcmp( other_class, class ) ) {
2844 // In the IDL, the parent class doesn't always know then names of the child
2845 // columns that are pointing to it, so don't use that end of the link
2846 const char* reltype = osrfHashGet( curr_link, "reltype" );
2847 if( reltype && strcmp( reltype, "has_many" ) ) {
2848 // Found a link between the classes
2849 fkey = osrfHashIteratorKey( itr );
2850 field = osrfHashGet( curr_link, "key" );
2855 osrfHashIteratorFree( itr );
2857 if( !field || !fkey ) {
2858 // Do another such search, with the classes reversed
2860 // For each link defined for the joined class:
2861 // see if the link references the left class
2862 osrfHashIterator* itr = osrfNewHashIterator( links );
2863 osrfHash* curr_link = NULL;
2864 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2865 const char* other_class = osrfHashGet( curr_link, "class" );
2866 if( other_class && !strcmp( other_class, leftclass ) ) {
2868 // In the IDL, the parent class doesn't know then names of the child