mirror of
https://github.com/SOCI/soci.git
synced 2026-02-13 00:59:39 -06:00
This can be avoided by using a different base test context class for the non-empty tests.
1263 lines
33 KiB
C++
1263 lines
33 KiB
C++
//
|
|
// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, Rafal Bobrowski
|
|
// 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)
|
|
//
|
|
//
|
|
|
|
#include "soci/soci.h"
|
|
#include "soci/firebird/soci-firebird.h"
|
|
#include "soci-compiler.h"
|
|
#include "firebird/error-firebird.h" // soci::details::Firebird::throw_iscerror()
|
|
#include "firebird/common.h"
|
|
#include "test-assert.h"
|
|
#include "test-context.h"
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <ctime>
|
|
#include <cstring>
|
|
#include <cmath>
|
|
|
|
#include <catch.hpp>
|
|
|
|
using namespace soci;
|
|
|
|
std::string connectString;
|
|
soci::backend_factory const &backEnd = *factory_firebird();
|
|
|
|
// fundamental tests - transactions in Firebird
|
|
TEST_CASE("Firebird transactions", "[firebird][transaction]")
|
|
{
|
|
soci::session sql(backEnd, connectString);
|
|
|
|
// In Firebird transaction is always required and is started
|
|
// automatically when session is opened. There is no need to
|
|
// call session::begin(); it will do nothing if there is active
|
|
// transaction.
|
|
|
|
// sql.begin();
|
|
|
|
try
|
|
{
|
|
sql << "drop table test1";
|
|
}
|
|
catch (soci_error const &)
|
|
{} // ignore if error
|
|
|
|
sql << "create table test1 (id integer)";
|
|
|
|
// After DDL statement transaction must be commited or changes
|
|
// won't be visible to active transaction.
|
|
sql.commit();
|
|
|
|
// After commit or rollback, transaction must be started manually.
|
|
sql.begin();
|
|
|
|
sql << "insert into test1(id) values(5)";
|
|
sql << "drop table test1";
|
|
|
|
// Transaction is automatically commited in session's destructor
|
|
}
|
|
|
|
// character types
|
|
TEST_CASE("Firebird char types", "[firebird][string]")
|
|
{
|
|
soci::session sql(backEnd, connectString);
|
|
|
|
try
|
|
{
|
|
sql << "drop table test2";
|
|
}
|
|
catch (soci_error const &)
|
|
{} // ignore if error
|
|
|
|
sql << "create table test2 (p1 char(10) character set none, p2 varchar(10) character set none)";
|
|
sql.commit();
|
|
|
|
sql.begin();
|
|
|
|
{
|
|
char a('a'), b('b'), c1, c2;
|
|
|
|
sql << "insert into test2(p1,p2) values(?,?)", use(a), use(b);
|
|
|
|
sql << "select p1,p2 from test2", into(c1), into(c2);
|
|
CHECK(c1 == 'a');
|
|
CHECK(c2 == 'b');
|
|
|
|
sql << "delete from test2";
|
|
}
|
|
|
|
#if 0 // SOCI doesn't support binding into(char *, ...) anymore, use std::string
|
|
{
|
|
char msg[] = "Hello, Firebird!";
|
|
char buf1[100], buf2[100], buf3[100];
|
|
char *b1 = buf1, *b2 = buf2, *b3 = buf3;
|
|
|
|
strcpy(b1, msg);
|
|
|
|
sql << "insert into test2(p1, p2) values (?,?)", use(b1, 100), use(b1, 100);
|
|
sql << "select p1, p2 from test2", into(b2, 100), into(b3, 100);
|
|
|
|
CHECK(!std::strcmp(buf2, buf3));
|
|
CHECK(!std::strcmp(buf2, "Hello, Fir"));
|
|
|
|
sql << "delete from test2";
|
|
}
|
|
|
|
{
|
|
char msg[] = "Hello, Firebird!";
|
|
char buf1[100], buf2[100], buf3[100];
|
|
strcpy(buf1, msg);
|
|
|
|
sql << "insert into test2(p1, p2) values (?,?)",
|
|
use(buf1), use(buf1);
|
|
sql << "select p1, p2 from test2", into(buf2), into(buf3);
|
|
|
|
CHECK(!std::strcmp(buf2, buf3));
|
|
CHECK(!std::strcmp(buf2, "Hello, Fir"));
|
|
|
|
sql << "delete from test2";
|
|
}
|
|
#endif
|
|
|
|
{
|
|
// The test string is exactly 10 bytes long, i.e. same as column length.
|
|
std::string b1("Hello, FB!"), b2, b3;
|
|
|
|
sql << "insert into test2(p1, p2) values (?,?)", use(b1), use(b1);
|
|
sql << "select p1, p2 from test2", into(b2), into(b3);
|
|
|
|
CHECK(b2 == b3);
|
|
CHECK(b2 == "Hello, FB!");
|
|
|
|
sql << "delete from test2";
|
|
}
|
|
|
|
{
|
|
// verify blank padding in CHAR fields
|
|
// In Firebird, CHAR fields are always padded with whitespaces.
|
|
char msg[] = "Hello";
|
|
sql << "insert into test2(p1) values(\'" << msg << "\')";
|
|
|
|
char buf[20];
|
|
std::string buf_str;
|
|
sql << "select p1 from test2", into(buf_str);
|
|
std::strcpy(buf, buf_str.c_str());
|
|
|
|
CHECK(std::strncmp(buf, msg, 5) == 0);
|
|
// This test works only for charset none
|
|
CHECK(std::strncmp(buf+5, " ", 5) == 0);
|
|
|
|
sql << "delete from test2";
|
|
}
|
|
|
|
sql << "drop table test2";
|
|
}
|
|
|
|
// date and time
|
|
TEST_CASE("Firebird date and time", "[firebird][datetime]")
|
|
{
|
|
soci::session sql(backEnd, connectString);
|
|
|
|
try
|
|
{
|
|
sql << "drop table test3";
|
|
}
|
|
catch (soci_error const &)
|
|
{} // ignore if error
|
|
|
|
sql << "create table test3 (p1 timestamp, p2 date, p3 time)";
|
|
sql.commit();
|
|
|
|
sql.begin();
|
|
|
|
std::tm t1 = std::tm();
|
|
std::tm t2 = std::tm();
|
|
std::tm t3 = std::tm();
|
|
std::time_t now = std::time(NULL);
|
|
std::tm t = *std::localtime(&now);
|
|
sql << "insert into test3(p1, p2, p3) "
|
|
<< "values (?,?,?)", use(t), use(t), use(t);
|
|
|
|
sql << "select p1, p2, p3 from test3", into(t1), into(t2), into(t3);
|
|
|
|
// timestamp
|
|
CHECK(t1.tm_year == t.tm_year);
|
|
CHECK(t1.tm_mon == t.tm_mon);
|
|
CHECK(t1.tm_mday == t.tm_mday);
|
|
CHECK(t1.tm_hour == t.tm_hour);
|
|
CHECK(t1.tm_min == t.tm_min);
|
|
CHECK(t1.tm_sec == t.tm_sec);
|
|
|
|
// date
|
|
CHECK(t2.tm_year == t.tm_year);
|
|
CHECK(t2.tm_mon == t.tm_mon);
|
|
CHECK(t2.tm_mday == t.tm_mday);
|
|
CHECK(t2.tm_hour == 0);
|
|
CHECK(t2.tm_min == 0);
|
|
CHECK(t2.tm_sec == 0);
|
|
|
|
// time
|
|
CHECK(t3.tm_year == 0);
|
|
CHECK(t3.tm_mon == 0);
|
|
CHECK(t3.tm_mday == 0);
|
|
CHECK(t3.tm_hour == t.tm_hour);
|
|
CHECK(t3.tm_min == t.tm_min);
|
|
CHECK(t3.tm_sec == t.tm_sec);
|
|
|
|
sql << "drop table test3";
|
|
}
|
|
|
|
// floating points
|
|
TEST_CASE("Firebird floating point", "[firebird][float]")
|
|
{
|
|
soci::session sql(backEnd, connectString);
|
|
|
|
try
|
|
{
|
|
sql << "drop table test4";
|
|
}
|
|
catch (soci_error const &)
|
|
{} // ignore if error
|
|
|
|
sql << "create table test4 (p1 numeric(8,2), "
|
|
<< "p2 decimal(14,8), p3 double precision, p4 integer)";
|
|
sql.commit();
|
|
|
|
sql.begin();
|
|
|
|
double d1 = 1234.23, d2 = 1e8, d3 = 1.0/1440.0,
|
|
d4, d5, d6;
|
|
|
|
sql << "insert into test4(p1, p2, p3) values (?,?,?)",
|
|
use(d1), use(d2), use(d3);
|
|
|
|
sql << "select p1, p2, p3 from test4",
|
|
into(d4), into(d5), into(d6);
|
|
|
|
// The doubles should make the round trip unchanged, so use the exact
|
|
// comparisons here.
|
|
CHECK(tests::are_doubles_exactly_equal(d1, d4));
|
|
CHECK(tests::are_doubles_exactly_equal(d2, d5));
|
|
CHECK(tests::are_doubles_exactly_equal(d3, d6));
|
|
|
|
// test negative doubles too
|
|
sql << "delete from test4";
|
|
d1 = -d1;
|
|
d2 = -d2;
|
|
d3 = -d3;
|
|
|
|
sql << "insert into test4(p1, p2, p3) values (?,?,?)",
|
|
use(d1), use(d2), use(d3);
|
|
|
|
sql << "select p1, p2, p3 from test4",
|
|
into(d4), into(d5), into(d6);
|
|
|
|
CHECK(tests::are_doubles_exactly_equal(d1, d4));
|
|
CHECK(tests::are_doubles_exactly_equal(d2, d5));
|
|
CHECK(tests::are_doubles_exactly_equal(d3, d6));
|
|
|
|
// verify an exception is thrown when fetching non-integral value
|
|
// to integral variable
|
|
try
|
|
{
|
|
int i;
|
|
sql << "select p1 from test4", into(i);
|
|
|
|
// expecting error
|
|
CHECK(false);
|
|
}
|
|
catch (soci_error const &e)
|
|
{
|
|
CHECK(e.get_error_message() ==
|
|
"Can't convert value with scale 2 to integral type");
|
|
}
|
|
|
|
// verify an exception is thrown when inserting non-integral value
|
|
// to integral column
|
|
try
|
|
{
|
|
sql << "insert into test4(p4) values(?)", use(d1);
|
|
|
|
// expecting error
|
|
CHECK(false);
|
|
}
|
|
catch (soci_error const &e)
|
|
{
|
|
CHECK(e.get_error_message() ==
|
|
"Can't convert non-integral value to integral column type");
|
|
}
|
|
|
|
sql << "drop table test4";
|
|
}
|
|
|
|
// integer types and indicators
|
|
TEST_CASE("Firebird integers", "[firebird][int]")
|
|
{
|
|
soci::session sql(backEnd, connectString);
|
|
|
|
{
|
|
short sh(0);
|
|
sql << "select 3 from rdb$database", into(sh);
|
|
CHECK(sh == 3);
|
|
}
|
|
|
|
{
|
|
int i(0);
|
|
sql << "select 5 from rdb$database", into(i);
|
|
CHECK(i == 5);
|
|
}
|
|
|
|
{
|
|
unsigned long ul(0);
|
|
sql << "select 7 from rdb$database", into(ul);
|
|
CHECK(ul == 7);
|
|
}
|
|
|
|
{
|
|
// test indicators
|
|
indicator ind;
|
|
int i;
|
|
|
|
sql << "select 2 from rdb$database", into(i, ind);
|
|
CHECK(ind == i_ok);
|
|
|
|
sql << "select NULL from rdb$database", into(i, ind);
|
|
CHECK(ind == i_null);
|
|
|
|
#if 0 // SOCI doesn't support binding into(char *, ...) anymore, use std::string
|
|
char buf[4];
|
|
sql << "select \'Hello\' from rdb$database", into(buf, ind);
|
|
CHECK(ind == i_truncated);
|
|
#endif
|
|
|
|
sql << "select 5 from rdb$database where 0 = 1", into(i, ind);
|
|
CHECK(sql.got_data() == false);
|
|
|
|
try
|
|
{
|
|
// expect error
|
|
sql << "select NULL from rdb$database", into(i);
|
|
CHECK(false);
|
|
}
|
|
catch (soci_error const &e)
|
|
{
|
|
CHECK(e.get_error_message() ==
|
|
"Null value fetched and no indicator defined.");
|
|
}
|
|
|
|
// expect no data
|
|
sql << "select 5 from rdb$database where 0 = 1", into(i);
|
|
CHECK(!sql.got_data());
|
|
}
|
|
}
|
|
|
|
// repeated fetch and bulk operations for character types
|
|
TEST_CASE("Firebird bulk operations", "[firebird][bulk]")
|
|
{
|
|
soci::session sql(backEnd, connectString);
|
|
|
|
try
|
|
{
|
|
sql << "drop table test6";
|
|
}
|
|
catch (soci_error const &)
|
|
{} // ignore if error
|
|
|
|
sql << "create table test6 (p1 char(10) character set none, p2 varchar(10) character set none)";
|
|
sql.commit();
|
|
|
|
sql.begin();
|
|
|
|
for (char c = 'a'; c <= 'z'; ++c)
|
|
{
|
|
sql << "insert into test6(p1, p2) values(?,?)", use(c), use(c);
|
|
}
|
|
|
|
{
|
|
char c, c1, c2;
|
|
|
|
statement st = (sql.prepare <<
|
|
"select p1,p2 from test6 order by p1", into(c1), into(c2));
|
|
|
|
// Verify that fetch after re-executing the same statement works.
|
|
for (int n = 0; n < 2; ++n)
|
|
{
|
|
st.execute();
|
|
|
|
c='a';
|
|
while (st.fetch())
|
|
{
|
|
CHECK(c == c1);
|
|
CHECK(c == c2);
|
|
++c;
|
|
}
|
|
CHECK(c == 'z'+1);
|
|
}
|
|
}
|
|
|
|
{
|
|
char c='a';
|
|
|
|
std::vector<char> c1(10), c2(10);
|
|
|
|
statement st = (sql.prepare <<
|
|
"select p1,p2 from test6 order by p1", into(c1), into(c2));
|
|
|
|
st.execute();
|
|
while (st.fetch())
|
|
{
|
|
for (std::size_t i = 0; i != c1.size(); ++i)
|
|
{
|
|
CHECK(c == c1[i]);
|
|
CHECK(c == c2[i]);
|
|
++c;
|
|
}
|
|
}
|
|
CHECK(c == 'z' + 1);
|
|
}
|
|
|
|
{
|
|
// verify an exception is thrown when empty vector is used
|
|
std::vector<char> vec;
|
|
try
|
|
{
|
|
sql << "select p1 from test6", into(vec);
|
|
CHECK(false);
|
|
}
|
|
catch (soci_error const &e)
|
|
{
|
|
CHECK(e.get_error_message() ==
|
|
"Vectors of size 0 are not allowed.");
|
|
}
|
|
}
|
|
|
|
sql << "delete from test6";
|
|
|
|
// verifying std::string
|
|
int const rowsToTest = 10;
|
|
for (int i = 0; i != rowsToTest; ++i)
|
|
{
|
|
std::ostringstream ss;
|
|
ss << "Hello_" << i;
|
|
|
|
std::string const &x = ss.str();
|
|
|
|
sql << "insert into test6(p1, p2) values(\'"
|
|
<< x << "\', \'" << x << "\')";
|
|
}
|
|
|
|
int count;
|
|
sql << "select count(*) from test6", into(count);
|
|
CHECK(count == rowsToTest);
|
|
|
|
{
|
|
int i = 0;
|
|
std::string s1, s2;
|
|
statement st = (sql.prepare <<
|
|
"select p1, p2 from test6 order by p1", into(s1), into(s2));
|
|
|
|
st.execute();
|
|
while (st.fetch())
|
|
{
|
|
std::ostringstream ss;
|
|
ss << "Hello_" << i;
|
|
std::string const &x = ss.str();
|
|
|
|
// Note: CHAR fields are always padded with whitespaces
|
|
ss << " ";
|
|
CHECK(s1 == ss.str());
|
|
CHECK(s2 == x);
|
|
++i;
|
|
}
|
|
CHECK(i == rowsToTest);
|
|
}
|
|
|
|
{
|
|
int i = 0;
|
|
|
|
std::vector<std::string> s1(4), s2(4);
|
|
statement st = (sql.prepare <<
|
|
"select p1, p2 from test6 order by p1", into(s1), into(s2));
|
|
st.execute();
|
|
while (st.fetch())
|
|
{
|
|
for (std::size_t j = 0; j != s1.size(); ++j)
|
|
{
|
|
std::ostringstream ss;
|
|
ss << "Hello_" << i;
|
|
std::string const &x = ss.str();
|
|
|
|
// Note: CHAR fields are always padded with whitespaces
|
|
ss << " ";
|
|
CHECK(ss.str() == s1[j]);
|
|
CHECK(x == s2[j]);
|
|
++i;
|
|
}
|
|
}
|
|
CHECK(i == rowsToTest);
|
|
}
|
|
|
|
sql << "drop table test6";
|
|
}
|
|
|
|
// named parameters
|
|
TEST_CASE("Firebird named parameters", "[firebird][named-params]")
|
|
{
|
|
soci::session sql(backEnd, connectString);
|
|
|
|
try
|
|
{
|
|
sql << "drop table test8";
|
|
}
|
|
catch (std::runtime_error &)
|
|
{} // ignore if error
|
|
|
|
sql << "create table test8(id1 integer, id2 integer)";
|
|
sql.commit();
|
|
|
|
sql.begin();
|
|
|
|
int j = 13, k = 4, i, m;
|
|
sql << "insert into test8(id1, id2) values(:id1, :id2)",
|
|
use(k, "id2"), use(j, "id1");
|
|
sql << "select id1, id2 from test8", into(i), into(m);
|
|
CHECK(i == j);
|
|
CHECK(m == k);
|
|
|
|
sql << "delete from test8";
|
|
|
|
std::vector<int> in1(3), in2(3);
|
|
in1[0] = 3;
|
|
in1[1] = 2;
|
|
in1[2] = 1;
|
|
in2[0] = 4;
|
|
in2[1] = 5;
|
|
in2[2] = 6;
|
|
|
|
{
|
|
statement st = (sql.prepare <<
|
|
"insert into test8(id1, id2) values(:id1, :id2)",
|
|
use(k, "id2"), use(j, "id1"));
|
|
|
|
std::size_t s = in1.size();
|
|
for (std::size_t x = 0; x < s; ++x)
|
|
{
|
|
j = in1[x];
|
|
k = in2[x];
|
|
st.execute();
|
|
}
|
|
}
|
|
|
|
{
|
|
statement st = (
|
|
sql.prepare << "select id1, id2 from test8", into(i), into(m));
|
|
st.execute();
|
|
|
|
std::size_t x(0);
|
|
while (st.fetch())
|
|
{
|
|
CHECK(i == in1[x]);
|
|
CHECK(m == in2[x]);
|
|
++x;
|
|
}
|
|
}
|
|
|
|
sql << "delete from test8";
|
|
|
|
// test vectors
|
|
sql << "insert into test8(id1, id2) values(:id1, :id2)",
|
|
use(in1, "id1"), use(in2, "id2");
|
|
|
|
std::vector<int> out1(3), out2(3);
|
|
|
|
sql << "select id1, id2 from test8", into(out1), into(out2);
|
|
std::size_t s = out1.size();
|
|
CHECK(s == 3);
|
|
|
|
for (std::size_t x = 0; x<s; ++x)
|
|
{
|
|
CHECK(out1[x] == in1[x]);
|
|
CHECK(out2[x] == in2[x]);
|
|
}
|
|
|
|
sql << "drop table test8";
|
|
}
|
|
|
|
// Dynamic binding to row objects
|
|
TEST_CASE("Firebird dynamic binding", "[firebird][dynamic]")
|
|
{
|
|
soci::session sql(backEnd, connectString);
|
|
|
|
try
|
|
{
|
|
sql << "drop table test9";
|
|
}
|
|
catch (std::runtime_error &)
|
|
{} // ignore if error
|
|
|
|
sql << "create table test9(id integer, msg varchar(20), ntest numeric(10,2))";
|
|
sql.commit();
|
|
|
|
sql.begin();
|
|
|
|
{
|
|
row r;
|
|
sql << "select * from test9", into(r);
|
|
CHECK(sql.got_data() == false);
|
|
}
|
|
|
|
std::string msg("Hello");
|
|
int i(1);
|
|
double d(3.14);
|
|
indicator ind(i_ok);
|
|
|
|
{
|
|
statement st((sql.prepare << "insert into test9(id, msg, ntest) "
|
|
<< "values(:id,:msg,:ntest)",
|
|
use(i, "id"), use(msg, "msg"), use(d, ind, "ntest")));
|
|
|
|
st.execute(1);
|
|
|
|
i = 2;
|
|
msg = "Firebird";
|
|
ind = i_null;
|
|
st.execute(1);
|
|
}
|
|
|
|
row r;
|
|
statement st = (sql.prepare <<
|
|
"select * from test9", into(r));
|
|
st.execute(1);
|
|
|
|
CHECK(r.size() == 3);
|
|
|
|
// get properties by position
|
|
CHECK(r.get_properties(0).get_name() == "ID");
|
|
CHECK(r.get_properties(1).get_name() == "MSG");
|
|
CHECK(r.get_properties(2).get_name() == "NTEST");
|
|
|
|
CHECK(r.get_properties(0).get_data_type() == dt_integer);
|
|
CHECK(r.get_properties(0).get_db_type() == db_int32);
|
|
CHECK(r.get_properties(1).get_data_type() == dt_string);
|
|
CHECK(r.get_properties(1).get_db_type() == db_string);
|
|
CHECK(r.get_properties(2).get_data_type() == dt_double);
|
|
CHECK(r.get_properties(2).get_db_type() == db_double);
|
|
|
|
// get properties by name
|
|
CHECK(r.get_properties("ID").get_name() == "ID");
|
|
CHECK(r.get_properties("MSG").get_name() == "MSG");
|
|
CHECK(r.get_properties("NTEST").get_name() == "NTEST");
|
|
|
|
CHECK(r.get_properties("ID").get_data_type() == dt_integer);
|
|
CHECK(r.get_properties("ID").get_db_type() == db_int32);
|
|
CHECK(r.get_properties("MSG").get_data_type() == dt_string);
|
|
CHECK(r.get_properties("MSG").get_db_type() == db_string);
|
|
CHECK(r.get_properties("NTEST").get_data_type() == dt_double);
|
|
CHECK(r.get_properties("NTEST").get_db_type() == db_double);
|
|
|
|
// get values by position
|
|
CHECK(r.get<int>(0) == 1);
|
|
CHECK(r.get<std::string>(1) == "Hello");
|
|
CHECK(tests::are_doubles_exactly_equal(r.get<double>(2), d));
|
|
|
|
// get values by name
|
|
CHECK(r.get<int>("ID") == 1);
|
|
CHECK(r.get<std::string>("MSG") == "Hello");
|
|
CHECK(tests::are_doubles_exactly_equal(r.get<double>("NTEST"), d));
|
|
|
|
st.fetch();
|
|
CHECK(r.get<int>(0) == 2);
|
|
CHECK(r.get<std::string>("MSG") == "Firebird");
|
|
CHECK(r.get_indicator(2) == i_null);
|
|
|
|
// verify default values
|
|
CHECK(tests::are_doubles_exactly_equal(r.get<double>("NTEST", 2), 2));
|
|
|
|
CHECK_THROWS_AS(r.get<double>("NTEST"), soci_error);
|
|
|
|
// verify exception thrown on invalid get<>
|
|
CHECK_THROWS_AS(r.get<std::string>(0), std::bad_cast);
|
|
|
|
sql << "drop table test9";
|
|
}
|
|
|
|
// stored procedures
|
|
TEST_CASE("Firebird stored procedures", "[firebird][procedure]")
|
|
{
|
|
soci::session sql(backEnd, connectString);
|
|
|
|
try
|
|
{
|
|
sql << "drop procedure sp_test10";
|
|
}
|
|
catch (std::runtime_error &)
|
|
{} // ignore if error
|
|
|
|
try
|
|
{
|
|
sql << "drop procedure sp_test10a";
|
|
}
|
|
catch (std::runtime_error &)
|
|
{} // ignore if error
|
|
|
|
try
|
|
{
|
|
sql << "drop table test10";
|
|
}
|
|
catch (std::runtime_error &)
|
|
{} // ignore if error
|
|
|
|
sql << "create table test10(id integer, id2 integer)";
|
|
|
|
sql << "create procedure sp_test10\n"
|
|
<< "returns (rid integer, rid2 integer)\n"
|
|
<< "as begin\n"
|
|
<< "for select id, id2 from test10 into rid, rid2 do begin\n"
|
|
<< "suspend;\n"
|
|
<< "end\n"
|
|
<< "end;\n";
|
|
|
|
sql << "create procedure sp_test10a (pid integer, pid2 integer)\n"
|
|
<< "as begin\n"
|
|
<< "insert into test10(id, id2) values (:pid, :pid2);\n"
|
|
<< "end;\n";
|
|
|
|
sql.commit();
|
|
|
|
sql.begin();
|
|
|
|
row r;
|
|
int p1 = 3, p2 = 4;
|
|
|
|
// calling procedures that do not return values requires
|
|
// 'execute procedure ...' statement
|
|
sql << "execute procedure sp_test10a ?, ?", use(p1), use(p2);
|
|
|
|
// calling procedures that return values requires
|
|
// 'select ... from ...' statement
|
|
sql << "select * from sp_test10", into(r);
|
|
|
|
CHECK(r.get<int>(0) == p1);
|
|
CHECK(r.get<int>(1) == p2);
|
|
|
|
sql << "delete from test10";
|
|
|
|
p1 = 5;
|
|
p2 = 6;
|
|
{
|
|
procedure proc = (
|
|
sql.prepare << "sp_test10a :p1, :p2",
|
|
use(p2, "p2"), use(p1, "p1"));
|
|
proc.execute(1);
|
|
}
|
|
|
|
{
|
|
row rw;
|
|
procedure proc = (sql.prepare << "sp_test10", into(rw));
|
|
proc.execute(1);
|
|
|
|
CHECK(rw.get<int>(0) == p1);
|
|
CHECK(rw.get<int>(1) == p2);
|
|
}
|
|
|
|
sql << "delete from test10";
|
|
|
|
// test vectors
|
|
std::vector<int> in1(3), in2(3);
|
|
in1[0] = 3;
|
|
in1[1] = 2;
|
|
in1[2] = 1;
|
|
in2[0] = 4;
|
|
in2[1] = 5;
|
|
in2[2] = 6;
|
|
|
|
{
|
|
procedure proc = (
|
|
sql.prepare << "sp_test10a :p1, :p2",
|
|
use(in2, "p2"), use(in1, "p1"));
|
|
proc.execute(1);
|
|
}
|
|
|
|
{
|
|
row rw;
|
|
procedure proc = (sql.prepare << "sp_test10", into(rw));
|
|
|
|
proc.execute(1);
|
|
CHECK(rw.get<int>(0) == in1[0]);
|
|
CHECK(rw.get<int>(1) == in2[0]);
|
|
proc.fetch();
|
|
CHECK(rw.get<int>(0) == in1[1]);
|
|
CHECK(rw.get<int>(1) == in2[1]);
|
|
proc.fetch();
|
|
CHECK(rw.get<int>(0) == in1[2]);
|
|
CHECK(rw.get<int>(1) == in2[2]);
|
|
CHECK(proc.fetch() == false);
|
|
}
|
|
|
|
{
|
|
std::vector<int> out1(3), out2(3);
|
|
procedure proc = (sql.prepare << "sp_test10", into(out1), into(out2));
|
|
proc.execute(1);
|
|
|
|
std::size_t s = out1.size();
|
|
CHECK(s == 3);
|
|
|
|
for (std::size_t x = 0; x < s; ++x)
|
|
{
|
|
CHECK(out1[x] == in1[x]);
|
|
CHECK(out2[x] == in2[x]);
|
|
}
|
|
}
|
|
|
|
sql.rollback();
|
|
|
|
sql.begin();
|
|
sql << "drop procedure sp_test10";
|
|
sql << "drop procedure sp_test10a";
|
|
sql << "drop table test10";
|
|
}
|
|
|
|
// direct access to Firebird using handles exposed by
|
|
// soci::FirebirdStatmentBackend
|
|
namespace soci
|
|
{
|
|
enum eRowCountType
|
|
{
|
|
eRowsSelected = isc_info_req_select_count,
|
|
eRowsInserted = isc_info_req_insert_count,
|
|
eRowsUpdated = isc_info_req_update_count,
|
|
eRowsDeleted = isc_info_req_delete_count
|
|
};
|
|
|
|
// Returns number of rows afected by last statement
|
|
// or -1 if there is no such counter available.
|
|
long getRowCount(soci::statement & statement, eRowCountType type)
|
|
{
|
|
ISC_STATUS stat[20];
|
|
char cnt_req[2], cnt_info[128];
|
|
|
|
cnt_req[0]=isc_info_sql_records;
|
|
cnt_req[1]=isc_info_end;
|
|
|
|
firebird_statement_backend* statementBackEnd
|
|
= static_cast<firebird_statement_backend*>(statement.get_backend());
|
|
|
|
// Note: This is very poorly documented function.
|
|
// It can extract number of rows returned by select statement,
|
|
// but it appears that this is only number of rows prefetched by
|
|
// client library, not total number of selected rows.
|
|
if (isc_dsql_sql_info(stat, &statementBackEnd->stmtp_, sizeof(cnt_req),
|
|
cnt_req, sizeof(cnt_info), cnt_info))
|
|
{
|
|
soci::details::firebird::throw_iscerror(stat);
|
|
}
|
|
|
|
long count = -1;
|
|
char type_ = static_cast<char>(type);
|
|
for (char *ptr = cnt_info + 3; *ptr != isc_info_end;)
|
|
{
|
|
char count_type = *ptr++;
|
|
int m = isc_vax_integer(ptr, 2);
|
|
ptr += 2;
|
|
count = isc_vax_integer(ptr, static_cast<short>(m));
|
|
|
|
if (count_type == type_)
|
|
{
|
|
// this is requested number
|
|
break;
|
|
}
|
|
ptr += m;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
} // namespace soci
|
|
|
|
TEST_CASE("Firebird direct API use", "[firebird][native]")
|
|
{
|
|
soci::session sql(backEnd, connectString);
|
|
|
|
try
|
|
{
|
|
sql << "drop table test11";
|
|
}
|
|
catch (std::runtime_error &)
|
|
{} // ignore if error
|
|
|
|
sql << "create table test11(id integer)";
|
|
sql.commit();
|
|
|
|
sql.begin();
|
|
|
|
{
|
|
std::vector<int> in(3);
|
|
in[0] = 3;
|
|
in[1] = 2;
|
|
in[2] = 1;
|
|
|
|
statement st = (sql.prepare << "insert into test11(id) values(?)",
|
|
use(in));
|
|
st.execute(1);
|
|
|
|
// Note: Firebird backend inserts every row with separate insert
|
|
// statement to achieve the effect of inserting vectors of values.
|
|
// Since getRowCount() returns number of rows affected by the *last*
|
|
// statement, it will return 1 here.
|
|
CHECK(getRowCount(st, eRowsInserted) == 1);
|
|
}
|
|
|
|
{
|
|
int i = 5;
|
|
statement st = (sql.prepare << "update test11 set id = ? where id<3",
|
|
use(i));
|
|
st.execute(1);
|
|
CHECK(getRowCount(st, eRowsUpdated) == 2);
|
|
|
|
// verify that no rows were deleted
|
|
CHECK(getRowCount(st, eRowsDeleted) == 0);
|
|
}
|
|
|
|
{
|
|
std::vector<int> out(3);
|
|
statement st = (sql.prepare << "select id from test11", into(out));
|
|
st.execute(1);
|
|
|
|
CHECK(getRowCount(st, eRowsSelected) == 3);
|
|
}
|
|
|
|
{
|
|
statement st = (sql.prepare << "delete from test11 where id=10");
|
|
st.execute(1);
|
|
CHECK(getRowCount(st, eRowsDeleted) == 0);
|
|
}
|
|
|
|
{
|
|
statement st = (sql.prepare << "delete from test11");
|
|
st.execute(1);
|
|
CHECK(getRowCount(st, eRowsDeleted) == 3);
|
|
}
|
|
|
|
sql << "drop table test11";
|
|
}
|
|
|
|
TEST_CASE("Firebird string coercions", "[firebird][string]")
|
|
{
|
|
soci::session sql(backEnd, connectString);
|
|
|
|
try
|
|
{
|
|
sql << "drop table test12";
|
|
}
|
|
catch (std::runtime_error &)
|
|
{} // ignore if error
|
|
|
|
sql << "create table test12(a decimal(10,3), b timestamp, c date, d time)";
|
|
sql.commit();
|
|
sql.begin();
|
|
|
|
// Check if passing input parameters as strings works
|
|
// for different column types.
|
|
{
|
|
std::string a = "-3.14150", b = "2013-02-28 23:36:01",
|
|
c = "2013-02-28", d = "23:36:01";
|
|
statement st = (sql.prepare <<
|
|
"insert into test12(a, b, c, d) values (?, ?, ?, ?)",
|
|
use(a), use(b), use(c), use(d));
|
|
st.execute(1);
|
|
CHECK(getRowCount(st, eRowsInserted) == 1);
|
|
}
|
|
|
|
{
|
|
double a;
|
|
std::tm b = std::tm(), c = std::tm(), d = std::tm();
|
|
sql << "select a, b, c, d from test12",
|
|
into(a), into(b), into(c), into(d);
|
|
CHECK(std::fabs(a - (-3.141)) < 0.000001);
|
|
CHECK(b.tm_year == 2013 - 1900);
|
|
CHECK(b.tm_mon == 2 - 1);
|
|
CHECK(b.tm_mday == 28);
|
|
CHECK(b.tm_hour == 23);
|
|
CHECK(b.tm_min == 36);
|
|
CHECK(b.tm_sec == 1);
|
|
CHECK(c.tm_year == 2013 - 1900);
|
|
CHECK(c.tm_mon == 2 - 1);
|
|
CHECK(c.tm_mday == 28);
|
|
CHECK(c.tm_hour == 0);
|
|
CHECK(c.tm_min == 0);
|
|
CHECK(c.tm_sec == 0);
|
|
CHECK(d.tm_hour == 23);
|
|
CHECK(d.tm_min == 36);
|
|
CHECK(d.tm_sec == 1);
|
|
}
|
|
|
|
sql << "drop table test12";
|
|
}
|
|
|
|
// Dynamic binding to row objects: decimals_as_strings
|
|
TEST_CASE("Firebird decimals as strings", "[firebird][decimal][string]")
|
|
{
|
|
using namespace soci::details::firebird;
|
|
|
|
int a = -12345678;
|
|
CHECK(format_decimal<int>(&a, 1) == "-123456780");
|
|
CHECK(format_decimal<int>(&a, 0) == "-12345678");
|
|
CHECK(format_decimal<int>(&a, -3) == "-12345.678");
|
|
CHECK(format_decimal<int>(&a, -8) == "-0.12345678");
|
|
CHECK(format_decimal<int>(&a, -9) == "-0.012345678");
|
|
|
|
a = 12345678;
|
|
CHECK(format_decimal<int>(&a, 1) == "123456780");
|
|
CHECK(format_decimal<int>(&a, 0) == "12345678");
|
|
CHECK(format_decimal<int>(&a, -3) == "12345.678");
|
|
CHECK(format_decimal<int>(&a, -8) == "0.12345678");
|
|
CHECK(format_decimal<int>(&a, -9) == "0.012345678");
|
|
|
|
soci::session sql(backEnd, connectString + " decimals_as_strings=1");
|
|
|
|
try
|
|
{
|
|
sql << "drop table test13";
|
|
}
|
|
catch (std::runtime_error &)
|
|
{} // ignore if error
|
|
|
|
sql << "create table test13(ntest1 decimal(10,2), "
|
|
<< "ntest2 decimal(4,4), ntest3 decimal(3,1))";
|
|
sql.commit();
|
|
|
|
sql.begin();
|
|
|
|
{
|
|
row r;
|
|
sql << "select * from test13", into(r);
|
|
CHECK(sql.got_data() == false);
|
|
}
|
|
|
|
std::string d_str0("+03.140"), d_str1("3.14"),
|
|
d_str2("3.1400"), d_str3("3.1");
|
|
indicator ind(i_ok);
|
|
|
|
{
|
|
statement st((sql.prepare <<
|
|
"insert into test13(ntest1, ntest2, ntest3) "
|
|
"values(:ntest1, :ntest2, :ntest3)",
|
|
use(d_str0, ind, "ntest1"), use(d_str0, "ntest2"),
|
|
use(d_str0, "ntest3")));
|
|
|
|
st.execute(1);
|
|
|
|
ind = i_null;
|
|
st.execute(1);
|
|
}
|
|
|
|
row r;
|
|
statement st = (sql.prepare << "select * from test13", into(r));
|
|
st.execute(1);
|
|
|
|
CHECK(r.size() == 3);
|
|
|
|
// get properties by position
|
|
CHECK(r.get_properties(0).get_name() == "NTEST1");
|
|
CHECK(r.get_properties(0).get_data_type() == dt_string);
|
|
CHECK(r.get_properties(0).get_db_type() == db_string);
|
|
CHECK(r.get_properties(1).get_name() == "NTEST2");
|
|
CHECK(r.get_properties(1).get_data_type() == dt_string);
|
|
CHECK(r.get_properties(1).get_db_type() == db_string);
|
|
CHECK(r.get_properties(2).get_name() == "NTEST3");
|
|
CHECK(r.get_properties(2).get_data_type() == dt_string);
|
|
CHECK(r.get_properties(2).get_db_type() == db_string);
|
|
|
|
// get properties by name
|
|
CHECK(r.get_properties("NTEST1").get_name() == "NTEST1");
|
|
CHECK(r.get_properties("NTEST1").get_data_type() == dt_string);
|
|
CHECK(r.get_properties("NTEST1").get_db_type() == db_string);
|
|
CHECK(r.get_properties("NTEST2").get_name() == "NTEST2");
|
|
CHECK(r.get_properties("NTEST2").get_data_type() == dt_string);
|
|
CHECK(r.get_properties("NTEST2").get_db_type() == db_string);
|
|
CHECK(r.get_properties("NTEST3").get_name() == "NTEST3");
|
|
CHECK(r.get_properties("NTEST3").get_data_type() == dt_string);
|
|
CHECK(r.get_properties("NTEST3").get_db_type() == db_string);
|
|
|
|
// get values by position
|
|
CHECK(r.get<std::string>(0) == d_str1);
|
|
CHECK(r.get<std::string>(1) == d_str2);
|
|
CHECK(r.get<std::string>(2) == d_str3);
|
|
|
|
// get values by name
|
|
CHECK(r.get<std::string>("NTEST1") == d_str1);
|
|
CHECK(r.get<std::string>("NTEST2") == d_str2);
|
|
CHECK(r.get<std::string>("NTEST3") == d_str3);
|
|
|
|
st.fetch();
|
|
CHECK(r.get_indicator(0) == i_null);
|
|
CHECK(r.get_indicator(1) == i_ok);
|
|
CHECK(r.get_indicator(2) == i_ok);
|
|
|
|
sql << "drop table test13";
|
|
}
|
|
|
|
//
|
|
// Support for soci Common Tests
|
|
//
|
|
|
|
struct TableCreator1 : public tests::table_creator_base
|
|
{
|
|
TableCreator1(soci::session & sql)
|
|
: tests::table_creator_base(sql)
|
|
{
|
|
sql << "create table soci_test(id integer, val integer, c char, "
|
|
"str varchar(20), sh smallint, ll bigint, ul bigint, "
|
|
"d double precision, num76 numeric(7,6), "
|
|
"tm timestamp, i1 integer, i2 integer, i3 integer, name varchar(20))";
|
|
sql.commit();
|
|
sql.begin();
|
|
}
|
|
};
|
|
|
|
struct TableCreator2 : public tests::table_creator_base
|
|
{
|
|
TableCreator2(soci::session & sql)
|
|
: tests::table_creator_base(sql)
|
|
{
|
|
sql << "create table soci_test(num_float float, num_int integer, "
|
|
"name varchar(20), sometime timestamp, chr char)";
|
|
sql.commit();
|
|
sql.begin();
|
|
}
|
|
};
|
|
|
|
struct TableCreator3 : public tests::table_creator_base
|
|
{
|
|
TableCreator3(soci::session & sql)
|
|
: tests::table_creator_base(sql)
|
|
{
|
|
sql << "create table soci_test(name varchar(100) not null, "
|
|
"phone varchar(15))";
|
|
sql.commit();
|
|
sql.begin();
|
|
}
|
|
};
|
|
|
|
struct TableCreator4 : public tests::table_creator_base
|
|
{
|
|
TableCreator4(soci::session & sql)
|
|
: tests::table_creator_base(sql)
|
|
{
|
|
sql << "create table soci_test(val integer)";
|
|
sql.commit();
|
|
sql.begin();
|
|
}
|
|
};
|
|
|
|
struct TableCreatorCLOB : public tests::table_creator_base
|
|
{
|
|
TableCreatorCLOB(soci::session & sql)
|
|
: tests::table_creator_base(sql)
|
|
{
|
|
sql << "create table soci_test(id integer, s blob sub_type text)";
|
|
sql.commit();
|
|
sql.begin();
|
|
}
|
|
};
|
|
|
|
struct TableCreatorBLOB : public tests::table_creator_base
|
|
{
|
|
TableCreatorBLOB(soci::session & sql)
|
|
: tests::table_creator_base(sql)
|
|
{
|
|
sql << "create table soci_test(id integer, b blob)";
|
|
sql.commit();
|
|
sql.begin();
|
|
}
|
|
};
|
|
|
|
struct TableCreatorXML : public tests::table_creator_base
|
|
{
|
|
TableCreatorXML(soci::session & sql)
|
|
: tests::table_creator_base(sql)
|
|
{
|
|
sql << "create table soci_test(id integer, x blob sub_type text)";
|
|
sql.commit();
|
|
sql.begin();
|
|
}
|
|
};
|
|
|
|
class test_context : public tests::test_context_common
|
|
{
|
|
public:
|
|
test_context() = default;
|
|
|
|
std::string get_example_connection_string() const override
|
|
{
|
|
return "service=/usr/local/firebird/db/test.fdb user=SYSDBA password=masterkey";
|
|
}
|
|
|
|
tests::table_creator_base* table_creator_1(soci::session& s) const override
|
|
{
|
|
return new TableCreator1(s);
|
|
}
|
|
|
|
tests::table_creator_base* table_creator_2(soci::session& s) const override
|
|
{
|
|
return new TableCreator2(s);
|
|
}
|
|
|
|
tests::table_creator_base* table_creator_3(soci::session& s) const override
|
|
{
|
|
return new TableCreator3(s);
|
|
}
|
|
|
|
tests::table_creator_base* table_creator_4(soci::session& s) const override
|
|
{
|
|
return new TableCreator4(s);
|
|
}
|
|
|
|
tests::table_creator_base* table_creator_clob(soci::session& s) const override
|
|
{
|
|
return new TableCreatorCLOB(s);
|
|
}
|
|
|
|
|
|
tests::table_creator_base* table_creator_blob(soci::session& s) const override
|
|
{
|
|
return new TableCreatorBLOB(s);
|
|
}
|
|
|
|
tests::table_creator_base* table_creator_xml(soci::session& s) const override
|
|
{
|
|
return new TableCreatorXML(s);
|
|
}
|
|
|
|
std::string to_date_time(std::string const &datdt_string) const override
|
|
{
|
|
return "'" + datdt_string + "'";
|
|
}
|
|
|
|
bool has_uint64_storage_bug() const override
|
|
{
|
|
// Firebird does not support unsigned integer types.
|
|
// We're using Firebird 3, which does not yet support a data
|
|
// type that can correctly store a UINT64_MAX. The biggest
|
|
// numeric data type available is numeric(18,0).
|
|
// Firebird 4 introduces the data type int128 and numeric(36,0),
|
|
// which will be sufficient for that in the future.
|
|
return true;
|
|
}
|
|
|
|
void on_after_ddl(soci::session& sql) const override
|
|
{
|
|
sql.commit();
|
|
}
|
|
|
|
std::string sql_length(std::string const& s) const override
|
|
{
|
|
return "char_length(" + s + ")";
|
|
}
|
|
};
|
|
|
|
test_context tc_firebird;
|