From 9fffdf67c47ea7ad1346f6d68ccb96ff266bd4a7 Mon Sep 17 00:00:00 2001 From: Mateusz Loskot Date: Tue, 26 Feb 2013 22:39:37 +0000 Subject: [PATCH] Add query transformation callback feature Implementation of query transformation requested in ticket #66: * Add query_transformation.h with internal types * Add method session::set_query_transformation() - assigns user-defined function or functor * Add session::get_query() - returns string with transformed query. * Replace use of session::get_query_stream() with get_query() * Keep session::get_query_stream() to access original query accumulated in current session statement * Add test_query_transformation to common tests. Tested using VS2012 on Windows 8 against DB2, ODBC (PostgreSQL, SQL Server), MySQL, Oracle, SQLite3. --- src/core/query_transformation.h | 49 +++++++++++++++++++++++++++ src/core/ref-counted-prepare-info.cpp | 2 +- src/core/ref-counted-statement.cpp | 2 +- src/core/session.cpp | 24 ++++++++++--- src/core/session.h | 11 ++++++ src/core/test/common-tests.h | 47 +++++++++++++++++++++++++ 6 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 src/core/query_transformation.h diff --git a/src/core/query_transformation.h b/src/core/query_transformation.h new file mode 100644 index 000000000..a8332b8fb --- /dev/null +++ b/src/core/query_transformation.h @@ -0,0 +1,49 @@ +// +// Copyright (C) 2013 Mateusz Loskot +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_QUERY_TRANSFORMATION_H_INCLUDED +#define SOCI_QUERY_TRANSFORMATION_H_INCLUDED + +#include "soci-config.h" +#include +#include + +namespace soci +{ + +namespace details +{ + +// TODO: use std::unary_function +class query_transformation_function : public std::unary_function +{ +public: + virtual result_type operator()(argument_type a) const = 0; +}; + +template +class query_transformation : public query_transformation_function +{ +public: + query_transformation(T callback) + : callback_(callback) + {} + + result_type operator()(argument_type query) const + { + return callback_(query); + } + +private: + T callback_; +}; + +} // namespace details + +} // namespace soci + +#endif // SOCI_QUERY_TRANSFORMATION_H_INCLUDED diff --git a/src/core/ref-counted-prepare-info.cpp b/src/core/ref-counted-prepare-info.cpp index 3196c94de..a655e16ea 100644 --- a/src/core/ref-counted-prepare-info.cpp +++ b/src/core/ref-counted-prepare-info.cpp @@ -42,5 +42,5 @@ void ref_counted_prepare_info::final_action() std::string ref_counted_prepare_info::get_query() const { - return session_.get_query_stream().str(); + return session_.get_query(); } diff --git a/src/core/ref-counted-statement.cpp b/src/core/ref-counted-statement.cpp index 4bf8b2736..ec7b4ddd4 100644 --- a/src/core/ref-counted-statement.cpp +++ b/src/core/ref-counted-statement.cpp @@ -23,7 +23,7 @@ void ref_counted_statement::final_action() try { st_.alloc(); - st_.prepare(session_.get_query_stream().str(), st_one_time_query); + st_.prepare(session_.get_query(), st_one_time_query); st_.define_and_bind(); const bool gotData = st_.execute(true); diff --git a/src/core/session.cpp b/src/core/session.cpp index aa1b3fc0f..6280071fd 100644 --- a/src/core/session.cpp +++ b/src/core/session.cpp @@ -10,6 +10,7 @@ #include "connection-pool.h" #include "soci-backend.h" #include "backend-loader.h" +#include "query_transformation.h" #ifdef _MSC_VER #pragma warning(disable:4355) @@ -48,7 +49,7 @@ void ensureConnected(session_backend * backEnd) } // namespace anonymous session::session() - : once(this), prepare(this), logStream_(NULL), + : once(this), prepare(this), query_transformation_(0), logStream_(NULL), lastFactory_(NULL), uppercaseColumnNames_(false), backEnd_(NULL), isFromPool_(false), pool_(NULL) { @@ -56,7 +57,7 @@ session::session() session::session(backend_factory const & factory, std::string const & connectString) - : once(this), prepare(this), logStream_(NULL), + : once(this), prepare(this), query_transformation_(0), logStream_(NULL), lastFactory_(&factory), lastConnectString_(connectString), uppercaseColumnNames_(false), isFromPool_(false), pool_(NULL) @@ -66,7 +67,7 @@ session::session(backend_factory const & factory, session::session(std::string const & backendName, std::string const & connectString) - : once(this), prepare(this), logStream_(NULL), + : once(this), prepare(this), query_transformation_(0), logStream_(NULL), uppercaseColumnNames_(false), isFromPool_(false), pool_(NULL) { @@ -79,7 +80,7 @@ session::session(std::string const & backendName, } session::session(std::string const & connectString) - : once(this), prepare(this), logStream_(NULL), + : once(this), prepare(this), query_transformation_(0), logStream_(NULL), uppercaseColumnNames_(false), isFromPool_(false), pool_(NULL) { @@ -115,6 +116,7 @@ session::~session() } else { + delete query_transformation_; delete backEnd_; } } @@ -260,6 +262,20 @@ std::ostringstream & session::get_query_stream() } } +std::string session::get_query() const +{ + // preserve logical constness of get_query, + // stream used as read-only here, + session* pthis = const_cast(this); + + // sole place where any user-defined query transformation is applied + if (query_transformation_) + { + return (*query_transformation_)(pthis->get_query_stream().str()); + } + return pthis->get_query_stream().str(); +} + void session::set_log_stream(std::ostream * s) { if (isFromPool_) diff --git a/src/core/session.h b/src/core/session.h index 7482bf875..4b9e159f2 100644 --- a/src/core/session.h +++ b/src/core/session.h @@ -9,6 +9,8 @@ #define SOCI_SESSION_H_INCLUDED #include "once-temp-type.h" +#include "query_transformation.h" + // std #include #include @@ -62,6 +64,14 @@ class SOCI_DECL session details::once_temp_type operator<<(T const & t) { return once << t; } std::ostringstream & get_query_stream(); + std::string get_query() const; + + template + void set_query_transformation(T callback) + { + delete query_transformation_; + query_transformation_= new details::query_transformation(callback); + } // support for basic logging void set_log_stream(std::ostream * s); @@ -107,6 +117,7 @@ class SOCI_DECL session session& operator=(session const &); std::ostringstream query_stream_; + details::query_transformation_function* query_transformation_; std::ostream * logStream_; std::string lastQuery_; diff --git a/src/core/test/common-tests.h b/src/core/test/common-tests.h index d03e179f4..d6cee3888 100644 --- a/src/core/test/common-tests.h +++ b/src/core/test/common-tests.h @@ -307,6 +307,7 @@ class common_tests test30(); test31(); test_get_affected_rows(); + test_query_transformation(); test_pull5(); test_issue67(); } @@ -3564,6 +3565,52 @@ void test31() std::cout << "test 31 passed\n"; } +// Issue 66 - test query transformation callback feature +static std::string lower_than_g(std::string query) +{ + return query + " WHERE c < 'g'"; +} + +struct greater_than_g_lower_than_j : std::unary_function +{ + result_type operator()(argument_type query) const + { + return query + " WHERE c > 'g' AND c < 'j'"; + } +}; + +void test_query_transformation() +{ + session sql(backEndFactory_, connectString_); + { + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + char c; + for (c = 'a'; c <= 'z'; ++c) + { + sql << "insert into soci_test(c) values(\'" << c << "\')"; + } + + char const* query = "select count(*) from soci_test"; + { + sql.set_query_transformation(lower_than_g); + int count; + sql << query, into(count); + + assert(count == 'g' - 'a'); + } + { + sql.set_query_transformation(greater_than_g_lower_than_j()); + int count; + sql << query, into(count); + + assert(count == 'j' - 'h'); + } + } + std::cout << "test test_query_transformation passed" << std::endl; +} + // Originally, submitted to SQLite3 backend and later moved to common test. // Test commit b394d039530f124802d06c3b1a969c3117683152 // Author: Mika Fischer