Files
soci/tests/oracle/test-oracle.cpp
2024-01-13 17:09:31 +01:00

1524 lines
41 KiB
C++

//
// // Copyright (C) 2004-2007 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)
//
#include "common-tests.h"
#include "soci/soci.h"
#include "soci/oracle/soci-oracle.h"
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <ctime>
using namespace soci;
using namespace soci::tests;
std::string connectString;
backend_factory const &backEnd = *soci::factory_oracle();
// Extra tests for date/time
TEST_CASE("Oracle datetime", "[oracle][datetime]")
{
soci::session sql(backEnd, connectString);
{
std::time_t now = std::time(NULL);
std::tm t1, t2;
t2 = *std::localtime(&now);
sql << "select t from (select :t as t from dual)",
into(t1), use(t2);
CHECK(t1.tm_sec == t2.tm_sec);
CHECK(t1.tm_min == t2.tm_min);
CHECK(t1.tm_hour == t2.tm_hour);
CHECK(t1.tm_mday == t2.tm_mday);
CHECK(t1.tm_mon == t2.tm_mon);
CHECK(t1.tm_year == t2.tm_year);
CHECK(t1.tm_wday == t2.tm_wday);
CHECK(t1.tm_yday == t2.tm_yday);
CHECK(t1.tm_isdst == t2.tm_isdst);
// make sure the date is stored properly in Oracle
char buf[25];
strftime(buf, sizeof(buf), "%m-%d-%Y %H:%M:%S", &t2);
std::string t_out;
std::string format("MM-DD-YYYY HH24:MI:SS");
sql << "select to_char(t, :format) from (select :t as t from dual)",
into(t_out), use(format), use(t2);
CHECK(t_out == std::string(buf));
}
{
// date and time - before year 2000
std::time_t then = std::time(NULL) - 17*365*24*60*60;
std::tm t1, t2;
t2 = *std::localtime(&then);
sql << "select t from (select :t as t from dual)",
into(t1), use(t2);
CHECK(t1.tm_sec == t2.tm_sec);
CHECK(t1.tm_min == t2.tm_min);
CHECK(t1.tm_hour == t2.tm_hour);
CHECK(t1.tm_mday == t2.tm_mday);
CHECK(t1.tm_mon == t2.tm_mon);
CHECK(t1.tm_year == t2.tm_year);
CHECK(t1.tm_wday == t2.tm_wday);
CHECK(t1.tm_yday == t2.tm_yday);
CHECK(t1.tm_isdst == t2.tm_isdst);
// make sure the date is stored properly in Oracle
char buf[25];
strftime(buf, sizeof(buf), "%m-%d-%Y %H:%M:%S", &t2);
std::string t_out;
std::string format("MM-DD-YYYY HH24:MI:SS");
sql << "select to_char(t, :format) from (select :t as t from dual)",
into(t_out), use(format), use(t2);
CHECK(t_out == std::string(buf));
}
}
// explicit calls test
TEST_CASE("Oracle explicit calls", "[oracle]")
{
soci::session sql(backEnd, connectString);
statement st(sql);
st.alloc();
int i = 0;
st.exchange(into(i));
st.prepare("select 7 from dual");
st.define_and_bind();
st.execute(1);
CHECK(i == 7);
}
// DDL test
// nested statement test
// (the same syntax is used for output cursors in PL/SQL)
struct basic_table_creator : public table_creator_base
{
basic_table_creator(soci::session & sql)
: table_creator_base(sql)
{
sql <<
"create table soci_test ("
" id number(5) not null,"
" name varchar2(100),"
" code number(5)"
")";
}
};
TEST_CASE("Oracle nested statement", "[oracle][blob]")
{
soci::session sql(backEnd, connectString);
basic_table_creator tableCreator(sql);
int id;
std::string name;
{
statement st1 = (sql.prepare <<
"insert into soci_test (id, name) values (:id, :name)",
use(id), use(name));
id = 1; name = "John"; st1.execute(1);
id = 2; name = "Anna"; st1.execute(1);
id = 3; name = "Mike"; st1.execute(1);
}
statement stInner(sql);
statement stOuter = (sql.prepare <<
"select cursor(select name from soci_test order by id)"
" from soci_test where id = 1",
into(stInner));
stInner.exchange(into(name));
stOuter.execute();
stOuter.fetch();
std::vector<std::string> names;
while (stInner.fetch()) { names.push_back(name); }
REQUIRE(names.size() == 3);
CHECK(names[0] == "John");
CHECK(names[1] == "Anna");
CHECK(names[2] == "Mike");
}
// ROWID test
TEST_CASE("Oracle rowid", "[oracle][rowid]")
{
soci::session sql(backEnd, connectString);
basic_table_creator tableCreator(sql);
sql << "insert into soci_test(id, name) values(7, \'John\')";
rowid rid(sql);
sql << "select rowid from soci_test where id = 7", into(rid);
int id;
std::string name;
sql << "select id, name from soci_test where rowid = :rid",
into(id), into(name), use(rid);
CHECK(id == 7);
CHECK(name == "John");
}
// Stored procedures
struct procedure_creator : procedure_creator_base
{
procedure_creator(soci::session & sql)
: procedure_creator_base(sql)
{
sql <<
"create or replace procedure soci_test(output out varchar2,"
"input in varchar2) as "
"begin output := input; end;";
}
};
TEST_CASE("Oracle stored procedure", "[oracle][stored-procedure]")
{
soci::session sql(backEnd, connectString);
procedure_creator procedure_creator(sql);
std::string in("my message");
std::string out;
statement st = (sql.prepare <<
"begin soci_test(:output, :input); end;",
use(out, "output"),
use(in, "input"));
st.execute(1);
CHECK(out == in);
// explicit procedure syntax
{
std::string in("my message2");
std::string out;
procedure proc = (sql.prepare <<
"soci_test(:output, :input)",
use(out, "output"), use(in, "input"));
proc.execute(1);
CHECK(out == in);
}
}
// bind into user-defined objects
struct string_holder
{
string_holder() {}
string_holder(const char* s) : s_(s) {}
string_holder(std::string s) : s_(s) {}
std::string get() const { return s_; }
private:
std::string s_;
};
namespace soci
{
template <>
struct type_conversion<string_holder>
{
typedef std::string base_type;
static void from_base(const std::string &s, indicator /* ind */,
string_holder &sh)
{
sh = string_holder(s);
}
static void to_base(const string_holder &sh, std::string &s, indicator &ind)
{
s = sh.get();
ind = i_ok;
}
};
}
struct in_out_procedure_creator : public procedure_creator_base
{
in_out_procedure_creator(soci::session & sql)
: procedure_creator_base(sql)
{
sql << "create or replace procedure soci_test(s in out varchar2)"
" as begin s := s || s; end;";
}
};
struct returns_null_procedure_creator : public procedure_creator_base
{
returns_null_procedure_creator(soci::session & sql)
: procedure_creator_base(sql)
{
sql << "create or replace procedure soci_test(s in out varchar2)"
" as begin s := NULL; end;";
}
};
TEST_CASE("Oracle user-defined objects", "[oracle][type_conversion]")
{
soci::session sql(backEnd, connectString);
{
basic_table_creator tableCreator(sql);
int id(1);
string_holder in("my string");
sql << "insert into soci_test(id, name) values(:id, :name)", use(id), use(in);
string_holder out;
sql << "select name from soci_test", into(out);
CHECK(out.get() == "my string");
row r;
sql << "select * from soci_test", into(r);
string_holder dynamicOut = r.get<string_holder>(1);
CHECK(dynamicOut.get() == "my string");
}
}
TEST_CASE("Oracle user-defined objects in/out", "[oracle][type_conversion]")
{
soci::session sql(backEnd, connectString);
// test procedure with user-defined type as in-out parameter
{
in_out_procedure_creator procedureCreator(sql);
std::string sh("test");
procedure proc = (sql.prepare << "soci_test(:s)", use(sh));
proc.execute(1);
CHECK(sh == "testtest");
}
// test procedure with user-defined type as in-out parameter
{
in_out_procedure_creator procedureCreator(sql);
string_holder sh("test");
procedure proc = (sql.prepare << "soci_test(:s)", use(sh));
proc.execute(1);
CHECK(sh.get() == "testtest");
}
}
TEST_CASE("Oracle null user-defined objects in/out", "[oracle][null][type_conversion]")
{
soci::session sql(backEnd, connectString);
// test procedure which returns null
returns_null_procedure_creator procedureCreator(sql);
string_holder sh;
indicator ind = i_ok;
procedure proc = (sql.prepare << "soci_test(:s)", use(sh, ind));
proc.execute(1);
CHECK(ind == i_null);
}
// test bulk insert features
TEST_CASE("Oracle bulk insert", "[oracle][insert][bulk]")
{
soci::session sql(backEnd, connectString);
basic_table_creator tableCreator(sql);
// verify exception is thrown if vectors of unequal size are passed in
{
std::vector<int> ids;
ids.push_back(1);
ids.push_back(2);
std::vector<int> codes;
codes.push_back(1);
try
{
sql << "insert into soci_test(id,code) values(:id,:code)",
use(ids), use(codes);
FAIL("expected exception not thrown");
}
catch (soci_error const &e)
{
std::string const error = e.what();
CAPTURE(error);
CHECK(error.find("Bind variable size mismatch")
!= std::string::npos);
}
try
{
sql << "select from soci_test", into(ids), into(codes);
FAIL("expected exception not thrown");
}
catch (std::exception const &e)
{
std::string const error = e.what();
CAPTURE(error);
CHECK(error.find("Bind variable size mismatch")
!= std::string::npos);
}
}
// verify partial insert occurs when one of the records is bad
{
std::vector<int> ids;
ids.push_back(100);
ids.push_back(1000000); // too big for column
try
{
sql << "insert into soci_test (id) values(:id)", use(ids, "id");
FAIL("expected exception not thrown");
}
catch (soci_error const &e)
{
std::string const error = e.what();
//TODO e could be made to tell which row(s) failed
CAPTURE(error);
CHECK(error.find("ORA-01438") != std::string::npos);
}
sql.commit();
int count(7);
sql << "select count(*) from soci_test", into(count);
CHECK(count == 1);
sql << "delete from soci_test";
}
// test insert
{
std::vector<int> ids;
for (int i = 0; i != 3; ++i)
{
ids.push_back(i+10);
}
statement st = (sql.prepare << "insert into soci_test(id) values(:id)",
use(ids));
st.execute(1);
int count;
sql << "select count(*) from soci_test", into(count);
CHECK(count == 3);
}
//verify an exception is thrown if into vector is zero length
{
std::vector<int> ids;
CHECK_THROWS_AS((sql << "select id from soci_test", into(ids)), soci_error);
}
// verify an exception is thrown if use vector is zero length
{
std::vector<int> ids;
CHECK_THROWS_AS((sql << "insert into soci_test(id) values(:id)", use(ids)), soci_error);
}
// test "no data" condition
{
std::vector<indicator> inds(3);
std::vector<int> ids_out(3);
statement st = (sql.prepare << "select id from soci_test where 1=0",
into(ids_out, inds));
// false return value means "no data"
CHECK(st.execute(1) == false);
// that's it - nothing else is guaranteed
// and nothing else is to be tested here
}
// test NULL indicators
{
std::vector<int> ids(3);
sql << "select id from soci_test", into(ids);
std::vector<indicator> inds_in;
inds_in.push_back(i_ok);
inds_in.push_back(i_null);
inds_in.push_back(i_ok);
std::vector<int> new_codes;
new_codes.push_back(10);
new_codes.push_back(11);
new_codes.push_back(10);
sql << "update soci_test set code = :code where id = :id",
use(new_codes, inds_in), use(ids);
std::vector<indicator> inds_out(3);
std::vector<int> codes(3);
sql << "select code from soci_test", into(codes, inds_out);
REQUIRE(codes.size() == 3);
REQUIRE(inds_out.size() == 3);
CHECK(codes[0] == 10);
CHECK(codes[2] == 10);
CHECK(inds_out[0] == i_ok);
CHECK(inds_out[1] == i_null);
CHECK(inds_out[2] == i_ok);
}
// verify an exception is thrown if null is selected
// and no indicator was provided
{
std::string msg;
std::vector<int> intos(3);
try
{
sql << "select code from soci_test", into(intos);
FAIL("expected exception not thrown");
}
catch (soci_error const &e)
{
CHECK(e.get_error_message() ==
"Null value fetched and no indicator defined." );
}
}
// test basic select
{
const size_t sz = 3;
std::vector<indicator> inds(sz);
std::vector<int> ids_out(sz);
statement st = (sql.prepare << "select id from soci_test",
into(ids_out, inds));
const bool gotData = st.execute(true);
CHECK(gotData);
REQUIRE(ids_out.size() == sz);
CHECK(ids_out[0] == 10);
CHECK(ids_out[2] == 12);
REQUIRE(inds.size() == 3);
CHECK(inds[0] == i_ok);
CHECK(inds[1] == i_ok);
CHECK(inds[2] == i_ok);
}
// verify execute(0)
{
std::vector<int> ids_out(2);
statement st = (sql.prepare << "select id from soci_test",
into(ids_out));
st.execute();
REQUIRE(ids_out.size() == 2);
bool gotData = st.fetch();
CHECK(gotData);
REQUIRE(ids_out.size() == 2);
CHECK(ids_out[0] == 10);
CHECK(ids_out[1] == 11);
gotData = st.fetch();
CHECK(gotData);
REQUIRE(ids_out.size() == 1);
CHECK(ids_out[0] == 12);
gotData = st.fetch();
CHECK(gotData == false);
}
// verify resizing happens if vector is larger
// than number of rows returned
{
std::vector<int> ids_out(4); // one too many
statement st2 = (sql.prepare << "select id from soci_test",
into(ids_out));
bool gotData = st2.execute(true);
CHECK(gotData);
REQUIRE(ids_out.size() == 3);
CHECK(ids_out[0] == 10);
CHECK(ids_out[2] == 12);
}
// verify resizing happens properly during fetch()
{
std::vector<int> more;
more.push_back(13);
more.push_back(14);
sql << "insert into soci_test(id) values(:id)", use(more);
std::vector<int> ids(2);
statement st3 = (sql.prepare << "select id from soci_test", into(ids));
bool gotData = st3.execute(true);
CHECK(gotData);
CHECK(ids[0] == 10);
CHECK(ids[1] == 11);
gotData = st3.fetch();
CHECK(gotData);
CHECK(ids[0] == 12);
CHECK(ids[1] == 13);
gotData = st3.fetch();
CHECK(gotData);
REQUIRE(ids.size() == 1);
CHECK(ids[0] == 14);
gotData = st3.fetch();
CHECK(gotData == false);
}
}
// more tests for bulk fetch
TEST_CASE("Oracle bulk fetch", "[oracle][fetch][bulk]")
{
soci::session sql(backEnd, connectString);
basic_table_creator tableCreator(sql);
std::vector<int> in;
for (int i = 1; i <= 10; ++i)
{
in.push_back(i);
}
sql << "insert into soci_test (id) values(:id)", use(in);
int count(0);
sql << "select count(*) from soci_test", into(count);
CHECK(count == 10);
// verify that the exception is thrown when trying to resize
// the output vector to the size that is bigger than that
// at the time of binding
{
std::vector<int> out(4);
statement st = (sql.prepare <<
"select id from soci_test", into(out));
st.execute();
st.fetch();
REQUIRE(out.size() == 4);
CHECK(out[0] == 1);
CHECK(out[1] == 2);
CHECK(out[2] == 3);
CHECK(out[3] == 4);
out.resize(5); // this should be detected as error
try
{
st.fetch();
FAIL("expected exception not thrown");
}
catch (soci_error const &e)
{
CHECK(e.get_error_message() ==
"Increasing the size of the output vector is not supported.");
}
}
// on the other hand, downsizing is OK
{
std::vector<int> out(4);
statement st = (sql.prepare <<
"select id from soci_test", into(out));
st.execute();
st.fetch();
REQUIRE(out.size() == 4);
CHECK(out[0] == 1);
CHECK(out[1] == 2);
CHECK(out[2] == 3);
CHECK(out[3] == 4);
out.resize(3); // ok
st.fetch();
REQUIRE(out.size() == 3);
CHECK(out[0] == 5);
CHECK(out[1] == 6);
CHECK(out[2] == 7);
out.resize(4); // ok, not bigger than initially
st.fetch();
REQUIRE(out.size() == 3); // downsized because of end of data
CHECK(out[0] == 8);
CHECK(out[1] == 9);
CHECK(out[2] == 10);
bool gotData = st.fetch();
CHECK(gotData == false); // end of data
}
}
struct person
{
int id;
std::string firstName;
string_holder lastName; //test mapping of type_conversion-based types
std::string gender;
};
// Object-Relational Mapping
// Note: Use the values class as shown below in type_conversions
// to achieve object relational mapping. The values class should
// not be used directly in any other fashion.
namespace soci
{
// name-based conversion
template<> struct type_conversion<person>
{
typedef values base_type;
static void from_base(values const &v, indicator /* ind */, person &p)
{
// ignoring possibility that the whole object might be NULL
p.id = v.get<int>("ID");
p.firstName = v.get<std::string>("FIRST_NAME");
p.lastName = v.get<string_holder>("LAST_NAME");
p.gender = v.get<std::string>("GENDER", "unknown");
}
static void to_base(person const & p, values & v, indicator & ind)
{
v.set("ID", p.id);
v.set("FIRST_NAME", p.firstName);
v.set("LAST_NAME", p.lastName);
v.set("GENDER", p.gender, p.gender.empty() ? i_null : i_ok);
ind = i_ok;
}
};
}
struct person_table_creator : public table_creator_base
{
person_table_creator(soci::session & sql)
: table_creator_base(sql)
{
sql << "create table soci_test(id numeric(5,0) NOT NULL,"
<< " last_name varchar2(20), first_name varchar2(20), "
" gender varchar2(10))";
}
};
struct times100_procedure_creator : public procedure_creator_base
{
times100_procedure_creator(soci::session & sql)
: procedure_creator_base(sql)
{
sql << "create or replace procedure soci_test(id in out number)"
" as begin id := id * 100; end;";
}
};
TEST_CASE("Oracle ORM", "[oracle][orm]")
{
soci::session sql(backEnd, connectString);
{
person_table_creator tableCreator(sql);
person p;
p.id = 1;
p.lastName = "Smith";
p.firstName = "Pat";
sql << "insert into soci_test(id, first_name, last_name, gender) "
<< "values(:ID, :FIRST_NAME, :LAST_NAME, :GENDER)", use(p);
// p should be unchanged
CHECK(p.id == 1);
CHECK(p.firstName == "Pat");
CHECK(p.lastName.get() == "Smith");
person p1;
sql << "select * from soci_test", into(p1);
CHECK(p1.id == 1);
CHECK(p1.firstName == "Pat");
CHECK(p1.lastName.get() == "Smith");
CHECK(p1.gender == "unknown");
p.firstName = "Patricia";
sql << "update soci_test set first_name = :FIRST_NAME "
"where id = :ID", use(p);
// p should be unchanged
CHECK(p.id == 1);
CHECK(p.firstName == "Patricia");
CHECK(p.lastName.get() == "Smith");
// Note: gender is now "unknown" because of the mapping, not ""
CHECK(p.gender == "unknown");
person p2;
sql << "select * from soci_test", into(p2);
CHECK(p2.id == 1);
CHECK(p2.firstName == "Patricia");
CHECK(p2.lastName.get() == "Smith");
// insert a second row so we can test fetching
person p3;
p3.id = 2;
p3.firstName = "Joe";
p3.lastName = "Smith";
sql << "insert into soci_test(id, first_name, last_name, gender) "
<< "values(:ID, :FIRST_NAME, :LAST_NAME, :GENDER)", use(p3);
person p4;
statement st = (sql.prepare << "select * from soci_test order by id",
into(p4));
st.execute();
bool gotData = st.fetch();
CHECK(gotData);
CHECK(p4.id == 1);
CHECK(p4.firstName == "Patricia");
gotData = st.fetch();
CHECK(gotData);
CHECK(p4.id == 2);
CHECK(p4.firstName == "Joe");
gotData = st.fetch();
CHECK(gotData == false);
}
// test with stored procedure
{
times100_procedure_creator procedureCreator(sql);
person p;
p.id = 1;
p.firstName = "Pat";
p.lastName = "Smith";
procedure proc = (sql.prepare << "soci_test(:ID)", use(p));
proc.execute(1);
CHECK(p.id == 100);
CHECK(p.firstName == "Pat");
CHECK(p.lastName.get() == "Smith");
}
// test with stored procedure which returns null
{
returns_null_procedure_creator procedureCreator(sql);
person p;
try
{
procedure proc = (sql.prepare << "soci_test(:FIRST_NAME)",
use(p));
proc.execute(1);
FAIL("expected exception not thrown");
}
catch (soci_error& e)
{
CHECK(e.get_error_message() ==
"Null value not allowed for this type");
}
procedure proc = (sql.prepare << "soci_test(:GENDER)",
use(p));
proc.execute(1);
CHECK(p.gender == "unknown");
}
}
// Experimental support for position based O/R Mapping
// additional type for position-based test
struct person2
{
int id;
std::string firstName;
std::string lastName;
std::string gender;
};
// additional type for stream-like test
struct person3 : person2 {};
namespace soci
{
// position-based conversion
template<> struct type_conversion<person2>
{
typedef values base_type;
static void from_base(values const &v, indicator /* ind */, person2 &p)
{
p.id = v.get<int>(0);
p.firstName = v.get<std::string>(1);
p.lastName = v.get<std::string>(2);
p.gender = v.get<std::string>(3, "whoknows");
}
// What about the "to" part? Does it make any sense to have it?
};
// stream-like conversion
template<> struct type_conversion<person3>
{
typedef values base_type;
static void from_base(values const &v, indicator /* ind */, person3 &p)
{
v >> p.id >> p.firstName >> p.lastName >> p.gender;
}
// TODO: The "to" part is certainly needed.
};
}
TEST_CASE("Oracle ORM by index", "[oracle][orm]")
{
soci::session sql(backEnd, connectString);
person_table_creator tableCreator(sql);
person p;
p.id = 1;
p.lastName = "Smith";
p.firstName = "Patricia";
sql << "insert into soci_test(id, first_name, last_name, gender) "
<< "values(:ID, :FIRST_NAME, :LAST_NAME, :GENDER)", use(p);
// test position-based conversion
person2 p3;
sql << "select id, first_name, last_name, gender from soci_test", into(p3);
CHECK(p3.id == 1);
CHECK(p3.firstName == "Patricia");
CHECK(p3.lastName == "Smith");
CHECK(p3.gender == "whoknows");
sql << "update soci_test set gender = 'F' where id = 1";
// additional test for stream-like conversion
person3 p4;
sql << "select id, first_name, last_name, gender from soci_test", into(p4);
CHECK(p4.id == 1);
CHECK(p4.firstName == "Patricia");
CHECK(p4.lastName == "Smith");
CHECK(p4.gender == "F");
}
//
// Backwards compatibility - support use of large strings with
// columns of type LONG
///
struct long_table_creator : public table_creator_base
{
long_table_creator(soci::session & sql)
: table_creator_base(sql)
{
sql << "create table soci_test(l long)";
}
};
TEST_CASE("Oracle large strings as long", "[oracle][compatibility]")
{
soci::session sql(backEnd, connectString);
long_table_creator creator(sql);
const std::string::size_type max = 32768;
std::string in(max, 'X');
sql << "insert into soci_test values(:l)", use(in);
std::string out;
sql << "select l from soci_test", into(out);
CHECK(out.size() == max);
CHECK(in == out);
}
// test for modifiable and const use elements
TEST_CASE("Oracle const and modifiable parameters", "[oracle][use]")
{
soci::session sql(backEnd, connectString);
int i = 7;
sql << "begin "
"select 2 * :i into :i from dual; "
"end;", use(i);
CHECK(i == 14);
const int j = 7;
try
{
sql << "begin "
"select 2 * :i into :i from dual;"
" end;", use(j);
FAIL("expected exception not thrown");
}
catch (soci_error const & e)
{
CHECK(e.get_error_message() ==
"Attempted modification of const use element");
}
}
struct longlong_table_creator : table_creator_base
{
longlong_table_creator(soci::session & sql)
: table_creator_base(sql)
{
sql << "create table soci_test(val number(20))";
}
};
// test using the result of to_number() which uses the default NUMBER type,
// with precision == scale == 0, with rowset
TEST_CASE("Oracle to_number with rowset", "[oracle][rowset][to_number]")
{
soci::session sql(backEnd, connectString);
soci::rowset<soci::row>
rs = (sql.prepare << "select to_number('123456789012345') from dual");
double d = rs.begin()->get<double>(0);
ASSERT_EQUAL_EXACT(d, 123456789012345);
const double pi = 3.14;
rs = (sql.prepare << "select to_number(:t) from dual", use(pi));
d = rs.begin()->get<double>(0);
ASSERT_EQUAL_EXACT(d, 3.14);
}
// long long test
TEST_CASE("Oracle long long", "[oracle][longlong]")
{
{
soci::session sql(backEnd, connectString);
longlong_table_creator tableCreator(sql);
long long v1 = 1000000000000LL;
sql << "insert into soci_test(val) values(:val)", use(v1);
long long v2 = 0LL;
sql << "select val from soci_test", into(v2);
CHECK(v2 == v1);
}
// vector<long long>
{
soci::session sql(backEnd, connectString);
longlong_table_creator tableCreator(sql);
std::vector<long long> v1;
v1.push_back(1000000000000LL);
v1.push_back(1000000000001LL);
v1.push_back(1000000000002LL);
v1.push_back(1000000000003LL);
v1.push_back(1000000000004LL);
sql << "insert into soci_test(val) values(:val)", use(v1);
std::vector<long long> v2(10);
sql << "select val from soci_test order by val desc", into(v2);
REQUIRE(v2.size() == 5);
CHECK(v2[0] == 1000000000004LL);
CHECK(v2[1] == 1000000000003LL);
CHECK(v2[2] == 1000000000002LL);
CHECK(v2[3] == 1000000000001LL);
CHECK(v2[4] == 1000000000000LL);
}
}
// Test the DDL and metadata functionality
TEST_CASE("Oracle DDL with metadata", "[oracle][ddl]")
{
soci::session sql(backEnd, connectString);
// note: prepare_column_descriptions expects l-value
std::string ddl_t1 = "DDL_T1";
std::string ddl_t2 = "DDL_T2";
std::string ddl_t3 = "DDL_T3";
// single-expression variant:
sql.create_table(ddl_t1).column("I", soci::dt_integer).column("J", soci::dt_integer);
// check whether this table was created:
bool ddl_t1_found = false;
bool ddl_t2_found = false;
bool ddl_t3_found = false;
std::string table_name;
soci::statement st = (sql.prepare_table_names(), into(table_name));
st.execute();
while (st.fetch())
{
if (table_name == ddl_t1) { ddl_t1_found = true; }
if (table_name == ddl_t2) { ddl_t2_found = true; }
if (table_name == ddl_t3) { ddl_t3_found = true; }
}
CHECK(ddl_t1_found);
CHECK(ddl_t2_found == false);
CHECK(ddl_t3_found == false);
// check whether ddl_t1 has the right structure:
bool i_found = false;
bool j_found = false;
bool other_found = false;
soci::column_info ci;
soci::statement st1 = (sql.prepare_column_descriptions(ddl_t1), into(ci));
st1.execute();
while (st1.fetch())
{
if (ci.name == "I")
{
CHECK(ci.type == soci::dt_integer);
CHECK(ci.nullable);
i_found = true;
}
else if (ci.name == "J")
{
CHECK(ci.type == soci::dt_integer);
CHECK(ci.nullable);
j_found = true;
}
else
{
other_found = true;
}
}
CHECK(i_found);
CHECK(j_found);
CHECK(other_found == false);
// two more tables:
// separately defined columns:
// (note: statement is executed when ddl object goes out of scope)
{
soci::ddl_type ddl = sql.create_table(ddl_t2);
ddl.column("I", soci::dt_integer);
ddl.column("J", soci::dt_integer);
ddl.column("K", soci::dt_integer)("not null");
ddl.primary_key("t2_pk", "J");
}
sql.add_column(ddl_t1, "K", soci::dt_integer);
sql.add_column(ddl_t1, "BIG", soci::dt_string, 0); // "unlimited" length -> CLOB
sql.drop_column(ddl_t1, "I");
// or with constraint as in t2:
sql.add_column(ddl_t2, "M", soci::dt_integer)("not null");
// third table with a foreign key to the second one
{
soci::ddl_type ddl = sql.create_table(ddl_t3);
ddl.column("X", soci::dt_integer);
ddl.column("Y", soci::dt_integer);
ddl.foreign_key("t3_fk", "X", ddl_t2, "J");
}
// check if all tables were created:
ddl_t1_found = false;
ddl_t2_found = false;
ddl_t3_found = false;
soci::statement st2 = (sql.prepare_table_names(), into(table_name));
st2.execute();
while (st2.fetch())
{
if (table_name == ddl_t1) { ddl_t1_found = true; }
if (table_name == ddl_t2) { ddl_t2_found = true; }
if (table_name == ddl_t3) { ddl_t3_found = true; }
}
CHECK(ddl_t1_found);
CHECK(ddl_t2_found);
CHECK(ddl_t3_found);
// check if ddl_t1 has the right structure (it was altered):
i_found = false;
j_found = false;
bool k_found = false;
bool big_found = false;
other_found = false;
soci::statement st3 = (sql.prepare_column_descriptions(ddl_t1), into(ci));
st3.execute();
while (st3.fetch())
{
if (ci.name == "J")
{
CHECK(ci.type == soci::dt_integer);
CHECK(ci.nullable);
j_found = true;
}
else if (ci.name == "K")
{
CHECK(ci.type == soci::dt_integer);
CHECK(ci.nullable);
k_found = true;
}
else if (ci.name == "BIG")
{
CHECK(ci.type == soci::dt_string);
CHECK(ci.precision == 0); // "unlimited" for strings
big_found = true;
}
else
{
other_found = true;
}
}
CHECK(i_found == false);
CHECK(j_found);
CHECK(k_found);
CHECK(big_found);
CHECK(other_found == false);
// check if ddl_t2 has the right structure:
i_found = false;
j_found = false;
k_found = false;
bool m_found = false;
other_found = false;
soci::statement st4 = (sql.prepare_column_descriptions(ddl_t2), into(ci));
st4.execute();
while (st4.fetch())
{
if (ci.name == "I")
{
CHECK(ci.type == soci::dt_integer);
CHECK(ci.nullable);
i_found = true;
}
else if (ci.name == "J")
{
CHECK(ci.type == soci::dt_integer);
CHECK(ci.nullable == false); // primary key
j_found = true;
}
else if (ci.name == "K")
{
CHECK(ci.type == soci::dt_integer);
CHECK(ci.nullable == false);
k_found = true;
}
else if (ci.name == "M")
{
CHECK(ci.type == soci::dt_integer);
CHECK(ci.nullable == false);
m_found = true;
}
else
{
other_found = true;
}
}
CHECK(i_found);
CHECK(j_found);
CHECK(k_found);
CHECK(m_found);
CHECK(other_found == false);
sql.drop_table(ddl_t1);
sql.drop_table(ddl_t3); // note: this must be dropped before ddl_t2
sql.drop_table(ddl_t2);
// check if all tables were dropped:
ddl_t1_found = false;
ddl_t2_found = false;
ddl_t3_found = false;
st2 = (sql.prepare_table_names(), into(table_name));
st2.execute();
while (st2.fetch())
{
if (table_name == ddl_t1) { ddl_t1_found = true; }
if (table_name == ddl_t2) { ddl_t2_found = true; }
if (table_name == ddl_t3) { ddl_t3_found = true; }
}
CHECK(ddl_t1_found == false);
CHECK(ddl_t2_found == false);
CHECK(ddl_t3_found == false);
int i = -1;
sql << "select length(" + sql.empty_blob() + ") from dual", into(i);
CHECK(i == 0);
sql << "select " + sql.nvl() + "(1, 2) from dual", into(i);
CHECK(i == 1);
sql << "select " + sql.nvl() + "(NULL, 2) from dual", into(i);
CHECK(i == 2);
}
// Test the bulk iterators functionality
TEST_CASE("Bulk iterators", "[oracle][bulkiters]")
{
soci::session sql(backEnd, connectString);
sql << "create table t (i integer)";
// test bulk iterators with basic types
{
std::vector<int> v;
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
v.push_back(50);
std::size_t begin = 2;
std::size_t end = 5;
sql << "insert into t (i) values (:v)", soci::use(v, begin, end);
v.clear();
v.resize(20);
begin = 5;
end = 20;
sql << "select i from t", soci::into(v, begin, end);
CHECK(end == 8);
for (std::size_t i = 0; i != 5; ++i)
{
CHECK(v[i] == 0);
}
CHECK(v[5] == 30);
CHECK(v[6] == 40);
CHECK(v[7] == 50);
for (std::size_t i = end; i != 20; ++i)
{
CHECK(v[i] == 0);
}
}
sql << "delete from t";
// test bulk iterators with user types
{
std::vector<MyInt> v;
v.push_back(MyInt(10));
v.push_back(MyInt(20));
v.push_back(MyInt(30));
v.push_back(MyInt(40));
v.push_back(MyInt(50));
std::size_t begin = 2;
std::size_t end = 5;
sql << "insert into t (i) values (:v)", soci::use(v, begin, end);
v.clear();
for (std::size_t i = 0; i != 20; ++i)
{
v.push_back(MyInt(-1));
}
begin = 5;
end = 20;
sql << "select i from t", soci::into(v, begin, end);
CHECK(end == 8);
for (std::size_t i = 0; i != 5; ++i)
{
CHECK(v[i].get() == -1);
}
CHECK(v[5].get() == 30);
CHECK(v[6].get() == 40);
CHECK(v[7].get() == 50);
for (std::size_t i = end; i != 20; ++i)
{
CHECK(v[i].get() == -1);
}
}
sql << "drop table t";
}
//
// Support for soci Common Tests
//
struct table_creator_one : public table_creator_base
{
table_creator_one(soci::session & sql)
: table_creator_base(sql)
{
sql << "create table soci_test(id number(10,0), val number(8,0), c char, "
"str varchar2(20), sh number, ll number, ul number, d number, "
"num76 numeric(7,6), "
"tm date, i1 number, i2 number, i3 number, name varchar2(20))";
}
};
struct table_creator_two : public table_creator_base
{
table_creator_two(soci::session & sql)
: table_creator_base(sql)
{
sql << "create table soci_test(num_float number, num_int numeric(4,0),"
" name varchar2(20), sometime date, chr char)";
}
};
struct table_creator_three : public table_creator_base
{
table_creator_three(soci::session & sql)
: table_creator_base(sql)
{
sql << "create table soci_test(name varchar2(100) not null, "
"phone varchar2(15))";
}
};
struct table_creator_four : public table_creator_base
{
table_creator_four(soci::session & sql)
: table_creator_base(sql)
{
sql << "create table soci_test(val number)";
}
};
struct table_creator_for_xml : table_creator_base
{
table_creator_for_xml(soci::session& sql)
: table_creator_base(sql)
{
sql << "create table soci_test(id integer, x xmltype)";
}
};
struct table_creator_for_clob : table_creator_base
{
table_creator_for_clob(soci::session& sql)
: table_creator_base(sql)
{
sql << "create table soci_test(id integer, s clob)";
}
};
struct table_creator_for_blob : public tests::table_creator_base
{
table_creator_for_blob(soci::session &sql) : tests::table_creator_base(sql)
{
sql << "create table soci_test(id integer, b blob)";
}
};
class test_context :public test_context_base
{
public:
test_context(backend_factory const &backEnd,
std::string const &connectString)
: test_context_base(backEnd, connectString) {}
Backend get_backend() const override { return Backend::Oracle; }
table_creator_base* table_creator_1(soci::session& s) const override
{
return new table_creator_one(s);
}
table_creator_base* table_creator_2(soci::session& s) const override
{
return new table_creator_two(s);
}
table_creator_base* table_creator_3(soci::session& s) const override
{
return new table_creator_three(s);
}
table_creator_base* table_creator_4(soci::session& s) const override
{
return new table_creator_four(s);
}
table_creator_base* table_creator_clob(soci::session& s) const override
{
return new table_creator_for_clob(s);
}
table_creator_base* table_creator_blob(soci::session& s) const override
{
return new table_creator_for_blob(s);
}
table_creator_base* table_creator_xml(soci::session& s) const override
{
return new table_creator_for_xml(s);
}
std::string to_xml(std::string const& x) const override
{
return "xmltype(" + x + ")";
}
std::string from_xml(std::string const& x) const override
{
// Notice that using just x.getCLOBVal() doesn't work, only
// table.x.getCLOBVal() or (x).getCLOBVal(), as used here, does.
return "(" + x + ").getCLOBVal()";
}
bool has_real_xml_support() const override
{
return true;
}
bool treats_empty_strings_as_null() const override
{
return true;
}
std::string to_date_time(std::string const &datdt_string) const override
{
return "to_date('" + datdt_string + "', 'YYYY-MM-DD HH24:MI:SS')";
}
std::string sql_length(std::string const& s) const override
{
// Oracle treats empty strings as NULLs, but we want to return the
// length of 0 for them for consistency with the other backends, so use
// nvl() explicitly to achieve this.
return "nvl(length(" + s + "), 0)";
}
};
int main(int argc, char** argv)
{
#ifdef _MSC_VER
// Redirect errors, unrecoverable problems, and assert() failures to STDERR,
// instead of debug message window.
// This hack is required to run assert()-driven tests by Buildbot.
// NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside.
_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
#endif //_MSC_VER
if (argc >= 2)
{
connectString = argv[1];
// Replace the connect string with the process name to ensure that
// CATCH uses the correct name in its messages.
argv[1] = argv[0];
argc--;
argv++;
}
else
{
std::cout << "usage: " << argv[0]
<< " connectstring [test-arguments...]\n"
<< "example: " << argv[0]
<< " \'service=orcl user=scott password=tiger\'\n";
std::exit(1);
}
if (!std::getenv("ORACLE_HOME"))
{
std::cerr << "ORACLE_HOME environment variable must be defined for Oracle tests.\n";
std::exit(1);
}
test_context tc(backEnd, connectString);
return Catch::Session().run(argc, argv);
}