Files
rippled/tests/common-tests.h
Nik Bougalis caab155a00 Squashed 'src/soci/' changes from 6e9312c..b2855dc
b2855dc Merge pull request #378 from ravselj/sqlite_memory_leak
bf5dad9 Memory leak fix in sqlite3_session_backend.
165737c Fix incorrect pointer instead pointee comparison.
625db74 Merge pull request #376 from ravselj/cmake_debug_postfix2
f3a1055 Added proper support for SOCI_DEBUG_POSTFIX by changing backend-loader macro. If debug postfix is specified in CMake then it is passed forward to soci-core backend loader which then combines proper name based on build configuration.
3459d7d Minor CMake fix that checks if shared mode is set before adding shared test(s).
66d407a Merge pull request #373 from musopr/ambiguous_session
9070742 Merge pull request #372 from musopr/clang_cxx_c11
7b08ec7 Fixed ambiguous 'session' reference
e9748de Include SOCI_CXX_VERSION_FLAGS when compiling Clang
abd6775 Merge pull request #368 from ravselj/cmake_debug_postfix
cae0086 Added CMAKE_DEBUG_POSTFIX to SOCI_LIB_SUFFIX. This fixes backend loading when CMAKE_DEBUG_POSTFIX is used.
3dd4726 Enable MSVC multi process compiling by setting /MP flag.( VS2005+ )
e5f577f Merge pull request #365 from ravselj/sqlite_msvc_fixes
c4dde08 Some trivial fixes in code to resolve MSVC warnings in SQLite back-end
330f0e0 Merge pull request #364 from ravselj/connection_pars
b78c8ef Merge pull request #358 from ArnaudD-FR/bind_clean_up
9f415ee Merge pull request #362 from ArnaudD-FR/sqlite3_optim_split
f1f0162 sqlite3 backend optimizations
0b1a835 Include soci-platform.h because of snprintf
ac65d58 Merge pull request #361 from ArnaudD-FR/blob
24c8383 Bug fix - std::map with key type of const char* is replaced with std::string
3e02a54 Changes due to compiler warnings.
0c88f8c New data_type dt_blob and simple-interface support
403b8de Fix bind_clean_up when using 'row'
92ada95 Merge pull request #363 from ravselj/oracle_win_fix
d1ad52f Add a unit test for CHAR(N) fields padding behaviour.
274d08a Explicitly set character set in Firebird unit tests for CHAR(N).
7623f76 More and better MSVC warnings fixes.
caa2370 Removed dummy and not compilable operator<<(boost::optional).
c025cc8 Fix inserting strings longer than 8000 bytes with ODBC/MS SQL.
d8d765f Update documentations
b5d6507 - oracle cmake Windows fix - test oracle compilation error fix
a6b3514 Split Statement::clean_up into bind_clean_up and clean_up
ba453c7 Make SQL state in ODBC backend error messages more clear.
a374e54 Make ODBC backend error messages more homogeneous.
c4255c9 Delete old /docs folder.
0d3b6b9 Merge pull request #344 from OniDaito/markdown_doc
04f9461 Merge pull request #352 from ravselj/cmake_bug_shared
955a915 - CMake bug fix when building MSVC with SHARED option enabled
f7be373 Merge pull request #348 from msobczak/classic-makefiles
36f373f Added classic Makefile for PostgreSQL test.
615cb94 Updated classic Makefile for PostgreSQL backend.
821092c Don't use both -ansi and -std=gnu++98 flags with g++.
07543f5 Merge pull request #346 from msobczak/classic-makefiles
291fbe7 Merge pull request #347 from jsonn/master
a87776e Make it easier to override SOCI_LIBDIR.
96e66f5 Corrected handling of generated file with backend search path.
0b26c32 Updated classic Makefiles for Oracle Express 11.2.
15a3705 Removed all the crap debian packaging threw in
6db2a65 Merge pull request #343 from OniDaito/master
cf11404 Added Markdown docs
ffbfdc0 Inlined the pragmas for the C++11 tests
ae3ac9d Mistake with CXX11 pragma fixed in session.h
303a966 Merge pull request #341 from mloskot/issues/340
7d7516d Add more qualifications uses of session class with namespace soci::session.
caa3e2b Merge pull request #335 from mloskot/issues/258
1e89d43 Fully qualify uses of session class with namespace soci::session.
1ed81ca Restore setting session with query transformation
84d29e2 Merge branch 'pull/336'
949924a Cope with GNU <=4.6 warning about the #pragma
3494b2b Wrap stream operator for boost::optional<int> with HAVE_BOOST
af4b1a3 Report SOCI_CXX_C11 in CMake output
5744a16 Merge branch 'master' of https://github.com/OniDaito/soci into pull/336
0ab7b37 Remove old build .tcl scripts.
8f2195a Check Postgression availability before proceeding with build.
cd3af53 Remove #include "error.h" of non-existent header
48a8bfa Added the C++11 changes back in
1aee2d1 Add -Wl,-flat_namespace -Wl,-undefined -Wl,suppress to LINK_FLAGS on Apple/OSX
2bf3aa7 Disable test of set_query_transformation with C++ lambda
1d26033 CMake should not terminate on unknown toolset
d8b64cb Remove SociSystemInfo.cmake modul as redundant.
fc3391f List Boost.DateTime in core dependencies
f4ff281 Indicate Travis CI builds Oracle WITH_BOOST=OFF
4863376 Merge pull request #326 from mloskot/issues/224
0c18240 Restore Oracle build with tests on Travis CI.
bc4abd7 Throw instead of truncating VARCHAR columns in Firebird backend.
4c612af No changes, just small code simplification in Firebird backend.
ba206ed Extract common part of MySQL and ODBC MySQL tests in a header.
a0fd859 Remove asserts from the SOCI headers and code.
b36944c Check vector indices instead of asserting that they are valid.
75ef8d8 Handle dt_unsigned_long_long in the "simple" SOCI layer.
2e2f60b Remove asserts on unknown type from the "simple" SOCI layer.
e21aef7 Throw instead of asserting if connection_pool::lease() fails.
4b4cecb Replace left over asserts with CATCH CHECK in the tests code.
388a8d3 Use compile-time asserts to check SQLINTEGER size.
af80fd8 Use SQLT_BDOUBLE instead of SQLT_FLT in Oracle backend.
c74132b Fix warning about pointer-to-int conversion in Firebird backend.
4916551 Reorganize more unit tests to use CATCH sections.
787b428 Fix duplicate test name in Oracle unit tests.
7f65799 Avoid warnings about int-to-pointer conversions in DB2 backend.
7e80c68 Use parameter names from the query, if given, in error messages.
8b0c372 Don't clean up SQLite3 statement prematurely.
9119ed8 Rename db2_statement_backend::names field to names_.
f5c86b7 Make SOCI_NORETURN public and use it in declaration.
62c17b2 Add mktime_from_ymdhms() helper.
ee0b9e4 Fix a clash between CATCH test cases defined in different files.
60a33f4 Reorganize "Use and into" unit test to use CATCH sections.
db59a48 Merge pull request #314 from rpiotaix/release/3.2
82ea95f Don't add DB2_INCLUDE_DIR to global include directories.
4f570d7 Restore Travis CI builds for other databases
71edca9 Merge pull request #306 from mloskot/postgression
1e4b365 Add a Travis build using PostgreSQL server at Postgression.
6abb7e1 Typo
1ccff47 Replace raw & with &amp; entity.
56d3aa6  No real changes, just remove trailing whitespace.
ef6d90d Fix previous attempt to correct include of SQLite3 backend header
31a22a1 Include SQLite3 backend public headers using soci/ prefix.
e4376ed Merge branch 'master' into develop-3.2.3
111b50a Merge branch 'hotfix/3.2.3'
0ef4912 Add 3.2.3 release notes
8da98b2 Add new contributors
ee7e155 Bump version number in docs
30c02f3 Merge branch 'master' of git://github.com/Alex-Vol/soci into hotfix/3.2.3-PR263
5577606 Improve readability of ODBC error message Closes #229
8e14c5b Improve error message when an odbc error occurs
8a50af6 Merge branch 'hotfix/3.2.3' of https://github.com/SOCI/soci into hotfix/3.2.3
a948ab3 Remove unused HAVE_XXX defines
2c85be1 Fix missing strtoll on Cygwin and MinGW
d9c059e Clarify documenation and examples on bulk operations.
a08d750 [travis] Disable Oracle build
70e67aa Replace prefix underscore with suffix in private member names
c799e4f Append each member of tuple/fusion instead of adding tuple/fusion
645e7a2 Merge github.com:pacocamberos/soci into pacocamberos-branch-mysql-blob
e96bc2b Ignore CMakeLists.txt.user created by Qt Creator IDE
f694e30 Bump library version number to 3.2.3
5ecebe9 fixed link error for msvc 64 bit
3278579 Fix email notifications for Travis CI builds.
ad9075c Configure Travis CI to fail the build a.s.a.p.
7481868 Explicitly set extra_float_digits when using PostgreSQL in ODBC.
83bbbd4 update sqlite3 backend documentation
57530a1 Merge pull request #302 from vadz/rich-exceptions
5cda82a Avoid throwing from soci_error copy ctor and assignment operator.
8bb6610 Provide context of the error in soci_error when possible.
a8ba1cb Add use_type_base::dump_value() for richer diagnostics.
76b3089 Add soci_error::get_error_message() method.
e8e5978 No real changes, just remove trailing whitespace.
a90018b Merge catch-tests branch.
7b8b37b Use REQUIRE() and not CHECK() for a test that can't be allowed to fail.
c9dbe19 Move all tests from tests/assert to tests directory itself.
246f657 Convert the tests to use CATCH testing framework.
b2060fb Add single header version of CATCH testing framework.
b87407e Add values::get_number_of_columns() accessor.
9628081 Define SOCI_NORETURN: a portable __attribute__((noreturn)) equivalent.
6ebcbcd Remove the never used details::statement_impl::namedUses_.
0f1f295 Add helper exchange_type_cast<>() template function.
02207fc Rename "version" class in the ODBC test to "odbc_version".
42549d0 Merge pull request #297 from nbougalis/unshadow
7d5eb2d Use a base-class member instead of shadowing it.
37fbe8f Don't always disable ODBC MS SQL test under Unix, just when using Travis.
ed37399 Fix ODBC backend get_affected_rows() when using FreeTDS driver.
f212ca9 Skip test which fails when using ODBC with MS SQL Server.
629386f Merge pull request #294 from denisarnaud/develop
294becb Fix for the issue #169: cleaner way to include headers.
120b88c Accept oraocci12 as a possible Oracle library name too.
8e1ddcd Merge pull request #213 from ayllon/develop
6552eb1 No real changes, just simplify Firebird backend code a little.
34be702 Test exact round trip for NUMERIC values for all backends.
24c0539 Fix rounding error in Firebird NUMERIC/DECIMAL conversions.
69352bc Speculatively enable exact floating point comparisons for all backends.
f497fb8 Disable exact floating point when using PostgreSQL ODBC driver.
6f9dd54 No changes, just fix a repetitive typo in "assert" in comments.
02f0a69 No changes, just remove annoying "EXECEPTION" typo.
4bf2d3c Explicitly disable exact floating point comparison in MySQL test.
1f3dd92 Replace accidental assignment with a comparison in Firebird unit test.
c3cd309 Make text-to-double conversion exact in PostgreSQL backend.
ff9146a Explicitly disable exact floating point comparison in SQLite test.
7039d00 Compare floating point values exactly in tests whenever possible.
53cd24e Don't start implicit transaction too eagerly in Firebird backend.
66f0d82 Add possibility to build Firebird using embedded library.
1489811 Update version to 4.0.0 in cmake too.
5838cc6 Reenable building, if not testing, Oracle backend.
dc4fb73 Move ORACLE_HOME to Oracle-specific script.
f9167a1 Avoid many warnings about floating point values comparison.
c85744b Define GCC_WARNING_SUPPRESS and GCC_WARNING_RESTORE macros.
ef7e9a9 Avoid warning about set but unused variable in Oracle backend.
86a4598 Avoid g++ warnings about casting away const with C-style casts.
2c887b3 Ignore CMake-generated files.
f61d7f5 Add missing include for std::max
21824a1 fixed deadlock in soci::connection_pool::try_lease
6e6bd46 Documentation for SQLite3 result code support.
97cbb0a Update PostgreSQL backend documentation for UUID type support.
5448cf1 Spelling fix in an error message: s/Commiting/Committing/.
5b073e3 Remove the unnecessary "error.h" inclusion from PostgreSQL code.
5776dd4 Introduced sqlite3_soci_error exception as subclass of soci_error. This new exception exposes the SQLite3 result code returned when any failure happens. Using this method is preferable to scrubbing string error messages in soci_error exceptions. Useful when there needs to be a distinction between general errors and database constraint errors. Unit test added to prove the funtionality does what is expected.
6d766e7 Introduced support for UUID column type in PostgreSQL statement.cpp. Unit test added for UUID column support testing all supported kinds of UUID formatted strings on input and standardized UUID formatted strings on output.
e106dc0 Optionally use the environment locale in the tests.
1b65061 Make sqlite3 and mysql backends work with any locale too.
8548642 Use locale-independent function for converting doubles to strings.
1260d4f Reimplement cstring_to_double() without using C++ standard library.
bc884fe Remove unused HAVE_XXX defines
4ffb21b Fix missing strtoll on Cygwin and MinGW
7bd4991 Clarify documenation and examples on bulk operations.
4d0785e Temporarily disable using Oracle backend on Travis.
da7e42c Merge pull request #242 from vadz/pgsql8-bytea
070b278 Merge branch 'fix_odbc_msvc_x86_64' of github.com:snikulov/soci into snikulov-fix_odbc_msvc_x86_64
fce8560 [travis] Disable Oracle build
0ff0e01 Replace prefix underscore with suffix in private member names
65a5ee3 Append each member of tuple/fusion instead of adding tuple/fusion
8e9fb42 Merge github.com:pacocamberos/soci into pacocamberos-branch-mysql-blob
3dca4e3 Ignore CMakeLists.txt.user created by Qt Creator IDE
de7e6f0 Bump library version number to 3.2.3
946dd5a Fix PostgreSQL unit test to pass with PostgreSQL < 9.0.
0a47eaf vs2013 got strtoll/strtoull
58d31f9 Update version to 4.0.0.
89df841 Merge pull request #239 from vadz/soci-headers-prefix
417ef5c Include all public headers using "soci/" prefix inside SOCI itself.
5a99a15 Merge pull request #238 from vadz/cstrtod
0585bb1 Merge pull request #237 from vadz/odbc-header-fix
6f4162c Add helper cstring_to_double() and use it in PostgreSQL backend.
73119cb Fix compilation of ODBC-specific SOCI header with new include paths.
d877390 Delete obsolete src/<backend>/test directories
06a970f Revert CMake setup for ODBC DB2 test
f04147a Move ODBC test DSN files to new tests location
0219c43 Fix paths to ODBC test DSN files
928174c Fix Firebird includes of private headers
bc171b6 Fix copy-n-paste error in CMake macro parameter name
1f4d6ef Fix includes to point to backend headers in subdirectories
552e81c Enable other tests in tests/assert
7a018a3 Move tests/assert into separate subdirectories
e2ec7ff Ignore Qt Creator and Eclipse files
5d527ec Fix windows.h case for cross-compilation
024ccc8 Firebird: fix harmless warning in 64 bit builds.
802f78e Merge pull request #216 from dgrambow/develop
32c5f88 Add get_last_insert_id tests for sqlite3 and mysql backends
ccd4c0d Add get_last_insert_id for sqlite3 and mysql backends Update docs/beyond.html accordingly
42aec23 Fix in soci::oracle to allow spaces in the params
b2ea9f7 fixed link error for msvc 64 bit
ef69fa9 [travis] Disable building tests due to #199
09acb8c [travis] Fix bash script syntax error
cba671f [travis] Disable ctest run due to #199
d8f7d9d [travis] Restore soci-devel notifications
22257b3 [cmake] Remove unused log message
0cffb8a CMake 2.8.7 have problems with per target includes
b430cb0 Attempt to correct -I paths for backend test
6c0721d Attempt to correct -I paths for backend folders
69d70c5 [travis] Disable tempoarily soci-devel notifications
f4802f1 [travis] Log build script name
8890aea [travis] Set CMAKE_VERBOSE_MAKEFILE=ON
717c38a [travis] Remove superfluos ] from make invocation
843a43c Fix travis-ci to run CMake from root directory
9e43795 Update copyright year
da025df Merge branch 'feature/125-new-layout' into develop
04a34f2 Link presentation from London C++ Meeting
11ef1fa Fix doc/index.html menu links
5393ee5 Merge branch 'hotfix/3.2.2' into develop
b6d97ff Implement new source tree layout #125

git-subtree-dir: src/soci
git-subtree-split: b2855dce54340522f149221c6ebe2d14fd1129ba
2015-08-18 08:43:51 -07:00

4138 lines
121 KiB
C++

//
// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton
// 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_COMMON_TESTS_H_INCLUDED
#define SOCI_COMMON_TESTS_H_INCLUDED
#include "soci/soci.h"
#ifdef HAVE_BOOST
// explicitly pull conversions for Boost's optional, tuple and fusion:
#include <boost/version.hpp>
#include "soci/boost-optional.h"
#include "soci/boost-tuple.h"
#include "soci/boost-gregorian-date.h"
#if defined(BOOST_VERSION) && BOOST_VERSION >= 103500
#include "soci/boost-fusion.h"
#endif // BOOST_VERSION
#endif // HAVE_BOOST
#include "soci-compiler.h"
#define CATCH_CONFIG_RUNNER
#include <catch.hpp>
#include <algorithm>
#include <cassert>
#include <clocale>
#include <cstdlib>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <limits>
#include <string>
#include <typeinfo>
// Although SQL standard mandates right padding CHAR(N) values to their length
// with spaces, some backends don't confirm to it:
//
// - Firebird does pad the string but to the byte-size (not character size) of
// the column (i.e. CHAR(10) NONE is padded to 10 bytes but CHAR(10) UTF8 --
// to 40).
// - For MySql PAD_CHAR_TO_FULL_LENGTH option must be set, otherwise the value
// is trimmed.
// - SQLite never behaves correctly at all.
//
// This method will check result string from column defined as fixed char It
// will check only bytes up to the original string size. If padded string is
// bigger than expected string then all remaining chars must be spaces so if
// any non-space character is found it will fail.
void
checkEqualPadded(const std::string& padded_str, const std::string& expected_str)
{
size_t const len = expected_str.length();
std::string const start_str(padded_str, 0, len);
if (start_str != expected_str)
{
throw soci::soci_error(
"Expected string \"" + expected_str + "\" "
"is different from the padded string \"" + padded_str + "\""
);
}
if (padded_str.length() > len)
{
std::string const end_str(padded_str, len);
if (end_str != std::string(padded_str.length() - len, ' '))
{
throw soci::soci_error(
"\"" + padded_str + "\" starts with \"" + padded_str +
"\" but non-space characater(s) are found aftewards"
);
}
}
}
#define CHECK_EQUAL_PADDED(padded_str, expected_str) \
CHECK_NOTHROW(checkEqualPadded(padded_str, expected_str));
// Objects used later in tests 14,15
struct PhonebookEntry
{
std::string name;
std::string phone;
};
struct PhonebookEntry2 : public PhonebookEntry
{
};
class PhonebookEntry3
{
public:
void setName(std::string const & n) { name_ = n; }
std::string getName() const { return name_; }
void setPhone(std::string const & p) { phone_ = p; }
std::string getPhone() const { return phone_; }
public:
std::string name_;
std::string phone_;
};
// user-defined object for test26 and test28
class MyInt
{
public:
MyInt() {}
MyInt(int i) : i_(i) {}
void set(int i) { i_ = i; }
int get() const { return i_; }
private:
int i_;
};
namespace soci
{
// basic type conversion for user-defined type with single base value
template<> struct type_conversion<MyInt>
{
typedef int base_type;
static void from_base(int i, indicator ind, MyInt &mi)
{
if (ind == i_ok)
{
mi.set(i);
}
}
static void to_base(MyInt const &mi, int &i, indicator &ind)
{
i = mi.get();
ind = i_ok;
}
};
// basic type conversion on many values (ORM)
template<> struct type_conversion<PhonebookEntry>
{
typedef soci::values base_type;
static void from_base(values const &v, indicator /* ind */, PhonebookEntry &pe)
{
// here we ignore the possibility the the whole object might be NULL
pe.name = v.get<std::string>("NAME");
pe.phone = v.get<std::string>("PHONE", "<NULL>");
}
static void to_base(PhonebookEntry const &pe, values &v, indicator &ind)
{
v.set("NAME", pe.name);
v.set("PHONE", pe.phone, pe.phone.empty() ? i_null : i_ok);
ind = i_ok;
}
};
// type conversion which directly calls values::get_indicator()
template<> struct type_conversion<PhonebookEntry2>
{
typedef soci::values base_type;
static void from_base(values const &v, indicator /* ind */, PhonebookEntry2 &pe)
{
// here we ignore the possibility the the whole object might be NULL
pe.name = v.get<std::string>("NAME");
indicator ind = v.get_indicator("PHONE"); //another way to test for null
pe.phone = ind == i_null ? "<NULL>" : v.get<std::string>("PHONE");
}
static void to_base(PhonebookEntry2 const &pe, values &v, indicator &ind)
{
v.set("NAME", pe.name);
v.set("PHONE", pe.phone, pe.phone.empty() ? i_null : i_ok);
ind = i_ok;
}
};
template<> struct type_conversion<PhonebookEntry3>
{
typedef soci::values base_type;
static void from_base(values const &v, indicator /* ind */, PhonebookEntry3 &pe)
{
// here we ignore the possibility the the whole object might be NULL
pe.setName(v.get<std::string>("NAME"));
pe.setPhone(v.get<std::string>("PHONE", "<NULL>"));
}
static void to_base(PhonebookEntry3 const &pe, values &v, indicator &ind)
{
v.set("NAME", pe.getName());
v.set("PHONE", pe.getPhone(), pe.getPhone().empty() ? i_null : i_ok);
ind = i_ok;
}
};
} // namespace soci
namespace soci
{
namespace tests
{
// TODO: improve cleanup capabilities by subtypes, soci_test name may be omitted --mloskot
// i.e. optional ctor param accepting custom table name
class table_creator_base
{
public:
table_creator_base(session& sql)
: msession(sql) { drop(); }
virtual ~table_creator_base() { drop();}
private:
void drop()
{
try
{
msession << "drop table soci_test";
}
catch (soci_error const& e)
{
//std::cerr << e.what() << std::endl;
e.what();
}
}
session& msession;
SOCI_NOT_COPYABLE(table_creator_base)
};
class procedure_creator_base
{
public:
procedure_creator_base(session& sql)
: msession(sql) { drop(); }
virtual ~procedure_creator_base() { drop();}
private:
void drop()
{
try { msession << "drop procedure soci_test"; } catch (soci_error&) {}
}
session& msession;
SOCI_NOT_COPYABLE(procedure_creator_base)
};
class function_creator_base
{
public:
function_creator_base(session& sql)
: msession(sql) { drop(); }
virtual ~function_creator_base() { drop();}
protected:
virtual std::string dropstatement()
{
return "drop function soci_test";
}
private:
void drop()
{
try { msession << dropstatement(); } catch (soci_error&) {}
}
session& msession;
SOCI_NOT_COPYABLE(function_creator_base)
};
// This is a singleton class, at any given time there is at most one test
// context alive and common_tests fixture class uses it.
class test_context_base
{
public:
test_context_base(backend_factory const &backEnd,
std::string const &connectString)
: backEndFactory_(backEnd),
connectString_(connectString)
{
// This can't be a CHECK() because the test context is constructed
// outside of any test.
assert(!the_test_context_);
the_test_context_ = this;
// To allow running tests in non-default ("C") locale, the following
// environment variable can be set and then the current default locale
// (which can itself be changed by setting LC_ALL environment variable)
// will then be used.
if (std::getenv("SOCI_TEST_USE_LC_ALL"))
std::setlocale(LC_ALL, "");
}
static test_context_base const& get_instance()
{
REQUIRE(the_test_context_);
return *the_test_context_;
}
backend_factory const & get_backend_factory() const
{
return backEndFactory_;
}
std::string get_connect_string() const
{
return connectString_;
}
virtual std::string to_date_time(std::string const &dateTime) const = 0;
virtual table_creator_base* table_creator_1(session&) const = 0;
virtual table_creator_base* table_creator_2(session&) const = 0;
virtual table_creator_base* table_creator_3(session&) const = 0;
virtual table_creator_base* table_creator_4(session&) const = 0;
// Override this if the backend doesn't handle floating point values
// correctly, i.e. writing a value and reading it back doesn't return
// *exactly* the same value.
virtual bool has_fp_bug() const { return false; }
// Override this if the backend doesn't handle multiple active select
// statements at the same time, i.e. a result set must be entirely consumed
// before creating a new one (this is the case of MS SQL without MARS).
virtual bool has_multiple_select_bug() const { return false; }
// Override this if the backend may not have transactions support.
virtual bool has_transactions_support(session&) const { return true; }
// Override this if the backend silently truncates string values too long
// to fit by default.
virtual bool has_silent_truncate_bug(session&) const { return false; }
// Override this to call commit() if it's necessary for the DDL statements
// to be taken into account (currently this is only the case for Firebird).
virtual void on_after_ddl(session&) const { }
// Put the database in SQL-complient mode for CHAR(N) values, return false
// if it's impossible, i.e. if the database doesn't behave correctly
// whatever we do.
virtual bool enable_std_char_padding(session&) const { return true; }
virtual ~test_context_base()
{
the_test_context_ = NULL;
}
private:
backend_factory const &backEndFactory_;
std::string const connectString_;
static test_context_base* the_test_context_;
SOCI_NOT_COPYABLE(test_context_base)
};
// Currently all tests consist of just a single source file, so we can define
// this member here because this header is included exactly once.
tests::test_context_base* tests::test_context_base::the_test_context_ = NULL;
// Compare doubles for approximate equality. This has to be used everywhere
// where we write "3.14" (or "6.28") to the database as a string and then
// compare the value read back with the literal 3.14 floating point constant
// because they are not the same.
//
// It is also used for the backends which currently don't handle doubles
// correctly.
//
// Notice that this function is normally not used directly but rather from the
// macro below.
inline bool are_doubles_approx_equal(double const a, double const b)
{
// The formula taken from CATCH test framework
// https://github.com/philsquared/Catch/
// Thanks to Richard Harris for his help refining this formula
double const epsilon(std::numeric_limits<float>::epsilon() * 100);
double const scale(1.0);
return std::fabs(a - b) < epsilon * (scale + (std::max)(std::fabs(a), std::fabs(b)));
}
// This is a macro to ensure we use the correct line numbers. The weird
// do/while construction is used to make this a statement and the even weirder
// condition in while ensures that the loop is executed exactly once without
// triggering warnings from MSVC about the condition being always false.
#define ASSERT_EQUAL_APPROX(a, b) \
do { \
if (!are_doubles_approx_equal((a), (b))) { \
FAIL( "Approximate equality check failed: " \
<< std::fixed \
<< std::setprecision(std::numeric_limits<double>::digits10 + 1) \
<< (a) << " != " << (b) ); \
} \
} while ( (void)0, 0 )
// Exact double comparison function. We need one, instead of writing "a == b",
// only in order to have some place to put the pragmas disabling gcc warnings.
inline bool
are_doubles_exactly_equal(double a, double b)
{
// Avoid g++ warnings: we do really want the exact equality here.
GCC_WARNING_SUPPRESS(float-equal)
return a == b;
GCC_WARNING_RESTORE(float-equal)
}
#define ASSERT_EQUAL_EXACT(a, b) \
do { \
if (!are_doubles_exactly_equal((a), (b))) { \
FAIL( "Exact equality check failed: " \
<< std::fixed \
<< std::setprecision(std::numeric_limits<double>::digits10 + 1) \
<< (a) << " != " << (b) ); \
} \
} while ( (void)0, 0 )
// Compare two floating point numbers either exactly or approximately depending
// on test_context::has_fp_bug() return value.
inline bool
are_doubles_equal(test_context_base const& tc, double a, double b)
{
return tc.has_fp_bug()
? are_doubles_approx_equal(a, b)
: are_doubles_exactly_equal(a, b);
}
// This macro should be used when where we don't have any problems with string
// literals vs floating point literals mismatches described above and would
// ideally compare the numbers exactly but, unfortunately, currently can't do
// this unconditionally because at least some backends are currently buggy and
// don't handle the floating point values correctly.
//
// This can be only used from inside the common_tests class as it relies on
// having an accessible "tc_" variable to determine whether exact or
// approximate comparison should be used.
#define ASSERT_EQUAL(a, b) \
do { \
if (!are_doubles_equal(tc_, (a), (b))) { \
FAIL( "Equality check failed: " \
<< std::fixed \
<< std::setprecision(std::numeric_limits<double>::digits10 + 1) \
<< (a) << " != " << (b) ); \
} \
} while ( (void)0, 0 )
class common_tests
{
public:
common_tests()
: tc_(test_context_base::get_instance()),
backEndFactory_(tc_.get_backend_factory()),
connectString_(tc_.get_connect_string())
{}
protected:
test_context_base const & tc_;
backend_factory const &backEndFactory_;
std::string const connectString_;
SOCI_NOT_COPYABLE(common_tests)
};
typedef std::auto_ptr<table_creator_base> auto_table_creator;
// Define the test cases in their own namespace to avoid clashes with the test
// cases defined in individual backend tests: as only line number is used for
// building the name of the "anonymous" function by the TEST_CASE macro, we
// could have a conflict between a test defined here and in some backend if
// they happened to start on the same line.
namespace test_cases
{
TEST_CASE_METHOD(common_tests, "Exception on not connected", "[core][exception]")
{
soci::session sql; // no connection
// ensure connection is checked, no crash occurs
CHECK_THROWS_AS(sql.begin(), soci_error);
CHECK_THROWS_AS(sql.commit(), soci_error);
CHECK_THROWS_AS(sql.rollback(), soci_error);
CHECK_THROWS_AS(sql.get_backend_name(), soci_error);
CHECK_THROWS_AS(sql.make_statement_backend(), soci_error);
CHECK_THROWS_AS(sql.make_rowid_backend(), soci_error);
CHECK_THROWS_AS(sql.make_blob_backend(), soci_error);
std::string s;
long l;
CHECK_THROWS_AS(sql.get_next_sequence_value(s, l), soci_error);
CHECK_THROWS_AS(sql.get_last_insert_id(s, l), soci_error);
}
TEST_CASE_METHOD(common_tests, "Basic functionality")
{
soci::session sql(backEndFactory_, connectString_);
auto_table_creator tableCreator(tc_.table_creator_1(sql));
CHECK_THROWS_AS(sql << "drop table soci_test_nosuchtable", soci_error);
sql << "insert into soci_test (id) values (" << 123 << ")";
int id;
sql << "select id from soci_test", into(id);
CHECK(id == 123);
}
// "into" tests, type conversions, etc.
TEST_CASE_METHOD(common_tests, "Use and into", "[core][into]")
{
soci::session sql(backEndFactory_, connectString_);
auto_table_creator tableCreator(tc_.table_creator_1(sql));
SECTION("Round trip works for char")
{
char c('a');
sql << "insert into soci_test(c) values(:c)", use(c);
sql << "select c from soci_test", into(c);
CHECK(c == 'a');
}
SECTION("Round trip works for string")
{
std::string helloSOCI("Hello, SOCI!");
sql << "insert into soci_test(str) values(:s)", use(helloSOCI);
std::string str;
sql << "select str from soci_test", into(str);
CHECK(str == "Hello, SOCI!");
}
SECTION("Round trip works for short")
{
short three(3);
sql << "insert into soci_test(sh) values(:id)", use(three);
short sh(0);
sql << "select sh from soci_test", into(sh);
CHECK(sh == 3);
}
SECTION("Round trip works for int")
{
int five(5);
sql << "insert into soci_test(id) values(:id)", use(five);
int i(0);
sql << "select id from soci_test", into(i);
CHECK(i == 5);
}
SECTION("Round trip works for unsigned long")
{
unsigned long seven(7);
sql << "insert into soci_test(ul) values(:ul)", use(seven);
unsigned long ul(0);
sql << "select ul from soci_test", into(ul);
CHECK(ul == 7);
}
SECTION("Round trip works for double")
{
double pi(3.14159265);
sql << "insert into soci_test(d) values(:d)", use(pi);
double d(0.0);
sql << "select d from soci_test", into(d);
ASSERT_EQUAL(d, pi);
}
SECTION("Round trip works for date without time")
{
std::tm nov15;
nov15.tm_year = 105;
nov15.tm_mon = 10;
nov15.tm_mday = 15;
nov15.tm_hour = 0;
nov15.tm_min = 0;
nov15.tm_sec = 0;
sql << "insert into soci_test(tm) values(:tm)", use(nov15);
std::tm t;
sql << "select tm from soci_test", into(t);
CHECK(t.tm_year == 105);
CHECK(t.tm_mon == 10);
CHECK(t.tm_mday == 15);
CHECK(t.tm_hour == 0);
CHECK(t.tm_min == 0);
CHECK(t.tm_sec == 0);
}
SECTION("Round trip works for date with time")
{
std::tm nov15;
nov15.tm_year = 105;
nov15.tm_mon = 10;
nov15.tm_mday = 15;
nov15.tm_hour = 22;
nov15.tm_min = 14;
nov15.tm_sec = 17;
sql << "insert into soci_test(tm) values(:tm)", use(nov15);
std::tm t;
sql << "select tm from soci_test", into(t);
CHECK(t.tm_year == 105);
CHECK(t.tm_mon == 10);
CHECK(t.tm_mday == 15);
CHECK(t.tm_hour == 22);
CHECK(t.tm_min == 14);
CHECK(t.tm_sec == 17);
}
SECTION("Indicator is filled correctly in the simplest case")
{
int id(1);
std::string str("Hello");
sql << "insert into soci_test(id, str) values(:id, :str)",
use(id), use(str);
int i;
indicator ind;
sql << "select id from soci_test", into(i, ind);
CHECK(ind == i_ok);
}
SECTION("Indicators work correctly more generally")
{
sql << "insert into soci_test(id,tm) values(NULL,NULL)";
int i;
indicator ind;
sql << "select id from soci_test", into(i, ind);
CHECK(ind == i_null);
// additional test for NULL with std::tm
std::tm t;
sql << "select tm from soci_test", into(t, ind);
CHECK(ind == i_null);
try
{
// expect error
sql << "select id from soci_test", into(i);
FAIL("expected exception not thrown");
}
catch (soci_error const &e)
{
CHECK(e.get_error_message() ==
"Null value fetched and no indicator defined.");
}
sql << "select id from soci_test where id = 1000", into(i, ind);
CHECK(sql.got_data() == false);
// no data expected
sql << "select id from soci_test where id = 1000", into(i);
CHECK(sql.got_data() == false);
// no data expected, test correct behaviour with use
int id = 1000;
sql << "select id from soci_test where id = :id", use(id), into(i);
CHECK(sql.got_data() == false);
}
}
// repeated fetch and bulk fetch
TEST_CASE_METHOD(common_tests, "Repeated and bulk fetch", "[core][bulk]")
{
soci::session sql(backEndFactory_, connectString_);
// create and populate the test table
auto_table_creator tableCreator(tc_.table_creator_1(sql));
SECTION("char")
{
char c;
for (c = 'a'; c <= 'z'; ++c)
{
sql << "insert into soci_test(c) values(\'" << c << "\')";
}
int count;
sql << "select count(*) from soci_test", into(count);
CHECK(count == 'z' - 'a' + 1);
{
char c2 = 'a';
statement st = (sql.prepare <<
"select c from soci_test order by c", into(c));
st.execute();
while (st.fetch())
{
CHECK(c == c2);
++c2;
}
CHECK(c2 == 'a' + count);
}
{
char c2 = 'a';
std::vector<char> vec(10);
statement st = (sql.prepare <<
"select c from soci_test order by c", into(vec));
st.execute();
while (st.fetch())
{
for (std::size_t i = 0; i != vec.size(); ++i)
{
CHECK(c2 == vec[i]);
++c2;
}
vec.resize(10);
}
CHECK(c2 == 'a' + count);
}
{
// verify an exception is thrown when empty vector is used
std::vector<char> vec;
try
{
sql << "select c from soci_test", into(vec);
FAIL("expected exception not thrown");
}
catch (soci_error const &e)
{
CHECK(e.get_error_message() ==
"Vectors of size 0 are not allowed.");
}
}
}
// repeated fetch and bulk fetch of std::string
SECTION("std::string")
{
int const rowsToTest = 10;
for (int i = 0; i != rowsToTest; ++i)
{
std::ostringstream ss;
ss << "Hello_" << i;
sql << "insert into soci_test(str) values(\'"
<< ss.str() << "\')";
}
int count;
sql << "select count(*) from soci_test", into(count);
CHECK(count == rowsToTest);
{
int i = 0;
std::string s;
statement st = (sql.prepare <<
"select str from soci_test order by str", into(s));
st.execute();
while (st.fetch())
{
std::ostringstream ss;
ss << "Hello_" << i;
CHECK(s == ss.str());
++i;
}
CHECK(i == rowsToTest);
}
{
int i = 0;
std::vector<std::string> vec(4);
statement st = (sql.prepare <<
"select str from soci_test order by str", into(vec));
st.execute();
while (st.fetch())
{
for (std::size_t j = 0; j != vec.size(); ++j)
{
std::ostringstream ss;
ss << "Hello_" << i;
CHECK(ss.str() == vec[j]);
++i;
}
vec.resize(4);
}
CHECK(i == rowsToTest);
}
}
SECTION("short")
{
short const rowsToTest = 100;
short sh;
for (sh = 0; sh != rowsToTest; ++sh)
{
sql << "insert into soci_test(sh) values(" << sh << ")";
}
int count;
sql << "select count(*) from soci_test", into(count);
CHECK(count == rowsToTest);
{
short sh2 = 0;
statement st = (sql.prepare <<
"select sh from soci_test order by sh", into(sh));
st.execute();
while (st.fetch())
{
CHECK(sh == sh2);
++sh2;
}
CHECK(sh2 == rowsToTest);
}
{
short sh2 = 0;
std::vector<short> vec(8);
statement st = (sql.prepare <<
"select sh from soci_test order by sh", into(vec));
st.execute();
while (st.fetch())
{
for (std::size_t i = 0; i != vec.size(); ++i)
{
CHECK(sh2 == vec[i]);
++sh2;
}
vec.resize(8);
}
CHECK(sh2 == rowsToTest);
}
}
SECTION("int")
{
int const rowsToTest = 100;
int i;
for (i = 0; i != rowsToTest; ++i)
{
sql << "insert into soci_test(id) values(" << i << ")";
}
int count;
sql << "select count(*) from soci_test", into(count);
CHECK(count == rowsToTest);
{
int i2 = 0;
statement st = (sql.prepare <<
"select id from soci_test order by id", into(i));
st.execute();
while (st.fetch())
{
CHECK(i == i2);
++i2;
}
CHECK(i2 == rowsToTest);
}
{
// additional test with the use element
int i2 = 0;
int cond = 0; // this condition is always true
statement st = (sql.prepare <<
"select id from soci_test where id >= :cond order by id",
use(cond), into(i));
st.execute();
while (st.fetch())
{
CHECK(i == i2);
++i2;
}
CHECK(i2 == rowsToTest);
}
{
int i2 = 0;
std::vector<int> vec(8);
statement st = (sql.prepare <<
"select id from soci_test order by id", into(vec));
st.execute();
while (st.fetch())
{
for (std::size_t i = 0; i != vec.size(); ++i)
{
CHECK(i2 == vec[i]);
++i2;
}
vec.resize(8);
}
CHECK(i2 == rowsToTest);
}
}
SECTION("unsigned int")
{
unsigned int const rowsToTest = 100;
unsigned int ul;
for (ul = 0; ul != rowsToTest; ++ul)
{
sql << "insert into soci_test(ul) values(" << ul << ")";
}
int count;
sql << "select count(*) from soci_test", into(count);
CHECK(count == static_cast<int>(rowsToTest));
{
unsigned int ul2 = 0;
statement st = (sql.prepare <<
"select ul from soci_test order by ul", into(ul));
st.execute();
while (st.fetch())
{
CHECK(ul == ul2);
++ul2;
}
CHECK(ul2 == rowsToTest);
}
{
unsigned int ul2 = 0;
std::vector<unsigned int> vec(8);
statement st = (sql.prepare <<
"select ul from soci_test order by ul", into(vec));
st.execute();
while (st.fetch())
{
for (std::size_t i = 0; i != vec.size(); ++i)
{
CHECK(ul2 == vec[i]);
++ul2;
}
vec.resize(8);
}
CHECK(ul2 == rowsToTest);
}
}
SECTION("double")
{
int const rowsToTest = 100;
double d = 0.0;
statement st = (sql.prepare <<
"insert into soci_test(d) values(:d)", use(d));
for (int i = 0; i != rowsToTest; ++i)
{
st.execute(true);
d += 0.6;
}
int count;
sql << "select count(*) from soci_test", into(count);
CHECK(count == rowsToTest);
{
double d2 = 0.0;
int i = 0;
statement st = (sql.prepare <<
"select d from soci_test order by d", into(d));
st.execute();
while (st.fetch())
{
ASSERT_EQUAL(d, d2);
d2 += 0.6;
++i;
}
CHECK(i == rowsToTest);
}
{
double d2 = 0.0;
int i = 0;
std::vector<double> vec(8);
statement st = (sql.prepare <<
"select d from soci_test order by d", into(vec));
st.execute();
while (st.fetch())
{
for (std::size_t j = 0; j != vec.size(); ++j)
{
ASSERT_EQUAL(d2, vec[j]);
d2 += 0.6;
++i;
}
vec.resize(8);
}
CHECK(i == rowsToTest);
}
}
SECTION("std::tm")
{
int const rowsToTest = 8;
for (int i = 0; i != rowsToTest; ++i)
{
std::ostringstream ss;
ss << 2000 + i << "-0" << 1 + i << '-' << 20 - i << ' '
<< 15 + i << ':' << 50 - i << ':' << 40 + i;
sql << "insert into soci_test(id, tm) values(" << i
<< ", " << tc_.to_date_time(ss.str()) << ")";
}
int count;
sql << "select count(*) from soci_test", into(count);
CHECK(count == rowsToTest);
{
std::tm t;
int i = 0;
statement st = (sql.prepare <<
"select tm from soci_test order by id", into(t));
st.execute();
while (st.fetch())
{
CHECK(t.tm_year == 2000 - 1900 + i);
CHECK(t.tm_mon == i);
CHECK(t.tm_mday == 20 - i);
CHECK(t.tm_hour == 15 + i);
CHECK(t.tm_min == 50 - i);
CHECK(t.tm_sec == 40 + i);
++i;
}
CHECK(i == rowsToTest);
}
{
int i = 0;
std::vector<std::tm> vec(3);
statement st = (sql.prepare <<
"select tm from soci_test order by id", into(vec));
st.execute();
while (st.fetch())
{
for (std::size_t j = 0; j != vec.size(); ++j)
{
CHECK(vec[j].tm_year == 2000 - 1900 + i);
CHECK(vec[j].tm_mon == i);
CHECK(vec[j].tm_mday == 20 - i);
CHECK(vec[j].tm_hour == 15 + i);
CHECK(vec[j].tm_min == 50 - i);
CHECK(vec[j].tm_sec == 40 + i);
++i;
}
vec.resize(3);
}
CHECK(i == rowsToTest);
}
}
}
// test for indicators (repeated fetch and bulk)
TEST_CASE_METHOD(common_tests, "Indicators", "[core][indicator]")
{
soci::session sql(backEndFactory_, connectString_);
// create and populate the test table
auto_table_creator tableCreator(tc_.table_creator_1(sql));
{
sql << "insert into soci_test(id, val) values(1, 10)";
sql << "insert into soci_test(id, val) values(2, 11)";
sql << "insert into soci_test(id, val) values(3, NULL)";
sql << "insert into soci_test(id, val) values(4, NULL)";
sql << "insert into soci_test(id, val) values(5, 12)";
{
int val;
indicator ind;
statement st = (sql.prepare <<
"select val from soci_test order by id", into(val, ind));
st.execute();
bool gotData = st.fetch();
CHECK(gotData);
CHECK(ind == i_ok);
CHECK(val == 10);
gotData = st.fetch();
CHECK(gotData);
CHECK(ind == i_ok);
CHECK(val == 11);
gotData = st.fetch();
CHECK(gotData);
CHECK(ind == i_null);
gotData = st.fetch();
CHECK(gotData);
CHECK(ind == i_null);
gotData = st.fetch();
CHECK(gotData);
CHECK(ind == i_ok);
CHECK(val == 12);
gotData = st.fetch();
CHECK(gotData == false);
}
{
std::vector<int> vals(3);
std::vector<indicator> inds(3);
statement st = (sql.prepare <<
"select val from soci_test order by id", into(vals, inds));
st.execute();
bool gotData = st.fetch();
CHECK(gotData);
CHECK(vals.size() == 3);
CHECK(inds.size() == 3);
CHECK(inds[0] == i_ok);
CHECK(vals[0] == 10);
CHECK(inds[1] == i_ok);
CHECK(vals[1] == 11);
CHECK(inds[2] == i_null);
gotData = st.fetch();
CHECK(gotData);
CHECK(vals.size() == 2);
CHECK(inds[0] == i_null);
CHECK(inds[1] == i_ok);
CHECK(vals[1] == 12);
gotData = st.fetch();
CHECK(gotData == false);
}
// additional test for "no data" condition
{
std::vector<int> vals(3);
std::vector<indicator> inds(3);
statement st = (sql.prepare <<
"select val from soci_test where 0 = 1", into(vals, inds));
bool gotData = st.execute(true);
CHECK(gotData == false);
// for convenience, vectors should be truncated
CHECK(vals.empty());
CHECK(inds.empty());
// for even more convenience, fetch should not fail
// but just report end of rowset
// (and vectors should be truncated)
vals.resize(1);
inds.resize(1);
gotData = st.fetch();
CHECK(gotData == false);
CHECK(vals.empty());
CHECK(inds.empty());
}
// additional test for "no data" without prepared statement
{
std::vector<int> vals(3);
std::vector<indicator> inds(3);
sql << "select val from soci_test where 0 = 1",
into(vals, inds);
// vectors should be truncated
CHECK(vals.empty());
CHECK(inds.empty());
}
}
}
// test for different sizes of data vector and indicators vector
// (library should force ind. vector to have same size as data vector)
TEST_CASE_METHOD(common_tests, "Indicators vector", "[core][indicator][vector]")
{
soci::session sql(backEndFactory_, connectString_);
// create and populate the test table
auto_table_creator tableCreator(tc_.table_creator_1(sql));
{
sql << "insert into soci_test(id, val) values(1, 10)";
sql << "insert into soci_test(id, val) values(2, 11)";
sql << "insert into soci_test(id, val) values(3, NULL)";
sql << "insert into soci_test(id, val) values(4, NULL)";
sql << "insert into soci_test(id, val) values(5, 12)";
{
std::vector<int> vals(4);
std::vector<indicator> inds;
statement st = (sql.prepare <<
"select val from soci_test order by id", into(vals, inds));
st.execute();
st.fetch();
CHECK(vals.size() == 4);
CHECK(inds.size() == 4);
vals.resize(3);
st.fetch();
CHECK(vals.size() == 1);
CHECK(inds.size() == 1);
}
}
}
// Note: this functionality is not available with older PostgreSQL
#ifndef SOCI_POSTGRESQL_NOPARAMS
// "use" tests, type conversions, etc.
TEST_CASE_METHOD(common_tests, "Use type conversion", "[core][use]")
{
soci::session sql(backEndFactory_, connectString_);
auto_table_creator tableCreator(tc_.table_creator_1(sql));
SECTION("char")
{
char c('a');
sql << "insert into soci_test(c) values(:c)", use(c);
c = 'b';
sql << "select c from soci_test", into(c);
CHECK(c == 'a');
}
SECTION("std::string")
{
std::string s = "Hello SOCI!";
sql << "insert into soci_test(str) values(:s)", use(s);
std::string str;
sql << "select str from soci_test", into(str);
CHECK(str == "Hello SOCI!");
}
SECTION("short")
{
short s = 123;
sql << "insert into soci_test(id) values(:id)", use(s);
short s2 = 0;
sql << "select id from soci_test", into(s2);
CHECK(s2 == 123);
}
SECTION("int")
{
int i = -12345678;
sql << "insert into soci_test(id) values(:i)", use(i);
int i2 = 0;
sql << "select id from soci_test", into(i2);
CHECK(i2 == -12345678);
}
SECTION("unsigned long")
{
unsigned long ul = 4000000000ul;
sql << "insert into soci_test(ul) values(:num)", use(ul);
unsigned long ul2 = 0;
sql << "select ul from soci_test", into(ul2);
CHECK(ul2 == 4000000000ul);
}
SECTION("double")
{
double d = 3.14159265;
sql << "insert into soci_test(d) values(:d)", use(d);
double d2 = 0;
sql << "select d from soci_test", into(d2);
ASSERT_EQUAL(d2, d);
}
SECTION("std::tm")
{
std::tm t;
t.tm_year = 105;
t.tm_mon = 10;
t.tm_mday = 19;
t.tm_hour = 21;
t.tm_min = 39;
t.tm_sec = 57;
sql << "insert into soci_test(tm) values(:t)", use(t);
std::tm t2;
t2.tm_year = 0;
t2.tm_mon = 0;
t2.tm_mday = 0;
t2.tm_hour = 0;
t2.tm_min = 0;
t2.tm_sec = 0;
sql << "select tm from soci_test", into(t2);
CHECK(t.tm_year == 105);
CHECK(t.tm_mon == 10);
CHECK(t.tm_mday == 19);
CHECK(t.tm_hour == 21);
CHECK(t.tm_min == 39);
CHECK(t.tm_sec == 57);
}
SECTION("repeated use")
{
int i;
statement st = (sql.prepare
<< "insert into soci_test(id) values(:id)", use(i));
i = 5;
st.execute(true);
i = 6;
st.execute(true);
i = 7;
st.execute(true);
std::vector<int> v(5);
sql << "select id from soci_test order by id", into(v);
CHECK(v.size() == 3);
CHECK(v[0] == 5);
CHECK(v[1] == 6);
CHECK(v[2] == 7);
}
// tests for use of const objects
SECTION("const char")
{
char const c('a');
sql << "insert into soci_test(c) values(:c)", use(c);
char c2 = 'b';
sql << "select c from soci_test", into(c2);
CHECK(c2 == 'a');
}
SECTION("const std::string")
{
std::string const s = "Hello const SOCI!";
sql << "insert into soci_test(str) values(:s)", use(s);
std::string str;
sql << "select str from soci_test", into(str);
CHECK(str == "Hello const SOCI!");
}
SECTION("const short")
{
short const s = 123;
sql << "insert into soci_test(id) values(:id)", use(s);
short s2 = 0;
sql << "select id from soci_test", into(s2);
CHECK(s2 == 123);
}
SECTION("const int")
{
int const i = -12345678;
sql << "insert into soci_test(id) values(:i)", use(i);
int i2 = 0;
sql << "select id from soci_test", into(i2);
CHECK(i2 == -12345678);
}
SECTION("const unsigned long")
{
unsigned long const ul = 4000000000ul;
sql << "insert into soci_test(ul) values(:num)", use(ul);
unsigned long ul2 = 0;
sql << "select ul from soci_test", into(ul2);
CHECK(ul2 == 4000000000ul);
}
SECTION("const double")
{
double const d = 3.14159265;
sql << "insert into soci_test(d) values(:d)", use(d);
double d2 = 0;
sql << "select d from soci_test", into(d2);
ASSERT_EQUAL(d2, d);
}
SECTION("const std::tm")
{
std::tm t;
t.tm_year = 105;
t.tm_mon = 10;
t.tm_mday = 19;
t.tm_hour = 21;
t.tm_min = 39;
t.tm_sec = 57;
std::tm const & ct = t;
sql << "insert into soci_test(tm) values(:t)", use(ct);
std::tm t2;
t2.tm_year = 0;
t2.tm_mon = 0;
t2.tm_mday = 0;
t2.tm_hour = 0;
t2.tm_min = 0;
t2.tm_sec = 0;
sql << "select tm from soci_test", into(t2);
CHECK(t.tm_year == 105);
CHECK(t.tm_mon == 10);
CHECK(t.tm_mday == 19);
CHECK(t.tm_hour == 21);
CHECK(t.tm_min == 39);
CHECK(t.tm_sec == 57);
}
}
#endif // SOCI_POSTGRESQL_NOPARAMS
// test for multiple use (and into) elements
TEST_CASE_METHOD(common_tests, "Multiple use and into", "[core][use][into]")
{
soci::session sql(backEndFactory_, connectString_);
auto_table_creator tableCreator(tc_.table_creator_1(sql));
{
int i1 = 5;
int i2 = 6;
int i3 = 7;
#ifndef SOCI_POSTGRESQL_NOPARAMS
sql << "insert into soci_test(i1, i2, i3) values(:i1, :i2, :i3)",
use(i1), use(i2), use(i3);
#else
// Older PostgreSQL does not support use elements.
sql << "insert into soci_test(i1, i2, i3) values(5, 6, 7)";
#endif // SOCI_POSTGRESQL_NOPARAMS
i1 = 0;
i2 = 0;
i3 = 0;
sql << "select i1, i2, i3 from soci_test",
into(i1), into(i2), into(i3);
CHECK(i1 == 5);
CHECK(i2 == 6);
CHECK(i3 == 7);
// same for vectors
sql << "delete from soci_test";
i1 = 0;
i2 = 0;
i3 = 0;
#ifndef SOCI_POSTGRESQL_NOPARAMS
statement st = (sql.prepare
<< "insert into soci_test(i1, i2, i3) values(:i1, :i2, :i3)",
use(i1), use(i2), use(i3));
i1 = 1;
i2 = 2;
i3 = 3;
st.execute(true);
i1 = 4;
i2 = 5;
i3 = 6;
st.execute(true);
i1 = 7;
i2 = 8;
i3 = 9;
st.execute(true);
#else
// Older PostgreSQL does not support use elements.
sql << "insert into soci_test(i1, i2, i3) values(1, 2, 3)";
sql << "insert into soci_test(i1, i2, i3) values(4, 5, 6)";
sql << "insert into soci_test(i1, i2, i3) values(7, 8, 9)";
#endif // SOCI_POSTGRESQL_NOPARAMS
std::vector<int> v1(5);
std::vector<int> v2(5);
std::vector<int> v3(5);
sql << "select i1, i2, i3 from soci_test order by i1",
into(v1), into(v2), into(v3);
CHECK(v1.size() == 3);
CHECK(v2.size() == 3);
CHECK(v3.size() == 3);
CHECK(v1[0] == 1);
CHECK(v1[1] == 4);
CHECK(v1[2] == 7);
CHECK(v2[0] == 2);
CHECK(v2[1] == 5);
CHECK(v2[2] == 8);
CHECK(v3[0] == 3);
CHECK(v3[1] == 6);
CHECK(v3[2] == 9);
}
}
// Not supported with older PostgreSQL
#ifndef SOCI_POSTGRESQL_NOPARAMS
// use vector elements
TEST_CASE_METHOD(common_tests, "Use vector", "[core][use][vector]")
{
soci::session sql(backEndFactory_, connectString_);
auto_table_creator tableCreator(tc_.table_creator_1(sql));
SECTION("char")
{
std::vector<char> v;
v.push_back('a');
v.push_back('b');
v.push_back('c');
v.push_back('d');
sql << "insert into soci_test(c) values(:c)", use(v);
std::vector<char> v2(4);
sql << "select c from soci_test order by c", into(v2);
CHECK(v2.size() == 4);
CHECK(v2[0] == 'a');
CHECK(v2[1] == 'b');
CHECK(v2[2] == 'c');
CHECK(v2[3] == 'd');
}
SECTION("std::string")
{
std::vector<std::string> v;
v.push_back("ala");
v.push_back("ma");
v.push_back("kota");
sql << "insert into soci_test(str) values(:s)", use(v);
std::vector<std::string> v2(4);
sql << "select str from soci_test order by str", into(v2);
CHECK(v2.size() == 3);
CHECK(v2[0] == "ala");
CHECK(v2[1] == "kota");
CHECK(v2[2] == "ma");
}
SECTION("short")
{
std::vector<short> v;
v.push_back(-5);
v.push_back(6);
v.push_back(7);
v.push_back(123);
sql << "insert into soci_test(sh) values(:sh)", use(v);
std::vector<short> v2(4);
sql << "select sh from soci_test order by sh", into(v2);
CHECK(v2.size() == 4);
CHECK(v2[0] == -5);
CHECK(v2[1] == 6);
CHECK(v2[2] == 7);
CHECK(v2[3] == 123);
}
SECTION("int")
{
std::vector<int> v;
v.push_back(-2000000000);
v.push_back(0);
v.push_back(1);
v.push_back(2000000000);
sql << "insert into soci_test(id) values(:i)", use(v);
std::vector<int> v2(4);
sql << "select id from soci_test order by id", into(v2);
CHECK(v2.size() == 4);
CHECK(v2[0] == -2000000000);
CHECK(v2[1] == 0);
CHECK(v2[2] == 1);
CHECK(v2[3] == 2000000000);
}
SECTION("unsigned int")
{
std::vector<unsigned int> v;
v.push_back(0);
v.push_back(1);
v.push_back(123);
v.push_back(1000);
sql << "insert into soci_test(ul) values(:ul)", use(v);
std::vector<unsigned int> v2(4);
sql << "select ul from soci_test order by ul", into(v2);
CHECK(v2.size() == 4);
CHECK(v2[0] == 0);
CHECK(v2[1] == 1);
CHECK(v2[2] == 123);
CHECK(v2[3] == 1000);
}
SECTION("double")
{
std::vector<double> v;
v.push_back(0);
v.push_back(-0.0001);
v.push_back(0.0001);
v.push_back(3.1415926);
sql << "insert into soci_test(d) values(:d)", use(v);
std::vector<double> v2(4);
sql << "select d from soci_test order by d", into(v2);
CHECK(v2.size() == 4);
ASSERT_EQUAL(v2[0],-0.0001);
ASSERT_EQUAL(v2[1], 0);
ASSERT_EQUAL(v2[2], 0.0001);
ASSERT_EQUAL(v2[3], 3.1415926);
}
SECTION("std::tm")
{
std::vector<std::tm> v;
std::tm t;
t.tm_year = 105;
t.tm_mon = 10;
t.tm_mday = 26;
t.tm_hour = 22;
t.tm_min = 45;
t.tm_sec = 17;
v.push_back(t);
t.tm_sec = 37;
v.push_back(t);
t.tm_mday = 25;
v.push_back(t);
sql << "insert into soci_test(tm) values(:t)", use(v);
std::vector<std::tm> v2(4);
sql << "select tm from soci_test order by tm", into(v2);
CHECK(v2.size() == 3);
CHECK(v2[0].tm_year == 105);
CHECK(v2[0].tm_mon == 10);
CHECK(v2[0].tm_mday == 25);
CHECK(v2[0].tm_hour == 22);
CHECK(v2[0].tm_min == 45);
CHECK(v2[0].tm_sec == 37);
CHECK(v2[1].tm_year == 105);
CHECK(v2[1].tm_mon == 10);
CHECK(v2[1].tm_mday == 26);
CHECK(v2[1].tm_hour == 22);
CHECK(v2[1].tm_min == 45);
CHECK(v2[1].tm_sec == 17);
CHECK(v2[2].tm_year == 105);
CHECK(v2[2].tm_mon == 10);
CHECK(v2[2].tm_mday == 26);
CHECK(v2[2].tm_hour == 22);
CHECK(v2[2].tm_min == 45);
CHECK(v2[2].tm_sec == 37);
}
SECTION("const int")
{
std::vector<int> v;
v.push_back(-2000000000);
v.push_back(0);
v.push_back(1);
v.push_back(2000000000);
std::vector<int> const & cv = v;
sql << "insert into soci_test(id) values(:i)", use(cv);
std::vector<int> v2(4);
sql << "select id from soci_test order by id", into(v2);
CHECK(v2.size() == 4);
CHECK(v2[0] == -2000000000);
CHECK(v2[1] == 0);
CHECK(v2[2] == 1);
CHECK(v2[3] == 2000000000);
}
}
// test for named binding
TEST_CASE_METHOD(common_tests, "Named parameters", "[core][use][named-params]")
{
soci::session sql(backEndFactory_, connectString_);
{
auto_table_creator tableCreator(tc_.table_creator_1(sql));
int i1 = 7;
int i2 = 8;
// verify the exception is thrown if both by position
// and by name use elements are specified
try
{
sql << "insert into soci_test(i1, i2) values(:i1, :i2)",
use(i1, "i1"), use(i2);
FAIL("expected exception not thrown");
}
catch (soci_error const& e)
{
CHECK(e.get_error_message() ==
"Binding for use elements must be either by position "
"or by name.");
}
// normal test
sql << "insert into soci_test(i1, i2) values(:i1, :i2)",
use(i1, "i1"), use(i2, "i2");
i1 = 0;
i2 = 0;
sql << "select i1, i2 from soci_test", into(i1), into(i2);
CHECK(i1 == 7);
CHECK(i2 == 8);
i2 = 0;
sql << "select i2 from soci_test where i1 = :i1", into(i2), use(i1);
CHECK(i2 == 8);
sql << "delete from soci_test";
// test vectors
std::vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
std::vector<int> v2;
v2.push_back(4);
v2.push_back(5);
v2.push_back(6);
sql << "insert into soci_test(i1, i2) values(:i1, :i2)",
use(v1, "i1"), use(v2, "i2");
sql << "select i2, i1 from soci_test order by i1 desc",
into(v1), into(v2);
CHECK(v1.size() == 3);
CHECK(v2.size() == 3);
CHECK(v1[0] == 6);
CHECK(v1[1] == 5);
CHECK(v1[2] == 4);
CHECK(v2[0] == 3);
CHECK(v2[1] == 2);
CHECK(v2[2] == 1);
}
}
#endif // SOCI_POSTGRESQL_NOPARAMS
// transaction test
TEST_CASE_METHOD(common_tests, "Transactions", "[core][transaction]")
{
soci::session sql(backEndFactory_, connectString_);
if (!tc_.has_transactions_support(sql))
{
WARN("Transactions not supported by the database, skipping the test.");
return;
}
auto_table_creator tableCreator(tc_.table_creator_1(sql));
int count;
sql << "select count(*) from soci_test", into(count);
CHECK(count == 0);
{
transaction tr(sql);
sql << "insert into soci_test (id, name) values(1, 'John')";
sql << "insert into soci_test (id, name) values(2, 'Anna')";
sql << "insert into soci_test (id, name) values(3, 'Mike')";
tr.commit();
}
{
transaction tr(sql);
sql << "select count(*) from soci_test", into(count);
CHECK(count == 3);
sql << "insert into soci_test (id, name) values(4, 'Stan')";
sql << "select count(*) from soci_test", into(count);
CHECK(count == 4);
tr.rollback();
sql << "select count(*) from soci_test", into(count);
CHECK(count == 3);
}
{
transaction tr(sql);
sql << "delete from soci_test";
sql << "select count(*) from soci_test", into(count);
CHECK(count == 0);
tr.rollback();
sql << "select count(*) from soci_test", into(count);
CHECK(count == 3);
}
{
// additional test for detection of double commit
transaction tr(sql);
tr.commit();
try
{
tr.commit();
FAIL("expected exception not thrown");
}
catch (soci_error const &e)
{
CHECK(e.get_error_message() ==
"The transaction object cannot be handled twice.");
}
}
}
#ifndef SOCI_POSTGRESQL_NOPARAMS
// test of use elements with indicators
TEST_CASE_METHOD(common_tests, "Use with indicators", "[core][use][indicator]")
{
soci::session sql(backEndFactory_, connectString_);
auto_table_creator tableCreator(tc_.table_creator_1(sql));
indicator ind1 = i_ok;
indicator ind2 = i_ok;
int id = 1;
int val = 10;
sql << "insert into soci_test(id, val) values(:id, :val)",
use(id, ind1), use(val, ind2);
id = 2;
val = 11;
ind2 = i_null;
sql << "insert into soci_test(id, val) values(:id, :val)",
use(id, ind1), use(val, ind2);
sql << "select val from soci_test where id = 1", into(val, ind2);
CHECK(ind2 == i_ok);
CHECK(val == 10);
sql << "select val from soci_test where id = 2", into(val, ind2);
CHECK(ind2 == i_null);
std::vector<int> ids;
ids.push_back(3);
ids.push_back(4);
ids.push_back(5);
std::vector<int> vals;
vals.push_back(12);
vals.push_back(13);
vals.push_back(14);
std::vector<indicator> inds;
inds.push_back(i_ok);
inds.push_back(i_null);
inds.push_back(i_ok);
sql << "insert into soci_test(id, val) values(:id, :val)",
use(ids), use(vals, inds);
ids.resize(5);
vals.resize(5);
sql << "select id, val from soci_test order by id desc",
into(ids), into(vals, inds);
CHECK(ids.size() == 5);
CHECK(ids[0] == 5);
CHECK(ids[1] == 4);
CHECK(ids[2] == 3);
CHECK(ids[3] == 2);
CHECK(ids[4] == 1);
CHECK(inds.size() == 5);
CHECK(inds[0] == i_ok);
CHECK(inds[1] == i_null);
CHECK(inds[2] == i_ok);
CHECK(inds[3] == i_null);
CHECK(inds[4] == i_ok);
CHECK(vals.size() == 5);
CHECK(vals[0] == 14);
CHECK(vals[2] == 12);
CHECK(vals[4] == 10);
}
#endif // SOCI_POSTGRESQL_NOPARAMS
// Dynamic binding to Row objects
TEST_CASE_METHOD(common_tests, "Dynamic row binding", "[core][dynamic]")
{
soci::session sql(backEndFactory_, connectString_);
sql.uppercase_column_names(true);
auto_table_creator tableCreator(tc_.table_creator_2(sql));
row r;
sql << "select * from soci_test", into(r);
CHECK(sql.got_data() == false);
sql << "insert into soci_test"
" values(3.14, 123, \'Johny\',"
<< tc_.to_date_time("2005-12-19 22:14:17")
<< ", 'a')";
// select into a row
{
row r;
statement st = (sql.prepare <<
"select * from soci_test", into(r));
st.execute(true);
CHECK(r.size() == 5);
CHECK(r.get_properties(0).get_data_type() == dt_double);
CHECK(r.get_properties(1).get_data_type() == dt_integer);
CHECK(r.get_properties(2).get_data_type() == dt_string);
CHECK(r.get_properties(3).get_data_type() == dt_date);
// type char is visible as string
// - to comply with the implementation for Oracle
CHECK(r.get_properties(4).get_data_type() == dt_string);
CHECK(r.get_properties("NUM_INT").get_data_type() == dt_integer);
CHECK(r.get_properties(0).get_name() == "NUM_FLOAT");
CHECK(r.get_properties(1).get_name() == "NUM_INT");
CHECK(r.get_properties(2).get_name() == "NAME");
CHECK(r.get_properties(3).get_name() == "SOMETIME");
CHECK(r.get_properties(4).get_name() == "CHR");
ASSERT_EQUAL_APPROX(r.get<double>(0), 3.14);
CHECK(r.get<int>(1) == 123);
CHECK(r.get<std::string>(2) == "Johny");
std::tm t = { 0 };
t = r.get<std::tm>(3);
CHECK(t.tm_year == 105);
// again, type char is visible as string
CHECK_EQUAL_PADDED(r.get<std::string>(4), "a");
ASSERT_EQUAL_APPROX(r.get<double>("NUM_FLOAT"), 3.14);
CHECK(r.get<int>("NUM_INT") == 123);
CHECK(r.get<std::string>("NAME") == "Johny");
CHECK_EQUAL_PADDED(r.get<std::string>("CHR"), "a");
CHECK(r.get_indicator(0) == i_ok);
// verify exception thrown on invalid get<>
bool caught = false;
try
{
r.get<std::string>(0);
}
catch (std::bad_cast const &)
{
caught = true;
}
CHECK(caught);
// additional test for stream-like extraction
{
double d;
int i;
std::string s;
std::tm t;
std::string c;
r >> d >> i >> s >> t >> c;
ASSERT_EQUAL_APPROX(d, 3.14);
CHECK(i == 123);
CHECK(s == "Johny");
CHECK(t.tm_year == 105);
CHECK(t.tm_mon == 11);
CHECK(t.tm_mday == 19);
CHECK(t.tm_hour == 22);
CHECK(t.tm_min == 14);
CHECK(t.tm_sec == 17);
CHECK_EQUAL_PADDED(c, "a");
}
}
// additional test to check if the row object can be
// reused between queries
{
row r;
sql << "select * from soci_test", into(r);
CHECK(r.size() == 5);
CHECK(r.get_properties(0).get_data_type() == dt_double);
CHECK(r.get_properties(1).get_data_type() == dt_integer);
CHECK(r.get_properties(2).get_data_type() == dt_string);
CHECK(r.get_properties(3).get_data_type() == dt_date);
sql << "select name, num_int from soci_test", into(r);
CHECK(r.size() == 2);
CHECK(r.get_properties(0).get_data_type() == dt_string);
CHECK(r.get_properties(1).get_data_type() == dt_integer);
}
}
// more dynamic bindings
TEST_CASE_METHOD(common_tests, "Dynamic row binding 2", "[core][dynamic]")
{
soci::session sql(backEndFactory_, connectString_);
auto_table_creator tableCreator(tc_.table_creator_1(sql));
sql << "insert into soci_test(id, val) values(1, 10)";
sql << "insert into soci_test(id, val) values(2, 20)";
sql << "insert into soci_test(id, val) values(3, 30)";
#ifndef SOCI_POSTGRESQL_NOPARAMS
{
int id = 2;
row r;
sql << "select val from soci_test where id = :id", use(id), into(r);
CHECK(r.size() == 1);
CHECK(r.get_properties(0).get_data_type() == dt_integer);
CHECK(r.get<int>(0) == 20);
}
{
int id;
row r;
statement st = (sql.prepare <<
"select val from soci_test where id = :id", use(id), into(r));
id = 2;
st.execute(true);
CHECK(r.size() == 1);
CHECK(r.get_properties(0).get_data_type() == dt_integer);
CHECK(r.get<int>(0) == 20);
id = 3;
st.execute(true);
CHECK(r.size() == 1);
CHECK(r.get_properties(0).get_data_type() == dt_integer);
CHECK(r.get<int>(0) == 30);
id = 1;
st.execute(true);
CHECK(r.size() == 1);
CHECK(r.get_properties(0).get_data_type() == dt_integer);
CHECK(r.get<int>(0) == 10);
}
#else
{
row r;
sql << "select val from soci_test where id = 2", into(r);
CHECK(r.size() == 1);
CHECK(r.get_properties(0).get_data_type() == dt_integer);
CHECK(r.get<int>(0) == 20);
}
#endif // SOCI_POSTGRESQL_NOPARAMS
}
// More Dynamic binding to row objects
TEST_CASE_METHOD(common_tests, "Dynamic row binding 3", "[core][dynamic]")
{
soci::session sql(backEndFactory_, connectString_);
sql.uppercase_column_names(true);
auto_table_creator tableCreator(tc_.table_creator_3(sql));
row r1;
sql << "select * from soci_test", into(r1);
CHECK(sql.got_data() == false);
sql << "insert into soci_test values('david', '(404)123-4567')";
sql << "insert into soci_test values('john', '(404)123-4567')";
sql << "insert into soci_test values('doe', '(404)123-4567')";
row r2;
statement st = (sql.prepare << "select * from soci_test", into(r2));
st.execute();
CHECK(r2.size() == 2);
int count = 0;
while (st.fetch())
{
++count;
CHECK(r2.get<std::string>("PHONE") == "(404)123-4567");
}
CHECK(count == 3);
}
// This is like the previous test but with a type_conversion instead of a row
TEST_CASE_METHOD(common_tests, "Dynamic binding with type conversions", "[core][dynamic][type_conversion]")
{
soci::session sql(backEndFactory_, connectString_);
sql.uppercase_column_names(true);
SECTION("simple conversions")
{
auto_table_creator tableCreator(tc_.table_creator_1(sql));
SECTION("between single basic type and user type")
{
MyInt mi;
mi.set(123);
sql << "insert into soci_test(id) values(:id)", use(mi);
int i;
sql << "select id from soci_test", into(i);
CHECK(i == 123);
sql << "update soci_test set id = id + 1";
sql << "select id from soci_test", into(mi);
CHECK(mi.get() == 124);
}
SECTION("with use const")
{
MyInt mi;
mi.set(123);
MyInt const & cmi = mi;
sql << "insert into soci_test(id) values(:id)", use(cmi);
int i;
sql << "select id from soci_test", into(i);
CHECK(i == 123);
}
}
SECTION("ORM conversions")
{
auto_table_creator tableCreator(tc_.table_creator_3(sql));
SECTION("conversions based on values")
{
PhonebookEntry p1;
sql << "select * from soci_test", into(p1);
CHECK(p1.name == "");
CHECK(p1.phone == "");
p1.name = "david";
// Note: uppercase column names are used here (and later on)
// for consistency with how they can be read from database
// (which means forced to uppercase on Oracle) and how they are
// set/get in the type conversion routines for PhonebookEntry.
// In short, IF the database is Oracle,
// then all column names for binding should be uppercase.
sql << "insert into soci_test values(:NAME, :PHONE)", use(p1);
sql << "insert into soci_test values('john', '(404)123-4567')";
sql << "insert into soci_test values('doe', '(404)123-4567')";
PhonebookEntry p2;
statement st = (sql.prepare << "select * from soci_test", into(p2));
st.execute();
int count = 0;
while (st.fetch())
{
++count;
if (p2.name == "david")
{
// see type_conversion<PhonebookEntry>
CHECK(p2.phone =="<NULL>");
}
else
{
CHECK(p2.phone == "(404)123-4567");
}
}
CHECK(count == 3);
}
SECTION("conversions based on values with use const")
{
PhonebookEntry p1;
p1.name = "Joe Coder";
p1.phone = "123-456";
PhonebookEntry const & cp1 = p1;
sql << "insert into soci_test values(:NAME, :PHONE)", use(cp1);
PhonebookEntry p2;
sql << "select * from soci_test", into(p2);
CHECK(sql.got_data());
CHECK(p2.name == "Joe Coder");
CHECK(p2.phone == "123-456");
}
SECTION("conversions based on accessor functions (as opposed to direct variable bindings)")
{
PhonebookEntry3 p1;
p1.setName("Joe Hacker");
p1.setPhone("10010110");
sql << "insert into soci_test values(:NAME, :PHONE)", use(p1);
PhonebookEntry3 p2;
sql << "select * from soci_test", into(p2);
CHECK(sql.got_data());
CHECK(p2.getName() == "Joe Hacker");
CHECK(p2.getPhone() == "10010110");
}
SECTION("PhonebookEntry2 type conversion to test calls to values::get_indicator()")
{
PhonebookEntry2 p1;
sql << "select * from soci_test", into(p1);
CHECK(p1.name == "");
CHECK(p1.phone == "");
p1.name = "david";
sql << "insert into soci_test values(:NAME, :PHONE)", use(p1);
sql << "insert into soci_test values('john', '(404)123-4567')";
sql << "insert into soci_test values('doe', '(404)123-4567')";
PhonebookEntry2 p2;
statement st = (sql.prepare << "select * from soci_test", into(p2));
st.execute();
int count = 0;
while (st.fetch())
{
++count;
if (p2.name == "david")
{
// see type_conversion<PhonebookEntry2>
CHECK(p2.phone =="<NULL>");
}
else
{
CHECK(p2.phone == "(404)123-4567");
}
}
CHECK(count == 3);
}
}
}
TEST_CASE_METHOD(common_tests, "Prepared insert with ORM", "[core][orm]")
{
soci::session sql(backEndFactory_, connectString_);
sql.uppercase_column_names(true);
auto_table_creator tableCreator(tc_.table_creator_3(sql));
PhonebookEntry temp;
PhonebookEntry e1 = { "name1", "phone1" };
PhonebookEntry e2 = { "name2", "phone2" };
//sql << "insert into soci_test values (:NAME, :PHONE)", use(temp);
statement insertStatement = (sql.prepare << "insert into soci_test values (:NAME, :PHONE)", use(temp));
temp = e1;
insertStatement.execute(true);
temp = e2;
insertStatement.execute(true);
int count = 0;
sql << "select count(*) from soci_test where NAME in ('name1', 'name2')", into(count);
CHECK(count == 2);
}
TEST_CASE_METHOD(common_tests, "Partial match with ORM", "[core][orm]")
{
soci::session sql(backEndFactory_, connectString_);
sql.uppercase_column_names(true);
auto_table_creator tableCreator(tc_.table_creator_3(sql));
PhonebookEntry in = { "name1", "phone1" };
std::string name = "nameA";
sql << "insert into soci_test values (:NAMED, :PHONE)", use(in), use(name, "NAMED");
PhonebookEntry out;
sql << "select * from soci_test where PHONE = 'phone1'", into(out);
CHECK(out.name == "nameA");
CHECK(out.phone == "phone1");
}
TEST_CASE_METHOD(common_tests, "Numeric round trip", "[core][float]")
{
soci::session sql(backEndFactory_, connectString_);
auto_table_creator tableCreator(tc_.table_creator_1(sql));
double d1 = 0.003958,
d2;
sql << "insert into soci_test(num76) values (:d1)", use(d1);
sql << "select num76 from soci_test", into(d2);
// The numeric value should make the round trip unchanged, we really want
// to use exact comparisons here.
ASSERT_EQUAL_EXACT(d1, d2);
// test negative doubles too
sql << "delete from soci_test";
d1 = -d1;
sql << "insert into soci_test(num76) values (:d1)", use(d1);
sql << "select num76 from soci_test", into(d2);
ASSERT_EQUAL_EXACT(d1, d2);
}
#ifndef SOCI_POSTGRESQL_NOPARAMS
// test for bulk fetch with single use
TEST_CASE_METHOD(common_tests, "Bulk fetch with single use", "[core][bulk]")
{
soci::session sql(backEndFactory_, connectString_);
auto_table_creator tableCreator(tc_.table_creator_1(sql));
sql << "insert into soci_test(name, id) values('john', 1)";
sql << "insert into soci_test(name, id) values('george', 2)";
sql << "insert into soci_test(name, id) values('anthony', 1)";
sql << "insert into soci_test(name, id) values('marc', 3)";
sql << "insert into soci_test(name, id) values('julian', 1)";
int code = 1;
std::vector<std::string> names(10);
sql << "select name from soci_test where id = :id order by name",
into(names), use(code);
CHECK(names.size() == 3);
CHECK(names[0] == "anthony");
CHECK(names[1] == "john");
CHECK(names[2] == "julian");
}
#endif // SOCI_POSTGRESQL_NOPARAMS
// test for basic logging support
TEST_CASE_METHOD(common_tests, "Basic logging support", "[core][logging]")
{
soci::session sql(backEndFactory_, connectString_);
std::ostringstream log;
sql.set_log_stream(&log);
try
{
sql << "drop table soci_test1";
}
catch (...) {}
CHECK(sql.get_last_query() == "drop table soci_test1");
sql.set_log_stream(NULL);
try
{
sql << "drop table soci_test2";
}
catch (...) {}
CHECK(sql.get_last_query() == "drop table soci_test2");
sql.set_log_stream(&log);
try
{
sql << "drop table soci_test3";
}
catch (...) {}
CHECK(sql.get_last_query() == "drop table soci_test3");
CHECK(log.str() ==
"drop table soci_test1\n"
"drop table soci_test3\n");
}
// test for rowset creation and copying
TEST_CASE_METHOD(common_tests, "Rowset creation and copying", "[core][rowset]")
{
soci::session sql(backEndFactory_, connectString_);
// create and populate the test table
auto_table_creator tableCreator(tc_.table_creator_1(sql));
{
// Open empty rowset
rowset<row> rs1 = (sql.prepare << "select * from soci_test");
CHECK(rs1.begin() == rs1.end());
}
{
// Copy construction
rowset<row> rs1 = (sql.prepare << "select * from soci_test");
rowset<row> rs2(rs1);
rowset<row> rs3(rs1);
rowset<row> rs4(rs3);
CHECK(rs1.begin() == rs2.begin());
CHECK(rs1.begin() == rs3.begin());
CHECK(rs1.end() == rs2.end());
CHECK(rs1.end() == rs3.end());
}
if (!tc_.has_multiple_select_bug())
{
// Assignment
rowset<row> rs1 = (sql.prepare << "select * from soci_test");
rowset<row> rs2 = (sql.prepare << "select * from soci_test");
rowset<row> rs3 = (sql.prepare << "select * from soci_test");
rs1 = rs2;
rs3 = rs2;
CHECK(rs1.begin() == rs2.begin());
CHECK(rs1.begin() == rs3.begin());
CHECK(rs1.end() == rs2.end());
CHECK(rs1.end() == rs3.end());
}
}
// test for simple iterating using rowset iterator (without reading data)
TEST_CASE_METHOD(common_tests, "Rowset iteration", "[core][rowset]")
{
soci::session sql(backEndFactory_, connectString_);
// create and populate the test table
auto_table_creator tableCreator(tc_.table_creator_1(sql));
{
sql << "insert into soci_test(id, val) values(1, 10)";
sql << "insert into soci_test(id, val) values(2, 11)";
sql << "insert into soci_test(id, val) values(3, NULL)";
sql << "insert into soci_test(id, val) values(4, NULL)";
sql << "insert into soci_test(id, val) values(5, 12)";
{
rowset<row> rs = (sql.prepare << "select * from soci_test");
CHECK(5 == std::distance(rs.begin(), rs.end()));
}
}
}
// test for reading rowset<row> using iterator
TEST_CASE_METHOD(common_tests, "Reading rows from rowset", "[core][row][rowset]")
{
soci::session sql(backEndFactory_, connectString_);
sql.uppercase_column_names(true);
// create and populate the test table
auto_table_creator tableCreator(tc_.table_creator_2(sql));
{
{
// Empty rowset
rowset<row> rs = (sql.prepare << "select * from soci_test");
CHECK(0 == std::distance(rs.begin(), rs.end()));
}
{
// Non-empty rowset
sql << "insert into soci_test values(3.14, 123, \'Johny\',"
<< tc_.to_date_time("2005-12-19 22:14:17")
<< ", 'a')";
sql << "insert into soci_test values(6.28, 246, \'Robert\',"
<< tc_.to_date_time("2004-10-01 18:44:10")
<< ", 'b')";
rowset<row> rs = (sql.prepare << "select * from soci_test");
rowset<row>::const_iterator it = rs.begin();
CHECK(it != rs.end());
//
// First row
//
row const & r1 = (*it);
// Properties
CHECK(r1.size() == 5);
CHECK(r1.get_properties(0).get_data_type() == dt_double);
CHECK(r1.get_properties(1).get_data_type() == dt_integer);
CHECK(r1.get_properties(2).get_data_type() == dt_string);
CHECK(r1.get_properties(3).get_data_type() == dt_date);
CHECK(r1.get_properties(4).get_data_type() == dt_string);
CHECK(r1.get_properties("NUM_INT").get_data_type() == dt_integer);
// Data
// Since we didn't specify order by in the above query,
// the 2 rows may be returned in either order
// (If we specify order by, we can't do it in a cross db
// compatible way, because the Oracle table for this has been
// created with lower case column names)
std::string name = r1.get<std::string>(2);
if (name == "Johny")
{
ASSERT_EQUAL_APPROX(r1.get<double>(0), 3.14);
CHECK(r1.get<int>(1) == 123);
CHECK(r1.get<std::string>(2) == "Johny");
std::tm t1 = { 0 };
t1 = r1.get<std::tm>(3);
CHECK(t1.tm_year == 105);
CHECK_EQUAL_PADDED(r1.get<std::string>(4), "a");
ASSERT_EQUAL_APPROX(r1.get<double>("NUM_FLOAT"), 3.14);
CHECK(r1.get<int>("NUM_INT") == 123);
CHECK(r1.get<std::string>("NAME") == "Johny");
CHECK_EQUAL_PADDED(r1.get<std::string>("CHR"), "a");
}
else if (name == "Robert")
{
ASSERT_EQUAL(r1.get<double>(0), 6.28);
CHECK(r1.get<int>(1) == 246);
CHECK(r1.get<std::string>(2) == "Robert");
std::tm t1 = r1.get<std::tm>(3);
CHECK(t1.tm_year == 104);
CHECK(r1.get<std::string>(4) == "b");
ASSERT_EQUAL(r1.get<double>("NUM_FLOAT"), 6.28);
CHECK(r1.get<int>("NUM_INT") == 246);
CHECK(r1.get<std::string>("NAME") == "Robert");
CHECK_EQUAL_PADDED(r1.get<std::string>("CHR"), "b");
}
else
{
CAPTURE(name);
FAIL("expected \"Johny\" or \"Robert\"");
}
//
// Iterate to second row
//
++it;
CHECK(it != rs.end());
//
// Second row
//
row const & r2 = (*it);
// Properties
CHECK(r2.size() == 5);
CHECK(r2.get_properties(0).get_data_type() == dt_double);
CHECK(r2.get_properties(1).get_data_type() == dt_integer);
CHECK(r2.get_properties(2).get_data_type() == dt_string);
CHECK(r2.get_properties(3).get_data_type() == dt_date);
CHECK(r2.get_properties(4).get_data_type() == dt_string);
CHECK(r2.get_properties("NUM_INT").get_data_type() == dt_integer);
std::string newName = r2.get<std::string>(2);
CHECK(name != newName);
if (newName == "Johny")
{
ASSERT_EQUAL_APPROX(r2.get<double>(0), 3.14);
CHECK(r2.get<int>(1) == 123);
CHECK(r2.get<std::string>(2) == "Johny");
std::tm t2 = r2.get<std::tm>(3);
CHECK(t2.tm_year == 105);
CHECK(r2.get<std::string>(4) == "a");
ASSERT_EQUAL_APPROX(r2.get<double>("NUM_FLOAT"), 3.14);
CHECK(r2.get<int>("NUM_INT") == 123);
CHECK(r2.get<std::string>("NAME") == "Johny");
CHECK(r2.get<std::string>("CHR") == "a");
}
else if (newName == "Robert")
{
ASSERT_EQUAL_APPROX(r2.get<double>(0), 6.28);
CHECK(r2.get<int>(1) == 246);
CHECK(r2.get<std::string>(2) == "Robert");
std::tm t2 = r2.get<std::tm>(3);
CHECK(t2.tm_year == 104);
CHECK_EQUAL_PADDED(r2.get<std::string>(4), "b");
ASSERT_EQUAL_APPROX(r2.get<double>("NUM_FLOAT"), 6.28);
CHECK(r2.get<int>("NUM_INT") == 246);
CHECK(r2.get<std::string>("NAME") == "Robert");
CHECK_EQUAL_PADDED(r2.get<std::string>("CHR"), "b");
}
else
{
CAPTURE(newName);
FAIL("expected \"Johny\" or \"Robert\"");
}
}
{
// Non-empty rowset with NULL values
sql << "insert into soci_test "
<< "(num_int, num_float , name, sometime, chr) "
<< "values (0, NULL, NULL, NULL, NULL)";
rowset<row> rs = (sql.prepare
<< "select num_int, num_float, name, sometime, chr "
<< "from soci_test where num_int = 0");
rowset<row>::const_iterator it = rs.begin();
CHECK(it != rs.end());
//
// First row
//
row const& r1 = (*it);
// Properties
CHECK(r1.size() == 5);
CHECK(r1.get_properties(0).get_data_type() == dt_integer);
CHECK(r1.get_properties(1).get_data_type() == dt_double);
CHECK(r1.get_properties(2).get_data_type() == dt_string);
CHECK(r1.get_properties(3).get_data_type() == dt_date);
CHECK(r1.get_properties(4).get_data_type() == dt_string);
// Data
CHECK(r1.get_indicator(0) == soci::i_ok);
CHECK(r1.get<int>(0) == 0);
CHECK(r1.get_indicator(1) == soci::i_null);
CHECK(r1.get_indicator(2) == soci::i_null);
CHECK(r1.get_indicator(3) == soci::i_null);
CHECK(r1.get_indicator(4) == soci::i_null);
}
}
}
// test for reading rowset<int> using iterator
TEST_CASE_METHOD(common_tests, "Reading ints from rowset", "[core][rowset]")
{
soci::session sql(backEndFactory_, connectString_);
// create and populate the test table
auto_table_creator tableCreator(tc_.table_creator_1(sql));
{
sql << "insert into soci_test(id) values(1)";
sql << "insert into soci_test(id) values(2)";
sql << "insert into soci_test(id) values(3)";
sql << "insert into soci_test(id) values(4)";
sql << "insert into soci_test(id) values(5)";
{
rowset<int> rs = (sql.prepare << "select id from soci_test order by id asc");
// 1st row
rowset<int>::const_iterator pos = rs.begin();
CHECK(1 == (*pos));
// 3rd row
std::advance(pos, 2);
CHECK(3 == (*pos));
// 5th row
std::advance(pos, 2);
CHECK(5 == (*pos));
// The End
++pos;
CHECK(pos == rs.end());
}
}
}
// test for handling 'use' and reading rowset<std::string> using iterator
TEST_CASE_METHOD(common_tests, "Reading strings from rowset", "[core][rowset]")
{
soci::session sql(backEndFactory_, connectString_);
// create and populate the test table
auto_table_creator tableCreator(tc_.table_creator_1(sql));
{
sql << "insert into soci_test(str) values('abc')";
sql << "insert into soci_test(str) values('def')";
sql << "insert into soci_test(str) values('ghi')";
sql << "insert into soci_test(str) values('jkl')";
{
// Expected result in numbers
std::string idle("def");
rowset<std::string> rs1 = (sql.prepare
<< "select str from soci_test where str = :idle",
use(idle));
CHECK(1 == std::distance(rs1.begin(), rs1.end()));
// Expected result in value
idle = "jkl";
rowset<std::string> rs2 = (sql.prepare
<< "select str from soci_test where str = :idle",
use(idle));
CHECK(idle == *(rs2.begin()));
}
}
}
// test for handling troublemaker
TEST_CASE_METHOD(common_tests, "Rowset expected exception", "[core][exception][rowset]")
{
soci::session sql(backEndFactory_, connectString_);
// create and populate the test table
auto_table_creator tableCreator(tc_.table_creator_1(sql));
sql << "insert into soci_test(str) values('abc')";
CHECK_THROWS_AS(
std::string troublemaker;
rowset<std::string> rs1 = (sql.prepare << "select str from soci_test",
into(troublemaker)),
soci_error
);
}
// test for handling NULL values with expected exception:
// "Null value fetched and no indicator defined."
TEST_CASE_METHOD(common_tests, "NULL expected exception", "[core][exception][null]")
{
soci::session sql(backEndFactory_, connectString_);
// create and populate the test table
auto_table_creator tableCreator(tc_.table_creator_1(sql));
sql << "insert into soci_test(val) values(1)";
sql << "insert into soci_test(val) values(2)";
sql << "insert into soci_test(val) values(NULL)";
sql << "insert into soci_test(val) values(3)";
rowset<int> rs = (sql.prepare << "select val from soci_test order by val asc");
int tester = 0;
CHECK_THROWS_AS(
for (rowset<int>::const_iterator it = rs.begin(); it != rs.end(); ++it)
{
tester = *it;
},
soci_error
);
(void)tester;
}
// This is like the first dynamic binding test but with rowset and iterators use
TEST_CASE_METHOD(common_tests, "Dynamic binding with rowset", "[core][dynamic][type_conversion]")
{
soci::session sql(backEndFactory_, connectString_);
sql.uppercase_column_names(true);
{
auto_table_creator tableCreator(tc_.table_creator_3(sql));
PhonebookEntry p1;
sql << "select * from soci_test", into(p1);
CHECK(p1.name == "");
CHECK(p1.phone == "");
p1.name = "david";
sql << "insert into soci_test values(:NAME, :PHONE)", use(p1);
sql << "insert into soci_test values('john', '(404)123-4567')";
sql << "insert into soci_test values('doe', '(404)123-4567')";
rowset<PhonebookEntry> rs = (sql.prepare << "select * from soci_test");
int count = 0;
for (rowset<PhonebookEntry>::const_iterator it = rs.begin(); it != rs.end(); ++it)
{
++count;
PhonebookEntry const& p2 = (*it);
if (p2.name == "david")
{
// see type_conversion<PhonebookEntry>
CHECK(p2.phone =="<NULL>");
}
else
{
CHECK(p2.phone == "(404)123-4567");
}
}
CHECK(3 == count);
}
}
#ifdef HAVE_BOOST
// test for handling NULL values with boost::optional
// (both into and use)
TEST_CASE_METHOD(common_tests, "NULL with optional", "[core][boost][null]")
{
soci::session sql(backEndFactory_, connectString_);
// create and populate the test table
auto_table_creator tableCreator(tc_.table_creator_1(sql));
{
sql << "insert into soci_test(val) values(7)";
{
// verify non-null value is fetched correctly
boost::optional<int> opt;
sql << "select val from soci_test", into(opt);
CHECK(opt.is_initialized());
CHECK(opt.get() == 7);
// indicators can be used with optional
// (although that's just a consequence of implementation,
// not an intended feature - but let's test it anyway)
indicator ind;
opt.reset();
sql << "select val from soci_test", into(opt, ind);
CHECK(opt.is_initialized());
CHECK(opt.get() == 7);
CHECK(ind == i_ok);
// verify null value is fetched correctly
sql << "select i1 from soci_test", into(opt);
CHECK(opt.is_initialized() == false);
// and with indicator
opt = 5;
sql << "select i1 from soci_test", into(opt, ind);
CHECK(opt.is_initialized() == false);
CHECK(ind == i_null);
// verify non-null is inserted correctly
opt = 3;
sql << "update soci_test set val = :v", use(opt);
int j = 0;
sql << "select val from soci_test", into(j);
CHECK(j == 3);
// verify null is inserted correctly
opt.reset();
sql << "update soci_test set val = :v", use(opt);
ind = i_ok;
sql << "select val from soci_test", into(j, ind);
CHECK(ind == i_null);
}
// vector tests (select)
{
sql << "delete from soci_test";
// simple readout of non-null data
sql << "insert into soci_test(id, val, str) values(1, 5, \'abc\')";
sql << "insert into soci_test(id, val, str) values(2, 6, \'def\')";
sql << "insert into soci_test(id, val, str) values(3, 7, \'ghi\')";
sql << "insert into soci_test(id, val, str) values(4, 8, null)";
sql << "insert into soci_test(id, val, str) values(5, 9, \'mno\')";
std::vector<boost::optional<int> > v(10);
sql << "select val from soci_test order by val", into(v);
CHECK(v.size() == 5);
CHECK(v[0].is_initialized());
CHECK(v[0].get() == 5);
CHECK(v[1].is_initialized());
CHECK(v[1].get() == 6);
CHECK(v[2].is_initialized());
CHECK(v[2].get() == 7);
CHECK(v[3].is_initialized());
CHECK(v[3].get() == 8);
CHECK(v[4].is_initialized());
CHECK(v[4].get() == 9);
// readout of nulls
sql << "update soci_test set val = null where id = 2 or id = 4";
std::vector<int> ids(5);
sql << "select id, val from soci_test order by id", into(ids), into(v);
CHECK(v.size() == 5);
CHECK(ids.size() == 5);
CHECK(v[0].is_initialized());
CHECK(v[0].get() == 5);
CHECK(v[1].is_initialized() == false);
CHECK(v[2].is_initialized());
CHECK(v[2].get() == 7);
CHECK(v[3].is_initialized() == false);
CHECK(v[4].is_initialized());
CHECK(v[4].get() == 9);
// readout with statement preparation
int id = 1;
ids.resize(3);
v.resize(3);
statement st = (sql.prepare <<
"select id, val from soci_test order by id", into(ids), into(v));
st.execute();
while (st.fetch())
{
for (std::size_t i = 0; i != v.size(); ++i)
{
CHECK(id == ids[i]);
if (id == 2 || id == 4)
{
CHECK(v[i].is_initialized() == false);
}
else
{
CHECK(v[i].is_initialized());
CHECK(v[i].get() == id + 4);
}
++id;
}
ids.resize(3);
v.resize(3);
}
CHECK(id == 6);
}
// and why not stress iterators and the dynamic binding, too!
{
rowset<row> rs = (sql.prepare << "select id, val, str from soci_test order by id");
rowset<row>::const_iterator it = rs.begin();
CHECK(it != rs.end());
row const& r1 = (*it);
CHECK(r1.size() == 3);
// Note: for the reason of differences between number(x,y) type and
// binary representation of integers, the following commented assertions
// do not work for Oracle.
// The problem is that for this single table the data type used in Oracle
// table creator for the id column is number(10,0),
// which allows to insert all int values.
// On the other hand, the column description scheme used in the Oracle
// backend figures out that the natural type for such a column
// is eUnsignedInt - this makes the following assertions fail.
// Other database backends (like PostgreSQL) use other types like int
// and this not only allows to insert all int values (obviously),
// but is also recognized as int (obviously).
// There is a similar problem with stream-like extraction,
// where internally get<T> is called and the type mismatch is detected
// for the id column - that's why the code below skips this column
// and tests the remaining column only.
//CHECK(r1.get_properties(0).get_data_type() == dt_integer);
CHECK(r1.get_properties(1).get_data_type() == dt_integer);
CHECK(r1.get_properties(2).get_data_type() == dt_string);
//CHECK(r1.get<int>(0) == 1);
CHECK(r1.get<int>(1) == 5);
CHECK(r1.get<std::string>(2) == "abc");
CHECK(r1.get<boost::optional<int> >(1).is_initialized());
CHECK(r1.get<boost::optional<int> >(1).get() == 5);
CHECK(r1.get<boost::optional<std::string> >(2).is_initialized());
CHECK(r1.get<boost::optional<std::string> >(2).get() == "abc");
++it;
row const& r2 = (*it);
CHECK(r2.size() == 3);
// CHECK(r2.get_properties(0).get_data_type() == dt_integer);
CHECK(r2.get_properties(1).get_data_type() == dt_integer);
CHECK(r2.get_properties(2).get_data_type() == dt_string);
//CHECK(r2.get<int>(0) == 2);
try
{
// expect exception here, this is NULL value
(void)r1.get<int>(1);
FAIL("expected exception not thrown");
}
catch (soci_error const &) {}
// but we can read it as optional
CHECK(r2.get<boost::optional<int> >(1).is_initialized() == false);
// stream-like data extraction
++it;
row const &r3 = (*it);
boost::optional<int> io;
boost::optional<std::string> so;
r3.skip(); // move to val and str columns
r3 >> io >> so;
CHECK(io.is_initialized());
CHECK(io.get() == 7);
CHECK(so.is_initialized());
CHECK(so.get() == "ghi");
++it;
row const &r4 = (*it);
r3.skip(); // move to val and str columns
r4 >> io >> so;
CHECK(io.is_initialized() == false);
CHECK(so.is_initialized() == false);
}
// bulk inserts of non-null data
{
sql << "delete from soci_test";
std::vector<int> ids;
std::vector<boost::optional<int> > v;
ids.push_back(10); v.push_back(20);
ids.push_back(11); v.push_back(21);
ids.push_back(12); v.push_back(22);
ids.push_back(13); v.push_back(23);
sql << "insert into soci_test(id, val) values(:id, :val)",
use(ids, "id"), use(v, "val");
int sum;
sql << "select sum(val) from soci_test", into(sum);
CHECK(sum == 86);
// bulk inserts of some-null data
sql << "delete from soci_test";
v[2].reset();
v[3].reset();
sql << "insert into soci_test(id, val) values(:id, :val)",
use(ids, "id"), use(v, "val");
sql << "select sum(val) from soci_test", into(sum);
CHECK(sum == 41);
}
// composability with user conversions
{
sql << "delete from soci_test";
boost::optional<MyInt> omi1;
boost::optional<MyInt> omi2;
omi1 = MyInt(125);
omi2.reset();
sql << "insert into soci_test(id, val) values(:id, :val)",
use(omi1), use(omi2);
sql << "select id, val from soci_test", into(omi2), into(omi1);
CHECK(omi1.is_initialized() == false);
CHECK(omi2.is_initialized());
CHECK(omi2.get().get() == 125);
}
// use with const optional and user conversions
{
sql << "delete from soci_test";
boost::optional<MyInt> omi1;
boost::optional<MyInt> omi2;
omi1 = MyInt(125);
omi2.reset();
boost::optional<MyInt> const & comi1 = omi1;
boost::optional<MyInt> const & comi2 = omi2;
sql << "insert into soci_test(id, val) values(:id, :val)",
use(comi1), use(comi2);
sql << "select id, val from soci_test", into(omi2), into(omi1);
CHECK(omi1.is_initialized() == false);
CHECK(omi2.is_initialized());
CHECK(omi2.get().get() == 125);
}
// use with rowset and table containing null values
{
auto_table_creator tableCreator(tc_.table_creator_1(sql));
sql << "insert into soci_test(id, val) values(1, 10)";
sql << "insert into soci_test(id, val) values(2, 11)";
sql << "insert into soci_test(id, val) values(3, NULL)";
sql << "insert into soci_test(id, val) values(4, 13)";
rowset<boost::optional<int> > rs = (sql.prepare <<
"select val from soci_test order by id asc");
// 1st row
rowset<boost::optional<int> >::const_iterator pos = rs.begin();
CHECK((*pos).is_initialized());
CHECK(10 == (*pos).get());
// 2nd row
++pos;
CHECK((*pos).is_initialized());
CHECK(11 == (*pos).get());
// 3rd row
++pos;
CHECK((*pos).is_initialized() == false);
// 4th row
++pos;
CHECK((*pos).is_initialized());
CHECK(13 == (*pos).get());
}
}
}
#endif // HAVE_BOOST
// connection and reconnection tests
TEST_CASE_METHOD(common_tests, "Connection and reconnection", "[core][connect]")
{
{
// empty session
soci::session sql;
// idempotent:
sql.close();
try
{
sql.reconnect();
FAIL("expected exception not thrown");
}
catch (soci_error const &e)
{
CHECK(e.get_error_message() ==
"Cannot reconnect without previous connection.");
}
// open from empty session
sql.open(backEndFactory_, connectString_);
sql.close();
// reconnecting from closed session
sql.reconnect();
// opening already connected session
try
{
sql.open(backEndFactory_, connectString_);
FAIL("expected exception not thrown");
}
catch (soci_error const &e)
{
CHECK(e.get_error_message() ==
"Cannot open already connected session.");
}
sql.close();
// open from closed
sql.open(backEndFactory_, connectString_);
// reconnect from already connected session
sql.reconnect();
}
{
soci::session sql;
try
{
sql << "this statement cannot execute";
FAIL("expected exception not thrown");
}
catch (soci_error const &e)
{
CHECK(e.get_error_message() ==
"Session is not connected.");
}
}
}
#ifdef HAVE_BOOST
TEST_CASE_METHOD(common_tests, "Boost tuple", "[core][boost][tuple]")
{
soci::session sql(backEndFactory_, connectString_);
auto_table_creator tableCreator(tc_.table_creator_2(sql));
{
boost::tuple<double, int, std::string> t1(3.5, 7, "Joe Hacker");
ASSERT_EQUAL(t1.get<0>(), 3.5);
CHECK(t1.get<1>() == 7);
CHECK(t1.get<2>() == "Joe Hacker");
sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1);
// basic query
boost::tuple<double, int, std::string> t2;
sql << "select num_float, num_int, name from soci_test", into(t2);
ASSERT_EQUAL(t2.get<0>(), 3.5);
CHECK(t2.get<1>() == 7);
CHECK(t2.get<2>() == "Joe Hacker");
sql << "delete from soci_test";
}
{
// composability with boost::optional
// use:
boost::tuple<double, boost::optional<int>, std::string> t1(
3.5, boost::optional<int>(7), "Joe Hacker");
ASSERT_EQUAL(t1.get<0>(), 3.5);
CHECK(t1.get<1>().is_initialized());
CHECK(t1.get<1>().get() == 7);
CHECK(t1.get<2>() == "Joe Hacker");
sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1);
// into:
boost::tuple<double, boost::optional<int>, std::string> t2;
sql << "select num_float, num_int, name from soci_test", into(t2);
ASSERT_EQUAL(t2.get<0>(), 3.5);
CHECK(t2.get<1>().is_initialized());
CHECK(t2.get<1>().get() == 7);
CHECK(t2.get<2>() == "Joe Hacker");
sql << "delete from soci_test";
}
{
// composability with user-provided conversions
// use:
boost::tuple<double, MyInt, std::string> t1(3.5, 7, "Joe Hacker");
ASSERT_EQUAL(t1.get<0>(), 3.5);
CHECK(t1.get<1>().get() == 7);
CHECK(t1.get<2>() == "Joe Hacker");
sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1);
// into:
boost::tuple<double, MyInt, std::string> t2;
sql << "select num_float, num_int, name from soci_test", into(t2);
ASSERT_EQUAL(t2.get<0>(), 3.5);
CHECK(t2.get<1>().get() == 7);
CHECK(t2.get<2>() == "Joe Hacker");
sql << "delete from soci_test";
}
{
// let's have fun - composition of tuple, optional and user-defined type
// use:
boost::tuple<double, boost::optional<MyInt>, std::string> t1(
3.5, boost::optional<MyInt>(7), "Joe Hacker");
ASSERT_EQUAL(t1.get<0>(), 3.5);
CHECK(t1.get<1>().is_initialized());
CHECK(t1.get<1>().get().get() == 7);
CHECK(t1.get<2>() == "Joe Hacker");
sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1);
// into:
boost::tuple<double, boost::optional<MyInt>, std::string> t2;
sql << "select num_float, num_int, name from soci_test", into(t2);
ASSERT_EQUAL(t2.get<0>(), 3.5);
CHECK(t2.get<1>().is_initialized());
CHECK(t2.get<1>().get().get() == 7);
CHECK(t2.get<2>() == "Joe Hacker");
sql << "update soci_test set num_int = NULL";
sql << "select num_float, num_int, name from soci_test", into(t2);
ASSERT_EQUAL(t2.get<0>(), 3.5);
CHECK(t2.get<1>().is_initialized() == false);
CHECK(t2.get<2>() == "Joe Hacker");
}
{
// rowset<tuple>
sql << "insert into soci_test(num_float, num_int, name) values(4.0, 8, 'Tony Coder')";
sql << "insert into soci_test(num_float, num_int, name) values(4.5, NULL, 'Cecile Sharp')";
sql << "insert into soci_test(num_float, num_int, name) values(5.0, 10, 'Djhava Ravaa')";
typedef boost::tuple<double, boost::optional<int>, std::string> T;
rowset<T> rs = (sql.prepare
<< "select num_float, num_int, name from soci_test order by num_float asc");
rowset<T>::const_iterator pos = rs.begin();
ASSERT_EQUAL(pos->get<0>(), 3.5);
CHECK(pos->get<1>().is_initialized() == false);
CHECK(pos->get<2>() == "Joe Hacker");
++pos;
ASSERT_EQUAL(pos->get<0>(), 4.0);
CHECK(pos->get<1>().is_initialized());
CHECK(pos->get<1>().get() == 8);
CHECK(pos->get<2>() == "Tony Coder");
++pos;
ASSERT_EQUAL(pos->get<0>(), 4.5);
CHECK(pos->get<1>().is_initialized() == false);
CHECK(pos->get<2>() == "Cecile Sharp");
++pos;
ASSERT_EQUAL(pos->get<0>(), 5.0);
CHECK(pos->get<1>().is_initialized());
CHECK(pos->get<1>().get() == 10);
CHECK(pos->get<2>() == "Djhava Ravaa");
++pos;
CHECK(pos == rs.end());
}
}
#if defined(BOOST_VERSION) && BOOST_VERSION >= 103500
TEST_CASE_METHOD(common_tests, "Boost fusion", "[core][boost][fusion]")
{
soci::session sql(backEndFactory_, connectString_);
auto_table_creator tableCreator(tc_.table_creator_2(sql));
{
boost::fusion::vector<double, int, std::string> t1(3.5, 7, "Joe Hacker");
ASSERT_EQUAL(boost::fusion::at_c<0>(t1), 3.5);
CHECK(boost::fusion::at_c<1>(t1) == 7);
CHECK(boost::fusion::at_c<2>(t1) == "Joe Hacker");
sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1);
// basic query
boost::fusion::vector<double, int, std::string> t2;
sql << "select num_float, num_int, name from soci_test", into(t2);
ASSERT_EQUAL(boost::fusion::at_c<0>(t2), 3.5);
CHECK(boost::fusion::at_c<1>(t2) == 7);
CHECK(boost::fusion::at_c<2>(t2) == "Joe Hacker");
sql << "delete from soci_test";
}
{
// composability with boost::optional
// use:
boost::fusion::vector<double, boost::optional<int>, std::string> t1(
3.5, boost::optional<int>(7), "Joe Hacker");
ASSERT_EQUAL(boost::fusion::at_c<0>(t1), 3.5);
CHECK(boost::fusion::at_c<1>(t1).is_initialized());
CHECK(boost::fusion::at_c<1>(t1).get() == 7);
CHECK(boost::fusion::at_c<2>(t1) == "Joe Hacker");
sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1);
// into:
boost::fusion::vector<double, boost::optional<int>, std::string> t2;
sql << "select num_float, num_int, name from soci_test", into(t2);
ASSERT_EQUAL(boost::fusion::at_c<0>(t2), 3.5);
CHECK(boost::fusion::at_c<1>(t2).is_initialized());
CHECK(boost::fusion::at_c<1>(t2) == 7);
CHECK(boost::fusion::at_c<2>(t2) == "Joe Hacker");
sql << "delete from soci_test";
}
{
// composability with user-provided conversions
// use:
boost::fusion::vector<double, MyInt, std::string> t1(3.5, 7, "Joe Hacker");
ASSERT_EQUAL(boost::fusion::at_c<0>(t1), 3.5);
CHECK(boost::fusion::at_c<1>(t1).get() == 7);
CHECK(boost::fusion::at_c<2>(t1) == "Joe Hacker");
sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1);
// into:
boost::fusion::vector<double, MyInt, std::string> t2;
sql << "select num_float, num_int, name from soci_test", into(t2);
ASSERT_EQUAL(boost::fusion::at_c<0>(t2), 3.5);
CHECK(boost::fusion::at_c<1>(t2).get() == 7);
CHECK(boost::fusion::at_c<2>(t2) == "Joe Hacker");
sql << "delete from soci_test";
}
{
// let's have fun - composition of tuple, optional and user-defined type
// use:
boost::fusion::vector<double, boost::optional<MyInt>, std::string> t1(
3.5, boost::optional<MyInt>(7), "Joe Hacker");
ASSERT_EQUAL(boost::fusion::at_c<0>(t1), 3.5);
CHECK(boost::fusion::at_c<1>(t1).is_initialized());
CHECK(boost::fusion::at_c<1>(t1).get().get() == 7);
CHECK(boost::fusion::at_c<2>(t1) == "Joe Hacker");
sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1);
// into:
boost::fusion::vector<double, boost::optional<MyInt>, std::string> t2;
sql << "select num_float, num_int, name from soci_test", into(t2);
ASSERT_EQUAL(boost::fusion::at_c<0>(t2), 3.5);
CHECK(boost::fusion::at_c<1>(t2).is_initialized());
CHECK(boost::fusion::at_c<1>(t2).get().get() == 7);
CHECK(boost::fusion::at_c<2>(t2) == "Joe Hacker");
sql << "update soci_test set num_int = NULL";
sql << "select num_float, num_int, name from soci_test", into(t2);
ASSERT_EQUAL(boost::fusion::at_c<0>(t2), 3.5);
CHECK(boost::fusion::at_c<1>(t2).is_initialized() == false);
CHECK(boost::fusion::at_c<2>(t2) == "Joe Hacker");
}
{
// rowset<fusion::vector>
sql << "insert into soci_test(num_float, num_int, name) values(4.0, 8, 'Tony Coder')";
sql << "insert into soci_test(num_float, num_int, name) values(4.5, NULL, 'Cecile Sharp')";
sql << "insert into soci_test(num_float, num_int, name) values(5.0, 10, 'Djhava Ravaa')";
typedef boost::fusion::vector<double, boost::optional<int>, std::string> T;
rowset<T> rs = (sql.prepare
<< "select num_float, num_int, name from soci_test order by num_float asc");
rowset<T>::const_iterator pos = rs.begin();
ASSERT_EQUAL(boost::fusion::at_c<0>(*pos), 3.5);
CHECK(boost::fusion::at_c<1>(*pos).is_initialized() == false);
CHECK(boost::fusion::at_c<2>(*pos) == "Joe Hacker");
++pos;
ASSERT_EQUAL(boost::fusion::at_c<0>(*pos), 4.0);
CHECK(boost::fusion::at_c<1>(*pos).is_initialized());
CHECK(boost::fusion::at_c<1>(*pos).get() == 8);
CHECK(boost::fusion::at_c<2>(*pos) == "Tony Coder");
++pos;
ASSERT_EQUAL(boost::fusion::at_c<0>(*pos), 4.5);
CHECK(boost::fusion::at_c<1>(*pos).is_initialized() == false);
CHECK(boost::fusion::at_c<2>(*pos) == "Cecile Sharp");
++pos;
ASSERT_EQUAL(boost::fusion::at_c<0>(*pos), 5.0);
CHECK(boost::fusion::at_c<1>(*pos).is_initialized());
CHECK(boost::fusion::at_c<1>(*pos).get() == 10);
CHECK(boost::fusion::at_c<2>(*pos) == "Djhava Ravaa");
++pos;
CHECK(pos == rs.end());
}
}
#endif // defined(BOOST_VERSION) && BOOST_VERSION >= 103500
// test for boost::gregorian::date
TEST_CASE_METHOD(common_tests, "Boost date", "[core][boost][datetime]")
{
soci::session sql(backEndFactory_, connectString_);
{
auto_table_creator tableCreator(tc_.table_creator_1(sql));
std::tm nov15;
nov15.tm_year = 105;
nov15.tm_mon = 10;
nov15.tm_mday = 15;
nov15.tm_hour = 0;
nov15.tm_min = 0;
nov15.tm_sec = 0;
sql << "insert into soci_test(tm) values(:tm)", use(nov15);
boost::gregorian::date bgd;
sql << "select tm from soci_test", into(bgd);
CHECK(bgd.year() == 2005);
CHECK(bgd.month() == 11);
CHECK(bgd.day() == 15);
sql << "update soci_test set tm = NULL";
try
{
sql << "select tm from soci_test", into(bgd);
FAIL("expected exception not thrown");
}
catch (soci_error const & e)
{
CHECK(e.get_error_message() ==
"Null value not allowed for this type");
}
}
{
auto_table_creator tableCreator(tc_.table_creator_1(sql));
boost::gregorian::date bgd(2008, boost::gregorian::May, 5);
sql << "insert into soci_test(tm) values(:tm)", use(bgd);
std::tm t;
sql << "select tm from soci_test", into(t);
CHECK(t.tm_year == 108);
CHECK(t.tm_mon == 4);
CHECK(t.tm_mday == 5);
}
}
#endif // HAVE_BOOST
// connection pool - simple sequential test, no multiple threads
TEST_CASE_METHOD(common_tests, "Connection pool", "[core][connection][pool]")
{
// phase 1: preparation
const size_t pool_size = 10;
connection_pool pool(pool_size);
for (std::size_t i = 0; i != pool_size; ++i)
{
session & sql = pool.at(i);
sql.open(backEndFactory_, connectString_);
}
// phase 2: usage
for (std::size_t i = 0; i != pool_size; ++i)
{
// poor man way to lease more than one connection
soci::session sql_unused1(pool);
soci::session sql(pool);
soci::session sql_unused2(pool);
{
auto_table_creator tableCreator(tc_.table_creator_1(sql));
char c('a');
sql << "insert into soci_test(c) values(:c)", use(c);
sql << "select c from soci_test", into(c);
CHECK(c == 'a');
}
}
}
// Issue 66 - test query transformation callback feature
static std::string no_op_transform(std::string query)
{
return query;
}
static std::string lower_than_g(std::string query)
{
return query + " WHERE c < 'g'";
}
struct where_condition : std::unary_function<std::string, std::string>
{
where_condition(std::string const& where)
: where_(where)
{}
result_type operator()(argument_type query) const
{
return query + " WHERE " + where_;
}
std::string where_;
};
void run_query_transformation_test(test_context_base const& tc, session& sql)
{
// create and populate the test table
auto_table_creator tableCreator(tc.table_creator_1(sql));
for (char c = 'a'; c <= 'z'; ++c)
{
sql << "insert into soci_test(c) values(\'" << c << "\')";
}
char const* query = "select count(*) from soci_test";
// free function, no-op
{
sql.set_query_transformation(no_op_transform);
int count;
sql << query, into(count);
CHECK(count == 'z' - 'a' + 1);
}
// free function
{
sql.set_query_transformation(lower_than_g);
int count;
sql << query, into(count);
CHECK(count == 'g' - 'a');
}
// function object with state
{
sql.set_query_transformation(where_condition("c > 'g' AND c < 'j'"));
int count = 0;
sql << query, into(count);
CHECK(count == 'j' - 'h');
count = 0;
sql.set_query_transformation(where_condition("c > 's' AND c <= 'z'"));
sql << query, into(count);
CHECK(count == 'z' - 's');
}
#if 0
// lambda is just presented as an example to curious users
{
sql.set_query_transformation(
[](std::string const& query) {
return query + " WHERE c > 'g' AND c < 'j'";
});
int count = 0;
sql << query, into(count);
CHECK(count == 'j' - 'h');
}
#endif
// prepared statements
// constant effect (pre-prepare set transformation)
{
// set transformation after statement is prepared
sql.set_query_transformation(lower_than_g);
// prepare statement
int count;
statement st = (sql.prepare << query, into(count));
// observe transformation effect
st.execute(true);
CHECK(count == 'g' - 'a');
// reset transformation
sql.set_query_transformation(no_op_transform);
// observe the same transformation, no-op set above has no effect
count = 0;
st.execute(true);
CHECK(count == 'g' - 'a');
}
// no effect (post-prepare set transformation)
{
// reset
sql.set_query_transformation(no_op_transform);
// prepare statement
int count;
statement st = (sql.prepare << query, into(count));
// set transformation after statement is prepared
sql.set_query_transformation(lower_than_g);
// observe no effect of WHERE clause injection
st.execute(true);
CHECK(count == 'z' - 'a' + 1);
}
}
TEST_CASE_METHOD(common_tests, "Query transformation", "[core][query-transform]")
{
soci::session sql(backEndFactory_, connectString_);
run_query_transformation_test(tc_, sql);
}
TEST_CASE_METHOD(common_tests, "Query transformation with connection pool", "[core][query-transform][pool]")
{
// phase 1: preparation
const size_t pool_size = 10;
connection_pool pool(pool_size);
for (std::size_t i = 0; i != pool_size; ++i)
{
session & sql = pool.at(i);
sql.open(backEndFactory_, connectString_);
}
soci::session sql(pool);
run_query_transformation_test(tc_, sql);
}
// Originally, submitted to SQLite3 backend and later moved to common test.
// Test commit b394d039530f124802d06c3b1a969c3117683152
// Author: Mika Fischer <mika.fischer@zoopnet.de>
// Date: Thu Nov 17 13:28:07 2011 +0100
// Implement get_affected_rows for SQLite3 backend
TEST_CASE_METHOD(common_tests, "Get affected rows", "[core][affected-rows]")
{
soci::session sql(backEndFactory_, connectString_);
auto_table_creator tableCreator(tc_.table_creator_4(sql));
if (!tableCreator.get())
{
std::cout << "test get_affected_rows skipped (function not implemented)" << std::endl;
return;
}
for (int i = 0; i != 10; i++)
{
sql << "insert into soci_test(val) values(:val)", use(i);
}
statement st1 = (sql.prepare <<
"update soci_test set val = val + 1");
st1.execute(true);
CHECK(st1.get_affected_rows() == 10);
statement st2 = (sql.prepare <<
"delete from soci_test where val <= 5");
st2.execute(true);
CHECK(st2.get_affected_rows() == 5);
statement st3 = (sql.prepare <<
"update soci_test set val = val + 1");
st3.execute(true);
CHECK(st3.get_affected_rows() == 5);
std::vector<int> v(5, 0);
for (std::size_t i = 0; i < v.size(); ++i)
{
v[i] = (7 + static_cast<int>(i));
}
// test affected rows for bulk operations.
statement st4 = (sql.prepare <<
"delete from soci_test where val = :v", use(v));
st4.execute(true);
CHECK(st4.get_affected_rows() == 5);
std::vector<std::string> w(2, "1");
w[1] = "a"; // this invalid value may cause an exception.
statement st5 = (sql.prepare <<
"insert into soci_test(val) values(:val)", use(w));
try { st5.execute(true); }
catch(...) {}
// confirm the partial insertion.
int val = 0;
sql << "select count(val) from soci_test", into(val);
if(val != 0)
{
// test the preserved 'number of rows
// affected' after a potential failure.
CHECK(st5.get_affected_rows() != 0);
}
}
// test fix for: Backend is not set properly with connection pool (pull #5)
TEST_CASE_METHOD(common_tests, "Backend with connection pool", "[core][pool]")
{
const size_t pool_size = 1;
connection_pool pool(pool_size);
for (std::size_t i = 0; i != pool_size; ++i)
{
session & sql = pool.at(i);
sql.open(backEndFactory_, connectString_);
}
soci::session sql(pool);
sql.reconnect();
sql.begin(); // no crash expected
}
// issue 67 - Allocated statement backend memory leaks on exception
// If the test runs under memory debugger and it passes, then
// soci::details::statement_impl::backEnd_ must not leak
TEST_CASE_METHOD(common_tests, "Backend memory leak", "[core][leak]")
{
soci::session sql(backEndFactory_, connectString_);
auto_table_creator tableCreator(tc_.table_creator_1(sql));
try
{
rowset<row> rs1 = (sql.prepare << "select * from soci_testX");
// TODO: On Linux, no exception thrown; neither from prepare, nor from execute?
// soci_odbc_test_postgresql:
// /home/travis/build/SOCI/soci/src/core/test/common-tests.h:3505:
// void soci::tests::common_tests::test_issue67(): Assertion `!"exception expected"' failed.
//FAIL("exception expected"); // relax temporarily
}
catch (soci_error const &e)
{
(void)e;
}
}
// issue 154 - Calling undefine_and_bind and then define_and_bind causes a leak.
// If the test runs under memory debugger and it passes, then
// soci::details::standard_use_type_backend and vector_use_type_backend must not leak
TEST_CASE_METHOD(common_tests, "Bind memory leak", "[core][leak]")
{
soci::session sql(backEndFactory_, connectString_);
auto_table_creator tableCreator(tc_.table_creator_1(sql));
sql << "insert into soci_test(id) values (1)";
{
int id = 1;
int val = 0;
statement st(sql);
st.exchange(use(id));
st.alloc();
st.prepare("select id from soci_test where id = :1");
st.define_and_bind();
st.undefine_and_bind();
st.exchange(soci::into(val));
st.define_and_bind();
st.execute(true);
CHECK(val == 1);
}
// vector variation
{
std::vector<int> ids(1, 2);
std::vector<int> vals(1, 1);
int val = 0;
statement st(sql);
st.exchange(use(ids));
st.alloc();
st.prepare("insert into soci_test(id, val) values (:1, :2)");
st.define_and_bind();
st.undefine_and_bind();
st.exchange(use(vals));
st.define_and_bind();
st.execute(true);
sql << "select val from soci_test where id = 2", into(val);
CHECK(val == 1);
}
}
TEST_CASE_METHOD(common_tests, "Insert error", "[core][insert][exception]")
{
soci::session sql(backEndFactory_, connectString_);
struct pk_table_creator : table_creator_base
{
explicit pk_table_creator(session& sql) : table_creator_base(sql)
{
// For some backends (at least Firebird), it is important to
// execute the DDL statements in a separate transaction, so start
// one here and commit it before using the new table below.
sql.begin();
sql << "create table soci_test("
"name varchar(100) not null primary key, "
"age integer not null"
")";
sql.commit();
}
} table_creator(sql);
SECTION("literal SQL queries appear in the error message")
{
sql << "insert into soci_test(name, age) values ('John', 74)";
sql << "insert into soci_test(name, age) values ('Paul', 72)";
sql << "insert into soci_test(name, age) values ('George', 72)";
try
{
// Oops, this should have been 'Ringo'
sql << "insert into soci_test(name, age) values ('John', 74)";
FAIL("exception expected on unique constraint violation not thrown");
}
catch (soci_error const &e)
{
std::string const msg = e.what();
CAPTURE(msg);
CHECK(msg.find("John") != std::string::npos);
}
}
SECTION("SQL queries parameters appear in the error message")
{
char const* const names[] = { "John", "Paul", "George", "John", NULL };
int const ages[] = { 74, 72, 72, 74, 0 };
std::string name;
int age;
statement st = (sql.prepare <<
"insert into soci_test(name, age) values (:name, :age)",
use(name), use(age));
try
{
int const *a = ages;
for (char const* const* n = names; n; ++n, ++a)
{
name = *n;
age = *a;
st.execute(true);
}
}
catch (soci_error const &e)
{
std::string const msg = e.what();
CAPTURE(msg);
CHECK(msg.find("John") != std::string::npos);
}
}
}
namespace
{
// This is just a helper to avoid duplicating the same code in two sections in
// the test below, it's logically part of it.
void check_for_exception_on_truncation(session& sql)
{
// As the name column has length 20, inserting a longer string into it
// shouldn't work, unless we're dealing with a database that doesn't
// respect column types at all (hello SQLite).
try
{
std::string const long_name("George Raymond Richard Martin");
sql << "insert into soci_test(name) values(:name)", use(long_name);
// If insert didn't throw, it should have at least preserved the data
// (only SQLite does this currently).
std::string name;
sql << "select name from soci_test", into(name);
CHECK(name == long_name);
}
catch (soci_error const &)
{
// Unfortunately the contents of the message differ too much between
// the backends (most give an error about value being "too long",
// Oracle says "too large" while SQL Server (via ODBC) just says that
// it "would be truncated"), so we can't really check that we received
// the right error here -- be optimistic and hope that we did.
}
}
} // anonymous namespace
TEST_CASE_METHOD(common_tests, "Truncation error", "[core][insert][truncate][exception]")
{
soci::session sql(backEndFactory_, connectString_);
if (tc_.has_silent_truncate_bug(sql))
{
WARN("Database is broken and silently truncates input data.");
return;
}
SECTION("Error given for char column")
{
struct fixed_name_table_creator : table_creator_base
{
fixed_name_table_creator(session& sql)
: table_creator_base(sql)
{
sql << "create table soci_test(name char(20))";
}
} tableCreator(sql);
tc_.on_after_ddl(sql);
check_for_exception_on_truncation(sql);
}
SECTION("Error given for varchar column")
{
// Reuse one of the standard tables which has a varchar(20) column.
auto_table_creator tableCreator(tc_.table_creator_1(sql));
check_for_exception_on_truncation(sql);
}
}
TEST_CASE_METHOD(common_tests, "Blank padding", "[core][insert][exception]")
{
soci::session sql(backEndFactory_, connectString_);
if (!tc_.enable_std_char_padding(sql))
{
WARN("This backend doesn't pad CHAR(N) correctly, skipping test.");
return;
}
struct fixed_name_table_creator : table_creator_base
{
fixed_name_table_creator(session& sql)
: table_creator_base(sql)
{
sql.begin();
sql << "create table soci_test(sc char, name char(10), name2 varchar(10))";
sql.commit();
}
} tableCreator(sql);
std::string test1 = "abcde ";
std::string singleChar = "a";
sql << "insert into soci_test(sc, name,name2) values(:sc,:name,:name2)",
use(singleChar), use(test1), use(test1);
std::string sc, tchar,tvarchar;
sql << "select sc,name,name2 from soci_test",
into(sc), into(tchar), into(tvarchar);
// Firebird can pad "a" to "a " when using UTF-8 encoding.
CHECK_EQUAL_PADDED(sc, singleChar);
CHECK_EQUAL_PADDED(tchar, test1);
CHECK(tvarchar == test1);
// Check 10-space string - same as inserting empty string since spaces will
// be padded up to full size of the column.
test1 = " ";
singleChar = " ";
sql << "update soci_test set sc=:sc, name=:name, name2=:name2",
use(singleChar), use(test1), use(test1);
sql << "select sc, name,name2 from soci_test",
into(sc), into(tchar), into(tvarchar);
CHECK_EQUAL_PADDED(sc, singleChar);
CHECK_EQUAL_PADDED(tchar, test1);
CHECK(tvarchar == test1);
}
} // namespace test_cases
} // namespace tests
} // namespace soci
#endif // SOCI_COMMON_TESTS_H_INCLUDED