First Commit
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
enable_testing()
|
||||
|
||||
if(NOT PostgreSQL_FOUND)
|
||||
find_package(PostgreSQL REQUIRED)
|
||||
endif()
|
||||
|
||||
file(GLOB TEST_SOURCES test*.cxx unit/test_*.cxx runner.cxx)
|
||||
|
||||
add_executable(runner ${TEST_SOURCES})
|
||||
target_link_libraries(runner PUBLIC pqxx)
|
||||
target_include_directories(runner PRIVATE ${PostgreSQL_INCLUDE_DIRS})
|
||||
add_test(
|
||||
NAME runner
|
||||
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
|
||||
COMMAND runner
|
||||
)
|
||||
|
||||
if(INSTALL_TEST)
|
||||
install(
|
||||
PROGRAMS runner
|
||||
TYPE BIN
|
||||
RENAME libpqxx-test-runner
|
||||
)
|
||||
endif()
|
||||
@@ -0,0 +1,129 @@
|
||||
# ##############################################################################
|
||||
# AUTOMATICALLY GENERATED FILE -- DO NOT EDIT.
|
||||
#
|
||||
# This file is generated automatically by libpqxx's template2mak.py script, and
|
||||
# will be rewritten from time to time.
|
||||
#
|
||||
# If you modify this file, chances are your modifications will be lost.
|
||||
#
|
||||
# The template2mak.py script should be available in the tools directory of the
|
||||
# libpqxx source archive.
|
||||
#
|
||||
# Generated from template './test/Makefile.am.template'.
|
||||
# ##############################################################################
|
||||
# Makefile.am is generated automatically for automake whenever test programs are
|
||||
# added to libpqxx.
|
||||
|
||||
EXTRA_DIST = Makefile.am.template
|
||||
|
||||
# Use the serial test runner, so tests don't get run in parallel. Otherwise,
|
||||
# output from test failures will be hidden away in log files where we can't
|
||||
# see them when running in a build slave.
|
||||
AUTOMAKE_OPTIONS=serial-tests
|
||||
|
||||
|
||||
AM_CPPFLAGS=-I$(top_builddir)/include -I$(top_srcdir)/include
|
||||
|
||||
# Override automatically generated list of default includes. It contains only
|
||||
# unnecessary entries, and incorrectly mentions include/pqxx directly.
|
||||
DEFAULT_INCLUDES=
|
||||
|
||||
noinst_HEADERS = test_helpers.hxx
|
||||
|
||||
CLEANFILES=pqxxlo.txt
|
||||
MAINTAINERCLEANFILES=Makefile.in
|
||||
|
||||
#TESTS_ENVIRONMENT=PGDATABASE=libpqxx
|
||||
# PGDATABASE, PGHOST, PGPORT, PGUSER
|
||||
|
||||
runner_SOURCES = \
|
||||
test00.cxx \
|
||||
test01.cxx \
|
||||
test02.cxx \
|
||||
test04.cxx \
|
||||
test07.cxx \
|
||||
test10.cxx \
|
||||
test11.cxx \
|
||||
test13.cxx \
|
||||
test14.cxx \
|
||||
test16.cxx \
|
||||
test17.cxx \
|
||||
test18.cxx \
|
||||
test20.cxx \
|
||||
test21.cxx \
|
||||
test26.cxx \
|
||||
test29.cxx \
|
||||
test30.cxx \
|
||||
test32.cxx \
|
||||
test37.cxx \
|
||||
test39.cxx \
|
||||
test46.cxx \
|
||||
test56.cxx \
|
||||
test60.cxx \
|
||||
test61.cxx \
|
||||
test62.cxx \
|
||||
test69.cxx \
|
||||
test70.cxx \
|
||||
test71.cxx \
|
||||
test72.cxx \
|
||||
test74.cxx \
|
||||
test75.cxx \
|
||||
test76.cxx \
|
||||
test77.cxx \
|
||||
test78.cxx \
|
||||
test79.cxx \
|
||||
test82.cxx \
|
||||
test84.cxx \
|
||||
test87.cxx \
|
||||
test88.cxx \
|
||||
test89.cxx \
|
||||
test90.cxx \
|
||||
unit/test_array.cxx \
|
||||
unit/test_binarystring.cxx \
|
||||
unit/test_blob.cxx \
|
||||
unit/test_cancel_query.cxx \
|
||||
unit/test_column.cxx \
|
||||
unit/test_composite.cxx \
|
||||
unit/test_connection.cxx \
|
||||
unit/test_cursor.cxx \
|
||||
unit/test_encodings.cxx \
|
||||
unit/test_error_verbosity.cxx \
|
||||
unit/test_errorhandler.cxx \
|
||||
unit/test_escape.cxx \
|
||||
unit/test_exceptions.cxx \
|
||||
unit/test_field.cxx \
|
||||
unit/test_float.cxx \
|
||||
unit/test_largeobject.cxx \
|
||||
unit/test_nonblocking_connect.cxx \
|
||||
unit/test_notification.cxx \
|
||||
unit/test_pipeline.cxx \
|
||||
unit/test_prepared_statement.cxx \
|
||||
unit/test_range.cxx \
|
||||
unit/test_read_transaction.cxx \
|
||||
unit/test_result_iteration.cxx \
|
||||
unit/test_result_slicing.cxx \
|
||||
unit/test_row.cxx \
|
||||
unit/test_separated_list.cxx \
|
||||
unit/test_simultaneous_transactions.cxx \
|
||||
unit/test_sql_cursor.cxx \
|
||||
unit/test_stateless_cursor.cxx \
|
||||
unit/test_strconv.cxx \
|
||||
unit/test_stream_from.cxx \
|
||||
unit/test_stream_to.cxx \
|
||||
unit/test_string_conversion.cxx \
|
||||
unit/test_subtransaction.cxx \
|
||||
unit/test_test_helpers.cxx \
|
||||
unit/test_thread_safety_model.cxx \
|
||||
unit/test_time.cxx \
|
||||
unit/test_transaction.cxx \
|
||||
unit/test_transaction_base.cxx \
|
||||
unit/test_transaction_focus.cxx \
|
||||
unit/test_transactor.cxx \
|
||||
unit/test_type_name.cxx \
|
||||
unit/test_zview.cxx \
|
||||
runner.cxx
|
||||
|
||||
runner_LDADD = $(top_builddir)/src/libpqxx.la ${POSTGRES_LIB}
|
||||
|
||||
TESTS = runner
|
||||
check_PROGRAMS = ${TESTS}
|
||||
@@ -0,0 +1,38 @@
|
||||
# Makefile.am is generated automatically for automake whenever test programs are
|
||||
# added to libpqxx.
|
||||
|
||||
EXTRA_DIST = Makefile.am.template
|
||||
|
||||
# Use the serial test runner, so tests don't get run in parallel. Otherwise,
|
||||
# output from test failures will be hidden away in log files where we can't
|
||||
# see them when running in a build slave.
|
||||
AUTOMAKE_OPTIONS=serial-tests
|
||||
|
||||
|
||||
AM_CPPFLAGS=-I$(top_builddir)/include -I$(top_srcdir)/include
|
||||
|
||||
# Override automatically generated list of default includes. It contains only
|
||||
# unnecessary entries, and incorrectly mentions include/pqxx directly.
|
||||
DEFAULT_INCLUDES=
|
||||
|
||||
noinst_HEADERS = test_helpers.hxx
|
||||
|
||||
CLEANFILES=pqxxlo.txt
|
||||
MAINTAINERCLEANFILES=Makefile.in
|
||||
|
||||
#TESTS_ENVIRONMENT=PGDATABASE=libpqxx
|
||||
# PGDATABASE, PGHOST, PGPORT, PGUSER
|
||||
|
||||
runner_SOURCES = \
|
||||
###MAKTEMPLATE:FOREACH test/test*.cxx
|
||||
###BASENAME###.cxx \
|
||||
###MAKTEMPLATE:ENDFOREACH
|
||||
###MAKTEMPLATE:FOREACH test/unit/test_*.cxx
|
||||
unit/###BASENAME###.cxx \
|
||||
###MAKTEMPLATE:ENDFOREACH
|
||||
runner.cxx
|
||||
|
||||
runner_LDADD = $(top_builddir)/src/libpqxx.la ${POSTGRES_LIB}
|
||||
|
||||
TESTS = runner
|
||||
check_PROGRAMS = ${TESTS}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,203 @@
|
||||
/* libpqxx test runner.
|
||||
*/
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <new>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
inline std::string deref_field(pqxx::field const &f)
|
||||
{
|
||||
return f.c_str();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
namespace pqxx::test
|
||||
{
|
||||
test_failure::test_failure(
|
||||
std::string const &ffile, int fline, std::string const &desc) :
|
||||
std::logic_error(desc), m_file(ffile), m_line(fline)
|
||||
{}
|
||||
|
||||
test_failure::~test_failure() noexcept = default;
|
||||
|
||||
|
||||
/// Drop table, if it exists.
|
||||
inline void drop_table(transaction_base &t, std::string const &table)
|
||||
{
|
||||
t.exec("DROP TABLE IF EXISTS " + table);
|
||||
}
|
||||
|
||||
|
||||
[[noreturn]] void
|
||||
check_notreached(char const file[], int line, std::string desc)
|
||||
{
|
||||
throw test_failure(file, line, desc);
|
||||
}
|
||||
|
||||
|
||||
void check(
|
||||
char const file[], int line, bool condition, char const text[],
|
||||
std::string const &desc)
|
||||
{
|
||||
if (not condition)
|
||||
throw test_failure(
|
||||
file, line, desc + " (failed expression: " + text + ")");
|
||||
}
|
||||
|
||||
|
||||
void expected_exception(std::string const &message)
|
||||
{
|
||||
std::cout << "(Expected) " << message << std::endl;
|
||||
}
|
||||
|
||||
|
||||
std::string list_row(row Obj)
|
||||
{
|
||||
return separated_list(", ", std::begin(Obj), std::end(Obj), deref_field);
|
||||
}
|
||||
|
||||
|
||||
std::string list_result(result Obj)
|
||||
{
|
||||
if (std::empty(Obj))
|
||||
return "<empty>";
|
||||
return "{" +
|
||||
separated_list(
|
||||
"}\n{", std::begin(Obj), std::end(Obj),
|
||||
[](row r) { return list_row(r); }) +
|
||||
"}";
|
||||
}
|
||||
|
||||
|
||||
std::string list_result_iterator(result::const_iterator Obj)
|
||||
{
|
||||
return "<iterator at " + to_string(Obj.rownumber()) + ">";
|
||||
}
|
||||
|
||||
|
||||
void create_pqxxevents(transaction_base &t)
|
||||
{
|
||||
t.exec(
|
||||
"CREATE TEMP TABLE pqxxevents(year integer, event varchar) "
|
||||
"ON COMMIT PRESERVE ROWS");
|
||||
t.exec("INSERT INTO pqxxevents(year, event) VALUES (71, 'jtv')");
|
||||
t.exec("INSERT INTO pqxxevents(year, event) VALUES (38, 'time_t overflow')");
|
||||
t.exec(
|
||||
"INSERT INTO pqxxevents(year, event) VALUES (1, '''911'' WTC attack')");
|
||||
t.exec("INSERT INTO pqxxevents(year, event) VALUES (81, 'C:\\>')");
|
||||
t.exec(
|
||||
"INSERT INTO pqxxevents(year, event) VALUES (1978, 'bloody\t\tcold')");
|
||||
t.exec("INSERT INTO pqxxevents(year, event) VALUES (99, '')");
|
||||
t.exec("INSERT INTO pqxxevents(year, event) VALUES (2002, 'libpqxx')");
|
||||
t.exec(
|
||||
"INSERT INTO pqxxevents(year, event) "
|
||||
"VALUES (1989, 'Ode an die Freiheit')");
|
||||
t.exec(
|
||||
"INSERT INTO pqxxevents(year, event) VALUES (2001, 'New millennium')");
|
||||
t.exec("INSERT INTO pqxxevents(year, event) VALUES (1974, '')");
|
||||
t.exec("INSERT INTO pqxxevents(year, event) VALUES (97, 'Asian crisis')");
|
||||
t.exec(
|
||||
"INSERT INTO pqxxevents(year, event) VALUES (2001, 'A Space Odyssey')");
|
||||
}
|
||||
} // namespace pqxx::test
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
std::map<std::string const, pqxx::test::testfunc> *all_tests{nullptr};
|
||||
} // namespace
|
||||
|
||||
|
||||
namespace pqxx::test
|
||||
{
|
||||
void register_test(char const name[], pqxx::test::testfunc func)
|
||||
{
|
||||
if (all_tests == nullptr)
|
||||
{
|
||||
all_tests = new std::map<std::string const, pqxx::test::testfunc>();
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(all_tests->find(name) == all_tests->end());
|
||||
}
|
||||
(*all_tests)[name] = func;
|
||||
}
|
||||
} // namespace pqxx::test
|
||||
|
||||
|
||||
int main(int argc, char const *argv[])
|
||||
{
|
||||
char const *const test_name{(argc > 1) ? argv[1] : nullptr};
|
||||
|
||||
int test_count = 0;
|
||||
std::list<std::string> failed;
|
||||
for (auto const &i : *all_tests)
|
||||
if (test_name == nullptr or std::string{test_name} == std::string{i.first})
|
||||
{
|
||||
std::cout << std::endl << "Running: " << i.first << std::endl;
|
||||
|
||||
bool success = false;
|
||||
try
|
||||
{
|
||||
i.second();
|
||||
success = true;
|
||||
}
|
||||
catch (pqxx::test::test_failure const &e)
|
||||
{
|
||||
std::cerr << "Test failure in " + e.file() + " line " +
|
||||
pqxx::to_string(e.line())
|
||||
<< ": " << e.what() << std::endl;
|
||||
}
|
||||
catch (std::bad_alloc const &)
|
||||
{
|
||||
std::cerr << "Out of memory!" << std::endl;
|
||||
}
|
||||
catch (pqxx::feature_not_supported const &e)
|
||||
{
|
||||
std::cerr << "Not testing unsupported feature: " << e.what()
|
||||
<< std::endl;
|
||||
success = true;
|
||||
--test_count;
|
||||
}
|
||||
catch (pqxx::sql_error const &e)
|
||||
{
|
||||
std::cerr << "SQL error: " << e.what() << std::endl
|
||||
<< "Query was: " << e.query() << std::endl;
|
||||
}
|
||||
catch (std::exception const &e)
|
||||
{
|
||||
std::cerr << "Exception: " << e.what() << std::endl;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::cerr << "Unknown exception" << std::endl;
|
||||
}
|
||||
|
||||
if (not success)
|
||||
{
|
||||
std::cerr << "FAILED: " << i.first << std::endl;
|
||||
failed.emplace_back(i.first);
|
||||
}
|
||||
++test_count;
|
||||
}
|
||||
|
||||
std::cout << "Ran " << test_count << " test(s).\n";
|
||||
|
||||
if (not std::empty(failed))
|
||||
{
|
||||
std::cerr << "*** " << std::size(failed) << " test(s) failed: ***\n";
|
||||
for (auto const &i : failed) std::cerr << "\t" << i << '\n';
|
||||
}
|
||||
|
||||
return int(std::size(failed));
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
#include <locale>
|
||||
|
||||
#include <pqxx/cursor>
|
||||
#include <pqxx/strconv>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Initial test program for libpqxx. Test functionality that doesn't require a
|
||||
// running database.
|
||||
|
||||
namespace
|
||||
{
|
||||
template<typename T>
|
||||
inline void
|
||||
strconv(std::string const &type, T const &Obj, std::string const &expected)
|
||||
{
|
||||
std::string const Objstr{to_string(Obj)};
|
||||
|
||||
PQXX_CHECK_EQUAL(Objstr, expected, "String mismatch for " + type + ".");
|
||||
T NewObj;
|
||||
from_string(Objstr, NewObj);
|
||||
PQXX_CHECK_EQUAL(
|
||||
to_string(NewObj), expected, "String mismatch for recycled " + type + ".");
|
||||
}
|
||||
|
||||
// There's no from_string<char const *>()...
|
||||
inline void
|
||||
strconv(std::string const &type, char const Obj[], std::string const &expected)
|
||||
{
|
||||
std::string const Objstr(to_string(Obj));
|
||||
PQXX_CHECK_EQUAL(Objstr, expected, "String mismatch for " + type + ".");
|
||||
}
|
||||
|
||||
constexpr double not_a_number{std::numeric_limits<double>::quiet_NaN()};
|
||||
|
||||
struct intderef
|
||||
{
|
||||
template<typename ITER> int operator()(ITER i) const noexcept
|
||||
{
|
||||
return int(*i);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void test_000()
|
||||
{
|
||||
PQXX_CHECK_EQUAL(
|
||||
oid_none, 0u,
|
||||
"InvalidIod is not zero as it used to be. This may conceivably "
|
||||
"cause problems in libpqxx.");
|
||||
|
||||
PQXX_CHECK(
|
||||
cursor_base::prior() < 0 and cursor_base::backward_all() < 0,
|
||||
"cursor_base::difference_type appears to be unsigned.");
|
||||
|
||||
constexpr char weird[]{"foo\t\n\0bar"};
|
||||
std::string const weirdstr(weird, std::size(weird) - 1);
|
||||
|
||||
// Test string conversions
|
||||
strconv("char const[]", "", "");
|
||||
strconv("char const[]", "foo", "foo");
|
||||
strconv("int", 0, "0");
|
||||
strconv("int", 100, "100");
|
||||
strconv("int", -1, "-1");
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
long const long_min{LONG_MIN}, long_max{LONG_MAX};
|
||||
#else
|
||||
long const long_min{std::numeric_limits<long>::min()},
|
||||
long_max{std::numeric_limits<long>::max()};
|
||||
#endif
|
||||
|
||||
std::stringstream lminstr, lmaxstr, llminstr, llmaxstr, ullmaxstr;
|
||||
lminstr.imbue(std::locale("C"));
|
||||
lmaxstr.imbue(std::locale("C"));
|
||||
llminstr.imbue(std::locale("C"));
|
||||
llmaxstr.imbue(std::locale("C"));
|
||||
ullmaxstr.imbue(std::locale("C"));
|
||||
|
||||
lminstr << long_min;
|
||||
lmaxstr << long_max;
|
||||
|
||||
auto const ullong_max{std::numeric_limits<unsigned long long>::max()};
|
||||
auto const llong_max{std::numeric_limits<long long>::max()},
|
||||
llong_min{std::numeric_limits<long long>::min()};
|
||||
|
||||
llminstr << llong_min;
|
||||
llmaxstr << llong_max;
|
||||
ullmaxstr << ullong_max;
|
||||
|
||||
strconv("long", 0, "0");
|
||||
strconv("long", long_min, lminstr.str());
|
||||
strconv("long", long_max, lmaxstr.str());
|
||||
strconv("double", not_a_number, "nan");
|
||||
strconv("string", std::string{}, "");
|
||||
strconv("string", weirdstr, weirdstr);
|
||||
strconv("long long", 0LL, "0");
|
||||
strconv("long long", llong_min, llminstr.str());
|
||||
strconv("long long", llong_max, llmaxstr.str());
|
||||
strconv("unsigned long long", 0ULL, "0");
|
||||
strconv("unsigned long long", ullong_max, ullmaxstr.str());
|
||||
|
||||
std::stringstream ss;
|
||||
strconv("empty stringstream", ss, "");
|
||||
ss << -3.1415;
|
||||
strconv("stringstream", ss, ss.str());
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_000);
|
||||
} // namespace
|
||||
@@ -0,0 +1,33 @@
|
||||
#include <iostream>
|
||||
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
// Simple test program for libpqxx. Open connection to database, start
|
||||
// a transaction, and perform a query inside it.
|
||||
void test_001()
|
||||
{
|
||||
connection conn;
|
||||
|
||||
// Begin a transaction acting on our current connection. Give it a human-
|
||||
// readable name so the library can include it in error messages.
|
||||
work tx{conn, "test1"};
|
||||
|
||||
// Perform a query on the database, storing result rows in R.
|
||||
result r(tx.exec("SELECT * FROM pg_tables"));
|
||||
|
||||
// We're expecting to find some tables...
|
||||
PQXX_CHECK(not std::empty(r), "No tables found.");
|
||||
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_001);
|
||||
} // namespace
|
||||
@@ -0,0 +1,76 @@
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Example/test program for libpqxx. Perform a query and enumerate its output
|
||||
// using array indexing.
|
||||
|
||||
namespace
|
||||
{
|
||||
void bad_connect()
|
||||
{
|
||||
connection conn{"totally#invalid@connect$string!?"};
|
||||
}
|
||||
|
||||
void test_002()
|
||||
{
|
||||
// Before we really connect, test the expected behaviour of the default
|
||||
// connection type, where a failure to connect results in an immediate
|
||||
// exception rather than a silent retry.
|
||||
PQXX_CHECK_THROWS_EXCEPTION(
|
||||
bad_connect(), "Invalid connection string did not cause exception.");
|
||||
|
||||
// Set up connection to database
|
||||
std::string ConnectString;
|
||||
connection C{ConnectString};
|
||||
|
||||
// Start transaction within context of connection.
|
||||
work T{C, "test2"};
|
||||
|
||||
// Perform query within transaction.
|
||||
result R(T.exec("SELECT * FROM pg_tables"));
|
||||
|
||||
// Let's keep the database waiting as briefly as possible: commit now,
|
||||
// before we start processing results. We could do this later, or since
|
||||
// we're not making any changes in the database that need to be committed,
|
||||
// we could in this case even omit it altogether.
|
||||
T.commit();
|
||||
|
||||
// Ah, this version of postgres will tell you which table a column in a
|
||||
// result came from. Let's just test that functionality...
|
||||
oid const rtable{R.column_table(0)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
rtable, R.column_table(pqxx::row::size_type(0)),
|
||||
"Inconsistent answers from column_table()");
|
||||
|
||||
std::string const rcol{R.column_name(0)};
|
||||
oid const crtable{R.column_table(rcol)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
crtable, rtable, "Field looked up by name gives different origin.");
|
||||
|
||||
// Now we've got all that settled, let's process our results.
|
||||
for (auto const &f : R)
|
||||
{
|
||||
oid const ftable{f[0].table()};
|
||||
PQXX_CHECK_EQUAL(ftable, rtable, "field::table() is broken.");
|
||||
|
||||
oid const ttable{f.column_table(0)};
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
ttable, f.column_table(pqxx::row::size_type(0)),
|
||||
"Inconsistent pqxx::row::column_table().");
|
||||
|
||||
PQXX_CHECK_EQUAL(ttable, rtable, "Inconsistent result::column_table().");
|
||||
|
||||
oid const cttable{f.column_table(rcol)};
|
||||
|
||||
PQXX_CHECK_EQUAL(cttable, rtable, "pqxx::row::column_table() is broken.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_002);
|
||||
} // namespace
|
||||
@@ -0,0 +1,77 @@
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
|
||||
#include <pqxx/internal/header-pre.hxx>
|
||||
|
||||
#include <pqxx/internal/wait.hxx>
|
||||
|
||||
#include <pqxx/internal/header-post.hxx>
|
||||
|
||||
#include <pqxx/notification>
|
||||
#include <pqxx/transaction>
|
||||
#include <pqxx/transactor>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
// Example program for libpqxx. Send notification to self.
|
||||
|
||||
namespace
|
||||
{
|
||||
int Backend_PID{0};
|
||||
|
||||
|
||||
// Sample implementation of notification receiver.
|
||||
class TestListener final : public notification_receiver
|
||||
{
|
||||
bool m_done;
|
||||
|
||||
public:
|
||||
explicit TestListener(connection &conn) :
|
||||
notification_receiver(conn, "listen"), m_done(false)
|
||||
{}
|
||||
|
||||
virtual void operator()(std::string const &, int be_pid) override
|
||||
{
|
||||
m_done = true;
|
||||
PQXX_CHECK_EQUAL(
|
||||
be_pid, Backend_PID, "Notification came from wrong backend process.");
|
||||
}
|
||||
|
||||
bool done() const { return m_done; }
|
||||
};
|
||||
|
||||
|
||||
void test_004()
|
||||
{
|
||||
connection conn;
|
||||
|
||||
TestListener L{conn};
|
||||
// Trigger our notification receiver.
|
||||
perform([&conn, &L] {
|
||||
work tx(conn);
|
||||
tx.exec0("NOTIFY " + conn.quote_name(L.channel()));
|
||||
Backend_PID = conn.backendpid();
|
||||
tx.commit();
|
||||
});
|
||||
|
||||
int notifs{0};
|
||||
for (int i{0}; (i < 20) and not L.done(); ++i)
|
||||
{
|
||||
PQXX_CHECK_EQUAL(notifs, 0, "Got unexpected notifications.");
|
||||
// Sleep for one second. I'm not proud of this, but how does one inject
|
||||
// a change to the built-in clock in a static language?
|
||||
pqxx::internal::wait_for(1'000'000u);
|
||||
notifs = conn.get_notifs();
|
||||
}
|
||||
|
||||
PQXX_CHECK_NOT_EQUAL(L.done(), false, "No notification received.");
|
||||
PQXX_CHECK_EQUAL(notifs, 1, "Got too many notifications.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_004);
|
||||
} // namespace
|
||||
@@ -0,0 +1,133 @@
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
|
||||
#include <pqxx/transaction>
|
||||
#include <pqxx/transactor>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Example program for libpqxx. Modify the database, retaining transactional
|
||||
// integrity using the transactor framework.
|
||||
//
|
||||
// This assumes the existence of a database table "pqxxevents" containing a
|
||||
// 2-digit "year" field, which is extended to a 4-digit format by assuming all
|
||||
// year numbers of 70 or higher are in the 20th century, and all others in the
|
||||
// 21st, and that no years before 1970 are possible.
|
||||
|
||||
namespace
|
||||
{
|
||||
// Convert year to 4-digit format.
|
||||
int To4Digits(int Y)
|
||||
{
|
||||
int Result{Y};
|
||||
|
||||
PQXX_CHECK(Y >= 0, "Negative year: " + to_string(Y));
|
||||
if (Y < 70)
|
||||
Result += 2000;
|
||||
else if (Y < 100)
|
||||
Result += 1900;
|
||||
else if (Y < 1970)
|
||||
PQXX_CHECK_NOTREACHED("Unexpected year: " + to_string(Y));
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
|
||||
void test_007()
|
||||
{
|
||||
connection conn;
|
||||
conn.set_client_encoding("SQL_ASCII");
|
||||
|
||||
{
|
||||
work tx{conn};
|
||||
test::create_pqxxevents(tx);
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
// Perform (an instantiation of) the UpdateYears transactor we've defined
|
||||
// in the code above. This is where the work gets done.
|
||||
std::map<int, int> conversions;
|
||||
perform([&conversions, &conn] {
|
||||
work tx{conn};
|
||||
// First select all different years occurring in the table.
|
||||
result R(tx.exec("SELECT year FROM pqxxevents"));
|
||||
|
||||
// See if we get reasonable type identifier for this column.
|
||||
oid const rctype{R.column_type(0)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
R.column_type(pqxx::row::size_type(0)), rctype,
|
||||
"Inconsistent result::column_type().");
|
||||
|
||||
std::string const rct{to_string(rctype)};
|
||||
PQXX_CHECK(rctype > 0, "Got strange type ID for column: " + rct);
|
||||
|
||||
std::string const rcol{R.column_name(0)};
|
||||
PQXX_CHECK(not std::empty(rcol), "Didn't get a name for column.");
|
||||
|
||||
oid const rcctype{R.column_type(rcol)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
rcctype, rctype, "Column type is not what it is by name.");
|
||||
|
||||
oid const rawrcctype{R.column_type(rcol)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
rawrcctype, rctype, "Column type by C-style name is different.");
|
||||
|
||||
// Note all different years currently occurring in the table, writing
|
||||
// them and their correct mappings to conversions.
|
||||
for (auto const &r : R)
|
||||
{
|
||||
int Y{0};
|
||||
|
||||
// Read year, and if it is non-null, note its converted value
|
||||
if (r[0] >> Y)
|
||||
conversions[Y] = To4Digits(Y);
|
||||
|
||||
// See if type identifiers are consistent
|
||||
oid const tctype{r.column_type(0)};
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
tctype, r.column_type(pqxx::row::size_type(0)),
|
||||
"Inconsistent pqxx::row::column_type()");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
tctype, rctype,
|
||||
"pqxx::row::column_type() is inconsistent with "
|
||||
"result::column_type().");
|
||||
|
||||
oid const ctctype{r.column_type(rcol)};
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
ctctype, rctype, "Column type lookup by column name is broken.");
|
||||
|
||||
oid const rawctctype{r.column_type(rcol)};
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
rawctctype, rctype, "Column type lookup by C-style name is broken.");
|
||||
|
||||
oid const fctype{r[0].type()};
|
||||
PQXX_CHECK_EQUAL(fctype, rctype, "Field type lookup is broken.");
|
||||
}
|
||||
|
||||
// For each occurring year, write converted date back to whereever it may
|
||||
// occur in the table. Since we're in a transaction, any changes made by
|
||||
// others at the same time will not affect us.
|
||||
for (auto const &c : conversions)
|
||||
{
|
||||
auto const query{
|
||||
"UPDATE pqxxevents "
|
||||
"SET year=" +
|
||||
to_string(c.second) +
|
||||
" "
|
||||
"WHERE year=" +
|
||||
to_string(c.first)};
|
||||
R = tx.exec0(query);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_007);
|
||||
} // namespace
|
||||
@@ -0,0 +1,106 @@
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include <pqxx/nontransaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Test program for libpqxx. Open connection to database, start a transaction,
|
||||
// abort it, and verify that it "never happened."
|
||||
|
||||
namespace
|
||||
{
|
||||
// Let's take a boring year that is not going to be in the "pqxxevents" table
|
||||
constexpr int BoringYear{1977};
|
||||
|
||||
std::string const Table("pqxxevents");
|
||||
|
||||
|
||||
// Count events, and boring events, in table
|
||||
std::pair<int, int> CountEvents(transaction_base &T)
|
||||
{
|
||||
std::string const events_query{"SELECT count(*) FROM " + Table};
|
||||
std::string const boring_query{
|
||||
events_query + " WHERE year=" + to_string(BoringYear)};
|
||||
return std::make_pair(
|
||||
T.query_value<int>(events_query), T.query_value<int>(boring_query));
|
||||
}
|
||||
|
||||
|
||||
// Try adding a record, then aborting it, and check whether the abort was
|
||||
// performed correctly.
|
||||
void Test(connection &C, bool ExplicitAbort)
|
||||
{
|
||||
std::pair<int, int> EventCounts;
|
||||
|
||||
// First run our doomed transaction. This will refuse to run if an event
|
||||
// exists for our Boring Year.
|
||||
{
|
||||
// Begin a transaction acting on our current connection; we'll abort it
|
||||
// later though.
|
||||
work Doomed{C, "Doomed"};
|
||||
|
||||
// Verify that our Boring Year was not yet in the events table
|
||||
EventCounts = CountEvents(Doomed);
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
EventCounts.second, 0, "Can't run, boring year is already in table.");
|
||||
|
||||
// Now let's try to introduce a row for our Boring Year
|
||||
Doomed.exec0(
|
||||
"INSERT INTO " + Table +
|
||||
"(year, event) "
|
||||
"VALUES (" +
|
||||
to_string(BoringYear) + ", 'yawn')");
|
||||
|
||||
auto const Recount{CountEvents(Doomed)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
Recount.second, 1, "Wrong # events for " + to_string(BoringYear));
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
Recount.first, EventCounts.first + 1, "Number of events changed.");
|
||||
|
||||
// Okay, we've added an entry but we don't really want to. Abort it
|
||||
// explicitly if requested, or simply let the Transaction object "expire."
|
||||
if (ExplicitAbort)
|
||||
Doomed.abort();
|
||||
|
||||
// If now explicit abort requested, Doomed Transaction still ends here
|
||||
}
|
||||
|
||||
// Now check that we're back in the original state. Note that this may go
|
||||
// wrong if somebody managed to change the table between our two
|
||||
// transactions.
|
||||
work Checkup(C, "Checkup");
|
||||
|
||||
auto const NewEvents{CountEvents(Checkup)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
NewEvents.first, EventCounts.first,
|
||||
"Number of events changed. This may be due to a bug in libpqxx, "
|
||||
"or the test table was modified by some other process.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
NewEvents.second, 0,
|
||||
"Found unexpected events. This may be due to a bug in libpqxx, "
|
||||
"or the test table was modified by some other process.");
|
||||
}
|
||||
|
||||
|
||||
void test_abort()
|
||||
{
|
||||
connection conn;
|
||||
nontransaction t{conn};
|
||||
test::create_pqxxevents(t);
|
||||
connection &c(t.conn());
|
||||
t.commit();
|
||||
Test(c, true);
|
||||
Test(c, false);
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_abort);
|
||||
} // namespace
|
||||
@@ -0,0 +1,76 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Test program for libpqxx. Query a table and report its metadata.
|
||||
namespace
|
||||
{
|
||||
void test_011()
|
||||
{
|
||||
connection conn;
|
||||
work tx{conn};
|
||||
std::string const Table{"pg_tables"};
|
||||
|
||||
result R(tx.exec("SELECT * FROM " + Table));
|
||||
|
||||
// Print column names
|
||||
for (pqxx::row::size_type c{0}; c < R.columns(); ++c)
|
||||
{
|
||||
std::string N{R.column_name(c)};
|
||||
PQXX_CHECK_EQUAL(R.column_number(N), c, "Inconsistent column numbers.");
|
||||
}
|
||||
|
||||
// If there are rows in R, compare their metadata to R's.
|
||||
if (not std::empty(R))
|
||||
{
|
||||
PQXX_CHECK_EQUAL(R[0].rownumber(), 0, "Row 0 has wrong number.");
|
||||
|
||||
if (std::size(R) >= 2)
|
||||
PQXX_CHECK_EQUAL(R[1].rownumber(), 1, "Row 1 has wrong number.");
|
||||
|
||||
// Test result::iterator::swap()
|
||||
pqxx::result::const_iterator const T1(R[0]), T2(R[1]);
|
||||
PQXX_CHECK_NOT_EQUAL(T1, T2, "Values are identical--can't test swap().");
|
||||
pqxx::result::const_iterator T1s(T1), T2s(T2);
|
||||
PQXX_CHECK_EQUAL(T1s, T1, "Result iterator copy-construction is wrong.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
T2s, T2, "Result iterator copy-construction is inconsistently wrong.");
|
||||
T1s.swap(T2s);
|
||||
PQXX_CHECK_NOT_EQUAL(T1s, T1, "Result iterator swap doesn't work.");
|
||||
PQXX_CHECK_NOT_EQUAL(
|
||||
T2s, T2, "Result iterator swap inconsistently wrong.");
|
||||
PQXX_CHECK_EQUAL(T2s, T1, "Result iterator swap is asymmetric.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
T1s, T2, "Result iterator swap is inconsistently asymmetric.");
|
||||
|
||||
for (pqxx::row::size_type c{0}; c < std::size(R[0]); ++c)
|
||||
{
|
||||
std::string N{R.column_name(c)};
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::string{R[0].at(c).c_str()}, R[0].at(N).c_str(),
|
||||
"Field by name != field by number.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::string{R[0][c].c_str()}, R[0][N].c_str(),
|
||||
"at() is inconsistent with operator[].");
|
||||
|
||||
PQXX_CHECK_EQUAL(R[0][c].name(), N, "Field names are inconsistent.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(R[0][c]), strlen(R[0][c].c_str()),
|
||||
"Field size is not what we expected.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_011);
|
||||
} // namespace
|
||||
@@ -0,0 +1,86 @@
|
||||
#include <pqxx/transaction>
|
||||
#include <pqxx/transactor>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Test program for libpqxx. Verify abort behaviour of transactor.
|
||||
//
|
||||
// The program will attempt to add an entry to a table called "pqxxevents",
|
||||
// with a key column called "year"--and then abort the change.
|
||||
//
|
||||
// Note for the superstitious: the numbering for this test program is pure
|
||||
// coincidence.
|
||||
namespace
|
||||
{
|
||||
// Let's take a boring year that is not going to be in the "pqxxevents" table
|
||||
constexpr unsigned int BoringYear = 1977;
|
||||
|
||||
|
||||
// Count events and specifically events occurring in Boring Year, leaving the
|
||||
// former count in the result pair's first member, and the latter in second.
|
||||
std::pair<int, int> count_events(connection &conn, std::string const &table)
|
||||
{
|
||||
work tx{conn};
|
||||
std::string const count_query{"SELECT count(*) FROM " + table};
|
||||
return std::make_pair(
|
||||
tx.query_value<int>(count_query),
|
||||
tx.query_value<int>(count_query + " WHERE year=" + to_string(BoringYear)));
|
||||
}
|
||||
|
||||
|
||||
struct deliberate_error : std::exception
|
||||
{};
|
||||
|
||||
|
||||
void failed_insert(connection &C, std::string const &table)
|
||||
{
|
||||
work tx(C);
|
||||
result R = tx.exec0(
|
||||
"INSERT INTO " + table + " VALUES (" + to_string(BoringYear) +
|
||||
", "
|
||||
"'yawn')");
|
||||
|
||||
PQXX_CHECK_EQUAL(R.affected_rows(), 1, "Bad affected_rows().");
|
||||
throw deliberate_error();
|
||||
}
|
||||
|
||||
|
||||
void test_013()
|
||||
{
|
||||
connection conn;
|
||||
{
|
||||
work tx{conn};
|
||||
test::create_pqxxevents(tx);
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
std::string const Table{"pqxxevents"};
|
||||
|
||||
auto const Before{
|
||||
perform([&conn, &Table] { return count_events(conn, Table); })};
|
||||
PQXX_CHECK_EQUAL(
|
||||
Before.second, 0,
|
||||
"Already have event for " + to_string(BoringYear) + "--can't test.");
|
||||
|
||||
quiet_errorhandler d(conn);
|
||||
PQXX_CHECK_THROWS(
|
||||
perform([&conn, &Table] { failed_insert(conn, Table); }), deliberate_error,
|
||||
"Failing transactor failed to throw correct exception.");
|
||||
|
||||
auto const After{
|
||||
perform([&conn, &Table] { return count_events(conn, Table); })};
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
After.first, Before.first, "abort() didn't reset event count.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
After.second, Before.second,
|
||||
"abort() didn't reset event count for " + to_string(BoringYear));
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_013);
|
||||
} // namespace
|
||||
@@ -0,0 +1,36 @@
|
||||
#include <iostream>
|
||||
|
||||
#include <pqxx/nontransaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Test nontransaction.
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_014()
|
||||
{
|
||||
connection conn;
|
||||
|
||||
// Begin a "non-transaction" acting on our current connection. This is
|
||||
// really all the transactional integrity we need since we're only
|
||||
// performing one query which does not modify the database.
|
||||
nontransaction tx{conn, "test14"};
|
||||
|
||||
// The transaction class family also has process_notice() functions.
|
||||
// These simply pass the notice through to their connection, but this may
|
||||
// be more convenient in some cases. All process_notice() functions accept
|
||||
// C++ strings as well as C strings.
|
||||
tx.process_notice(std::string{"Started nontransaction\n"});
|
||||
|
||||
// "Commit" the non-transaction. This doesn't really do anything since
|
||||
// nontransaction doesn't start a backend transaction.
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_014);
|
||||
} // namespace
|
||||
@@ -0,0 +1,46 @@
|
||||
#include <iostream>
|
||||
|
||||
#include <pqxx/connection>
|
||||
#include <pqxx/robusttransaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Test robusttransaction.
|
||||
namespace
|
||||
{
|
||||
void test_016()
|
||||
{
|
||||
connection conn;
|
||||
robusttransaction<> tx{conn};
|
||||
result R{tx.exec("SELECT * FROM pg_tables")};
|
||||
|
||||
result::const_iterator c;
|
||||
for (c = std::begin(R); c != std::end(R); ++c)
|
||||
;
|
||||
|
||||
// See if back() and row comparison work properly
|
||||
PQXX_CHECK(
|
||||
std::size(R) >= 2, "Not enough rows in pg_tables to test, sorry!");
|
||||
|
||||
--c;
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
c->size(), std::size(R.back()),
|
||||
"Size mismatch between row iterator and back().");
|
||||
|
||||
std::string const nullstr;
|
||||
for (pqxx::row::size_type i{0}; i < c->size(); ++i)
|
||||
PQXX_CHECK_EQUAL(
|
||||
c[i].as(nullstr), R.back()[i].as(nullstr), "Value mismatch in back().");
|
||||
PQXX_CHECK(*c == R.back(), "Row equality is broken.");
|
||||
PQXX_CHECK(not(*c != R.back()), "Row inequality is broken.");
|
||||
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_016);
|
||||
} // namespace
|
||||
@@ -0,0 +1,29 @@
|
||||
#include <iostream>
|
||||
|
||||
#include <pqxx/connection>
|
||||
#include <pqxx/nontransaction>
|
||||
#include <pqxx/transactor>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Simple test program for libpqxx. Open connection to database, start
|
||||
// a dummy transaction to gain nontransactional access, and perform a query.
|
||||
namespace
|
||||
{
|
||||
void test_017()
|
||||
{
|
||||
connection conn;
|
||||
perform([&conn] {
|
||||
nontransaction tx{conn};
|
||||
auto const r{tx.exec("SELECT * FROM generate_series(1, 4)")};
|
||||
PQXX_CHECK_EQUAL(std::size(r), 4, "Weird query result.");
|
||||
tx.commit();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_017);
|
||||
} // namespace
|
||||
@@ -0,0 +1,80 @@
|
||||
#include <pqxx/connection>
|
||||
#include <pqxx/nontransaction>
|
||||
#include <pqxx/robusttransaction>
|
||||
#include <pqxx/transactor>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Test program for libpqxx. Verify abort behaviour of RobustTransaction.
|
||||
//
|
||||
// The program will attempt to add an entry to a table called "pqxxevents",
|
||||
// with a key column called "year"--and then abort the change.
|
||||
namespace
|
||||
{
|
||||
// Let's take a boring year that is not going to be in the "pqxxevents" table
|
||||
constexpr long BoringYear{1977};
|
||||
|
||||
|
||||
// Count events and specifically events occurring in Boring Year, leaving the
|
||||
// former count in the result pair's first member, and the latter in second.
|
||||
std::pair<int, int> count_events(connection &conn, std::string const &table)
|
||||
{
|
||||
nontransaction tx{conn};
|
||||
std::string const count_query{"SELECT count(*) FROM " + table};
|
||||
return std::make_pair(
|
||||
tx.query_value<int>(count_query),
|
||||
tx.query_value<int>(count_query + " WHERE year=" + to_string(BoringYear)));
|
||||
}
|
||||
|
||||
|
||||
struct deliberate_error : std::exception
|
||||
{};
|
||||
|
||||
|
||||
void test_018()
|
||||
{
|
||||
connection conn;
|
||||
{
|
||||
work tx{conn};
|
||||
test::create_pqxxevents(tx);
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
std::string const Table{"pqxxevents"};
|
||||
|
||||
auto const Before{
|
||||
perform([&conn, &Table] { return count_events(conn, Table); })};
|
||||
PQXX_CHECK_EQUAL(
|
||||
Before.second, 0,
|
||||
"Already have event for " + to_string(BoringYear) + ", cannot run.");
|
||||
|
||||
{
|
||||
quiet_errorhandler d{conn};
|
||||
PQXX_CHECK_THROWS(
|
||||
perform([&conn, Table] {
|
||||
robusttransaction<serializable> tx{conn};
|
||||
tx.exec0(
|
||||
"INSERT INTO " + Table + " VALUES (" + to_string(BoringYear) +
|
||||
", '" + tx.esc("yawn") + "')");
|
||||
|
||||
throw deliberate_error();
|
||||
}),
|
||||
deliberate_error,
|
||||
"Not getting expected exception from failing transactor.");
|
||||
}
|
||||
|
||||
auto const After{
|
||||
perform([&conn, &Table] { return count_events(conn, Table); })};
|
||||
|
||||
PQXX_CHECK_EQUAL(After.first, Before.first, "Event count changed.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
After.second, Before.second,
|
||||
"Event count for " + to_string(BoringYear) + " changed.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_018);
|
||||
} // namespace
|
||||
@@ -0,0 +1,95 @@
|
||||
#include <iostream>
|
||||
|
||||
#include <pqxx/connection>
|
||||
#include <pqxx/nontransaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Test: nontransaction changes are not rolled back on abort.
|
||||
namespace
|
||||
{
|
||||
constexpr unsigned long BoringYear{1977};
|
||||
|
||||
|
||||
void test_020()
|
||||
{
|
||||
connection conn;
|
||||
nontransaction t1{conn};
|
||||
test::create_pqxxevents(t1);
|
||||
|
||||
std::string const Table{"pqxxevents"};
|
||||
|
||||
// Verify our start condition before beginning: there must not be a 1977
|
||||
// record already.
|
||||
result R(t1.exec(("SELECT * FROM " + Table +
|
||||
" "
|
||||
"WHERE year=" +
|
||||
to_string(BoringYear))
|
||||
.c_str()));
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(R), 0,
|
||||
"Already have a row for " + to_string(BoringYear) + ", cannot test.");
|
||||
|
||||
// (Not needed, but verify that clear() works on empty containers)
|
||||
R.clear();
|
||||
PQXX_CHECK(std::empty(R), "result::clear() is broken.");
|
||||
|
||||
// OK. Having laid that worry to rest, add a record for 1977.
|
||||
t1.exec0(
|
||||
"INSERT INTO " + Table +
|
||||
" VALUES"
|
||||
"(" +
|
||||
to_string(BoringYear) +
|
||||
","
|
||||
"'Yawn'"
|
||||
")");
|
||||
|
||||
// Abort T1. Since T1 is a nontransaction, which provides only the
|
||||
// transaction class interface without providing any form of transactional
|
||||
// integrity, this is not going to undo our work.
|
||||
t1.abort();
|
||||
|
||||
// Verify that our record was added, despite the Abort()
|
||||
nontransaction t2{conn, "t2"};
|
||||
R = t2.exec(("SELECT * FROM " + Table +
|
||||
" "
|
||||
"WHERE year=" +
|
||||
to_string(BoringYear))
|
||||
.c_str());
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(R), 1,
|
||||
"Found wrong number of rows for " + to_string(BoringYear) + ".");
|
||||
|
||||
PQXX_CHECK(R.capacity() >= std::size(R), "Result's capacity is too small.");
|
||||
|
||||
R.clear();
|
||||
PQXX_CHECK(std::empty(R), "result::clear() doesn't work.");
|
||||
|
||||
// Now remove our record again
|
||||
t2.exec0(
|
||||
"DELETE FROM " + Table +
|
||||
" "
|
||||
"WHERE year=" +
|
||||
to_string(BoringYear));
|
||||
|
||||
t2.commit();
|
||||
|
||||
// And again, verify results
|
||||
nontransaction t3{conn, "t3"};
|
||||
|
||||
R = t3.exec(("SELECT * FROM " + Table +
|
||||
" "
|
||||
"WHERE year=" +
|
||||
to_string(BoringYear))
|
||||
.c_str());
|
||||
|
||||
PQXX_CHECK_EQUAL(std::size(R), 0, "Record still found after removal.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_020);
|
||||
} // namespace
|
||||
@@ -0,0 +1,70 @@
|
||||
#include <iostream>
|
||||
|
||||
#include <pqxx/connection>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Simple test program for libpqxx. Open a connection to database, start a
|
||||
// transaction, and perform a query inside it.
|
||||
namespace
|
||||
{
|
||||
void test_021()
|
||||
{
|
||||
connection conn;
|
||||
|
||||
std::string const HostName{
|
||||
((conn.hostname() == nullptr) ? "<local>" : conn.hostname())};
|
||||
conn.process_notice(
|
||||
std::string{} + "database=" + conn.dbname() +
|
||||
", "
|
||||
"username=" +
|
||||
conn.username() +
|
||||
", "
|
||||
"hostname=" +
|
||||
HostName +
|
||||
", "
|
||||
"port=" +
|
||||
to_string(conn.port()) +
|
||||
", "
|
||||
"backendpid=" +
|
||||
to_string(conn.backendpid()) + "\n");
|
||||
|
||||
work tx{conn, "test_021"};
|
||||
|
||||
// By now our connection should really have been created
|
||||
conn.process_notice("Printing details on actual connection\n");
|
||||
conn.process_notice(
|
||||
std::string{} + "database=" + conn.dbname() +
|
||||
", "
|
||||
"username=" +
|
||||
conn.username() +
|
||||
", "
|
||||
"hostname=" +
|
||||
HostName +
|
||||
", "
|
||||
"port=" +
|
||||
to_string(conn.port()) +
|
||||
", "
|
||||
"backendpid=" +
|
||||
to_string(conn.backendpid()) + "\n");
|
||||
|
||||
std::string P;
|
||||
from_string(conn.port(), P);
|
||||
PQXX_CHECK_EQUAL(
|
||||
P, to_string(conn.port()), "Port string conversion is broken.");
|
||||
PQXX_CHECK_EQUAL(to_string(P), P, "Port string conversion is broken.");
|
||||
|
||||
result R(tx.exec("SELECT * FROM pg_tables"));
|
||||
|
||||
tx.process_notice(pqxx::internal::concat(
|
||||
to_string(std::size(R)), " result row in transaction ", tx.name(), "\n"));
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_021);
|
||||
} // namespace
|
||||
@@ -0,0 +1,86 @@
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
|
||||
#include <pqxx/nontransaction>
|
||||
#include <pqxx/transaction>
|
||||
#include <pqxx/transactor>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Example program for libpqxx. Modify the database, retaining transactional
|
||||
// integrity using the transactor framework.
|
||||
namespace
|
||||
{
|
||||
// Convert year to 4-digit format.
|
||||
int To4Digits(int Y)
|
||||
{
|
||||
int Result{Y};
|
||||
|
||||
PQXX_CHECK(Y >= 0, "Negative year: " + to_string(Y));
|
||||
|
||||
if (Y < 70)
|
||||
Result += 2000;
|
||||
else if (Y < 100)
|
||||
Result += 1900;
|
||||
else
|
||||
PQXX_CHECK(Y >= 1970, "Unexpected year: " + to_string(Y));
|
||||
return Result;
|
||||
}
|
||||
|
||||
|
||||
// Transaction definition for year-field update. Returns conversions done.
|
||||
std::map<int, int> update_years(connection &C)
|
||||
{
|
||||
std::map<int, int> conversions;
|
||||
work tx{C};
|
||||
|
||||
// Note all different years currently occurring in the table, writing them
|
||||
// and their correct mappings to m_conversions
|
||||
for (auto const &[y] :
|
||||
tx.stream<std::optional<int>>("SELECT year FROM pqxxevents"))
|
||||
{
|
||||
// Read year, and if it is non-null, note its converted value
|
||||
if (bool(y))
|
||||
conversions[y.value()] = To4Digits(y.value());
|
||||
}
|
||||
|
||||
// For each occurring year, write converted date back to whereever it may
|
||||
// occur in the table. Since we're in a transaction, any changes made by
|
||||
// others at the same time will not affect us.
|
||||
for (auto const &c : conversions)
|
||||
tx.exec0(
|
||||
"UPDATE pqxxevents "
|
||||
"SET year=" +
|
||||
to_string(c.second) +
|
||||
" "
|
||||
"WHERE year=" +
|
||||
to_string(c.first));
|
||||
|
||||
tx.commit();
|
||||
|
||||
return conversions;
|
||||
}
|
||||
|
||||
|
||||
void test_026()
|
||||
{
|
||||
connection conn;
|
||||
{
|
||||
nontransaction tx{conn};
|
||||
test::create_pqxxevents(tx);
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
// Perform (an instantiation of) the UpdateYears transactor we've defined
|
||||
// in the code above. This is where the work gets done.
|
||||
auto const conversions{perform([&conn] { return update_years(conn); })};
|
||||
|
||||
PQXX_CHECK(not std::empty(conversions), "No conversions done!");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_026);
|
||||
} // namespace
|
||||
@@ -0,0 +1,108 @@
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include <pqxx/nontransaction>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Test program for libpqxx. Open connection to database, start a transaction,
|
||||
// abort it, and verify that it "never happened."
|
||||
//
|
||||
// The program will attempt to add an entry to a table called "pqxxevents",
|
||||
// with a key column called "year"--and then abort the change.
|
||||
namespace
|
||||
{
|
||||
// Let's take a boring year that is not going to be in the "pqxxevents" table
|
||||
constexpr int BoringYear{1977};
|
||||
|
||||
std::string const Table{"pqxxevents"};
|
||||
|
||||
|
||||
// Count events, and boring events, in table
|
||||
std::pair<int, int> CountEvents(transaction_base &tx)
|
||||
{
|
||||
std::string const events_query{"SELECT count(*) FROM " + Table};
|
||||
std::string const boring_query{
|
||||
events_query + " WHERE year=" + to_string(BoringYear)};
|
||||
|
||||
return std::make_pair(
|
||||
tx.query_value<int>(events_query), tx.query_value<int>(boring_query));
|
||||
}
|
||||
|
||||
|
||||
// Try adding a record, then aborting it, and check whether the abort was
|
||||
// performed correctly.
|
||||
void Test(connection &conn, bool ExplicitAbort)
|
||||
{
|
||||
std::vector<std::string> BoringRow{to_string(BoringYear), "yawn"};
|
||||
|
||||
std::pair<int, int> EventCounts;
|
||||
|
||||
// First run our doomed transaction. This will refuse to run if an event
|
||||
// exists for our Boring Year.
|
||||
{
|
||||
// Begin a transaction acting on our current connection; we'll abort it
|
||||
// later though.
|
||||
work Doomed(conn, "Doomed");
|
||||
|
||||
// Verify that our Boring Year was not yet in the events table
|
||||
EventCounts = CountEvents(Doomed);
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
EventCounts.second, 0,
|
||||
"Can't run; " + to_string(BoringYear) + " is already in the table.");
|
||||
|
||||
// Now let's try to introduce a row for our Boring Year
|
||||
Doomed.exec0(
|
||||
"INSERT INTO " + Table +
|
||||
"(year, event) "
|
||||
"VALUES (" +
|
||||
to_string(BoringYear) + ", 'yawn')");
|
||||
|
||||
auto Recount{CountEvents(Doomed)};
|
||||
PQXX_CHECK_EQUAL(Recount.second, 1, "Unexpected number of events.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
Recount.first, EventCounts.first + 1, "Number of events changed.");
|
||||
|
||||
// Okay, we've added an entry but we don't really want to. Abort it
|
||||
// explicitly if requested, or simply let the Transaction object "expire."
|
||||
if (ExplicitAbort)
|
||||
Doomed.abort();
|
||||
|
||||
// If now explicit abort requested, Doomed Transaction still ends here
|
||||
}
|
||||
|
||||
// Now check that we're back in the original state. Note that this may go
|
||||
// wrong if somebody managed to change the table between our two
|
||||
// transactions.
|
||||
work Checkup(conn, "Checkup");
|
||||
|
||||
auto NewEvents{CountEvents(Checkup)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
NewEvents.first, EventCounts.first, "Wrong number of events.");
|
||||
|
||||
PQXX_CHECK_EQUAL(NewEvents.second, 0, "Found unexpected events.");
|
||||
}
|
||||
|
||||
|
||||
void test_029()
|
||||
{
|
||||
connection conn;
|
||||
{
|
||||
nontransaction tx{conn};
|
||||
test::create_pqxxevents(tx);
|
||||
}
|
||||
|
||||
// Test abort semantics, both with explicit and implicit abort
|
||||
Test(conn, true);
|
||||
Test(conn, false);
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_029);
|
||||
} // namespace
|
||||
@@ -0,0 +1,73 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Test program for libpqxx. Query a table and report its metadata.
|
||||
namespace
|
||||
{
|
||||
void test_030()
|
||||
{
|
||||
std::string const Table{"pg_tables"};
|
||||
|
||||
connection conn;
|
||||
work tx{conn, "test30"};
|
||||
|
||||
result R(tx.exec(("SELECT * FROM " + Table).c_str()));
|
||||
PQXX_CHECK(not std::empty(R), "Table " + Table + " is empty, cannot test.");
|
||||
|
||||
// Print column names
|
||||
for (pqxx::row::size_type c{0}; c < R.columns(); ++c)
|
||||
{
|
||||
std::string N{R.column_name(c)};
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
R[0].column_number(N), R.column_number(N),
|
||||
"row::column_number() is inconsistent with result::column_number().");
|
||||
|
||||
PQXX_CHECK_EQUAL(R[0].column_number(N), c, "Inconsistent column numbers.");
|
||||
}
|
||||
|
||||
// If there are rows in R, compare their metadata to R's.
|
||||
if (std::empty(R))
|
||||
{
|
||||
std::cout << "(Table is empty.)\n";
|
||||
return;
|
||||
}
|
||||
|
||||
PQXX_CHECK_EQUAL(R[0].rownumber(), 0, "Row 0 reports wrong number.");
|
||||
|
||||
if (std::size(R) < 2)
|
||||
std::cout << "(Only one row in table.)\n";
|
||||
else
|
||||
PQXX_CHECK_EQUAL(R[1].rownumber(), 1, "Row 1 reports wrong number.");
|
||||
|
||||
for (pqxx::row::size_type c{0}; c < std::size(R[0]); ++c)
|
||||
{
|
||||
std::string N{R.column_name(c)};
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::string{R[0].at(c).c_str()}, R[0].at(N).c_str(),
|
||||
"Different field values by name and by number.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::string{R[0][c].c_str()}, R[0][N].c_str(),
|
||||
"at() is inconsistent with operator[].");
|
||||
|
||||
PQXX_CHECK_EQUAL(R[0][c].name(), N, "Inconsistent field names.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(R[0][c]), std::strlen(R[0][c].c_str()),
|
||||
"Inconsistent field lengths.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_030);
|
||||
} // namespace
|
||||
@@ -0,0 +1,78 @@
|
||||
#include <pqxx/nontransaction>
|
||||
#include <pqxx/transaction>
|
||||
#include <pqxx/transactor>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Test program for libpqxx. Verify abort behaviour of transactor.
|
||||
//
|
||||
// The program will attempt to add an entry to a table called "pqxxevents",
|
||||
// with a key column called "year"--and then abort the change.
|
||||
//
|
||||
// Note for the superstitious: the numbering for this test program is pure
|
||||
// coincidence.
|
||||
|
||||
namespace
|
||||
{
|
||||
// Let's take a boring year that is not going to be in the "pqxxevents" table
|
||||
constexpr int BoringYear{1977};
|
||||
|
||||
std::pair<int, int> count_events(connection &conn, std::string const &table)
|
||||
{
|
||||
std::string const count_query{"SELECT count(*) FROM " + table};
|
||||
work tx{conn};
|
||||
return std::make_pair(
|
||||
tx.query_value<int>(count_query),
|
||||
tx.query_value<int>(count_query + " WHERE year=" + to_string(BoringYear)));
|
||||
}
|
||||
|
||||
|
||||
struct deliberate_error : std::exception
|
||||
{};
|
||||
|
||||
|
||||
void test_032()
|
||||
{
|
||||
connection conn;
|
||||
{
|
||||
nontransaction tx{conn};
|
||||
test::create_pqxxevents(tx);
|
||||
}
|
||||
|
||||
std::string const Table{"pqxxevents"};
|
||||
|
||||
std::pair<int, int> const Before{
|
||||
perform([&conn, &Table] { return count_events(conn, Table); })};
|
||||
PQXX_CHECK_EQUAL(
|
||||
Before.second, 0,
|
||||
"Already have event for " + to_string(BoringYear) + ", cannot test.");
|
||||
|
||||
{
|
||||
quiet_errorhandler d(conn);
|
||||
PQXX_CHECK_THROWS(
|
||||
perform([&conn, &Table] {
|
||||
work{conn}.exec0(
|
||||
"INSERT INTO " + Table + " VALUES (" + to_string(BoringYear) +
|
||||
", "
|
||||
"'yawn')");
|
||||
throw deliberate_error();
|
||||
}),
|
||||
deliberate_error,
|
||||
"Did not get expected exception from failing transactor.");
|
||||
}
|
||||
|
||||
std::pair<int, int> const After{
|
||||
perform([&conn, &Table] { return count_events(conn, Table); })};
|
||||
|
||||
PQXX_CHECK_EQUAL(After.first, Before.first, "Event count changed.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
After.second, Before.second,
|
||||
"Event count for " + to_string(BoringYear) + " changed.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_032);
|
||||
} // namespace
|
||||
@@ -0,0 +1,78 @@
|
||||
#include <pqxx/nontransaction>
|
||||
#include <pqxx/robusttransaction>
|
||||
#include <pqxx/transactor>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Test program for libpqxx. Verify abort behaviour of RobustTransaction.
|
||||
//
|
||||
// The program will attempt to add an entry to a table called "pqxxevents",
|
||||
// with a key column called "year"--and then abort the change.
|
||||
namespace
|
||||
{
|
||||
// Let's take a boring year that is not going to be in the "pqxxevents" table
|
||||
constexpr int BoringYear{1977};
|
||||
|
||||
// Count events and specifically events occurring in Boring Year, leaving the
|
||||
// former count in the result pair's first member, and the latter in second.
|
||||
std::pair<int, int> count_events(connection &conn, std::string const &table)
|
||||
{
|
||||
std::string const count_query{"SELECT count(*) FROM " + table};
|
||||
nontransaction tx{conn};
|
||||
return std::make_pair(
|
||||
tx.query_value<int>(count_query),
|
||||
tx.query_value<int>(count_query + " WHERE year=" + to_string(BoringYear)));
|
||||
}
|
||||
|
||||
|
||||
struct deliberate_error : std::exception
|
||||
{};
|
||||
|
||||
|
||||
void test_037()
|
||||
{
|
||||
connection conn;
|
||||
{
|
||||
nontransaction tx{conn};
|
||||
test::create_pqxxevents(tx);
|
||||
}
|
||||
|
||||
std::string const Table{"pqxxevents"};
|
||||
|
||||
auto const Before{
|
||||
perform([&conn, &Table] { return count_events(conn, Table); })};
|
||||
PQXX_CHECK_EQUAL(
|
||||
Before.second, 0,
|
||||
"Already have event for " + to_string(BoringYear) + ", cannot test.");
|
||||
|
||||
{
|
||||
quiet_errorhandler d(conn);
|
||||
PQXX_CHECK_THROWS(
|
||||
perform([&conn, &Table] {
|
||||
robusttransaction<> tx{conn};
|
||||
tx.exec0(
|
||||
"INSERT INTO " + Table + " VALUES (" + to_string(BoringYear) +
|
||||
", "
|
||||
"'yawn')");
|
||||
|
||||
throw deliberate_error();
|
||||
}),
|
||||
deliberate_error,
|
||||
"Did not get expected exception from failing transactor.");
|
||||
}
|
||||
|
||||
auto const After{
|
||||
perform([&conn, &Table] { return count_events(conn, Table); })};
|
||||
|
||||
PQXX_CHECK_EQUAL(After.first, Before.first, "Number of events changed.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
After.second, Before.second,
|
||||
"Number of events for " + to_string(BoringYear) + " changed.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_037);
|
||||
} // namespace
|
||||
@@ -0,0 +1,89 @@
|
||||
#include <pqxx/nontransaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Test: nontransaction changes are committed immediately.
|
||||
namespace
|
||||
{
|
||||
int BoringYear{1977};
|
||||
|
||||
|
||||
void test_039()
|
||||
{
|
||||
connection conn;
|
||||
nontransaction tx1{conn};
|
||||
test::create_pqxxevents(tx1);
|
||||
std::string const Table{"pqxxevents"};
|
||||
|
||||
// Verify our start condition before beginning: there must not be a 1977
|
||||
// record already.
|
||||
result R(tx1.exec(
|
||||
"SELECT * FROM " + Table +
|
||||
" "
|
||||
"WHERE year=" +
|
||||
to_string(BoringYear)));
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(R), 0,
|
||||
"Already have a row for " + to_string(BoringYear) + ", cannot test.");
|
||||
|
||||
// (Not needed, but verify that clear() works on empty containers)
|
||||
R.clear();
|
||||
PQXX_CHECK(std::empty(R), "Result is non-empty after clear().");
|
||||
|
||||
// OK. Having laid that worry to rest, add a record for 1977.
|
||||
tx1.exec0(
|
||||
"INSERT INTO " + Table +
|
||||
" VALUES"
|
||||
"(" +
|
||||
to_string(BoringYear) +
|
||||
","
|
||||
"'Yawn'"
|
||||
")");
|
||||
|
||||
// Abort tx1. Since tx1 is a nontransaction, which provides only the
|
||||
// transaction class interface without providing any form of transactional
|
||||
// integrity, this is not going to undo our work.
|
||||
tx1.abort();
|
||||
|
||||
// Verify that our record was added, despite the Abort()
|
||||
nontransaction tx2(conn, "tx2");
|
||||
R = tx2.exec(
|
||||
"SELECT * FROM " + Table +
|
||||
" "
|
||||
"WHERE year=" +
|
||||
to_string(BoringYear));
|
||||
PQXX_CHECK_EQUAL(std::size(R), 1, "Unexpected result size.");
|
||||
|
||||
PQXX_CHECK(R.capacity() >= std::size(R), "Result's capacity is too small.");
|
||||
|
||||
R.clear();
|
||||
PQXX_CHECK(std::empty(R), "result::clear() is broken.");
|
||||
|
||||
// Now remove our record again
|
||||
tx2.exec0(
|
||||
"DELETE FROM " + Table +
|
||||
" "
|
||||
"WHERE year=" +
|
||||
to_string(BoringYear));
|
||||
|
||||
tx2.commit();
|
||||
|
||||
// And again, verify results
|
||||
nontransaction tx3(conn, "tx3");
|
||||
|
||||
R = tx3.exec(
|
||||
"SELECT * FROM " + Table +
|
||||
" "
|
||||
"WHERE year=" +
|
||||
to_string(BoringYear));
|
||||
|
||||
PQXX_CHECK_EQUAL(std::size(R), 0, "Record is not gone as expected.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_039);
|
||||
} // namespace
|
||||
@@ -0,0 +1,74 @@
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Streams test program for libpqxx. Insert a result field into various
|
||||
// types of streams.
|
||||
namespace
|
||||
{
|
||||
void test_046()
|
||||
{
|
||||
connection conn;
|
||||
work tx{conn};
|
||||
|
||||
row R{tx.exec1("SELECT count(*) FROM pg_tables")};
|
||||
|
||||
// Read the value into a stringstream.
|
||||
std::stringstream I;
|
||||
I << R[0];
|
||||
|
||||
// Now convert the stringstream into a numeric type
|
||||
long L{}, L2{};
|
||||
I >> L;
|
||||
|
||||
R[0].to(L2);
|
||||
PQXX_CHECK_EQUAL(L, L2, "Inconsistency between conversion methods.");
|
||||
|
||||
float F{}, F2{};
|
||||
std::stringstream I2;
|
||||
I2 << R[0];
|
||||
I2 >> F;
|
||||
R[0].to(F2);
|
||||
PQXX_CHECK_BOUNDS(F2, F - 0.01, F + 0.01, "Bad floating-point result.");
|
||||
|
||||
auto F3{from_string<float>(R[0].c_str())};
|
||||
PQXX_CHECK_BOUNDS(F3, F - 0.01, F + 0.01, "Bad float from from_string.");
|
||||
|
||||
auto D{from_string<double>(R[0].c_str())};
|
||||
PQXX_CHECK_BOUNDS(D, F - 0.01, F + 0.01, "Bad double from from_string.");
|
||||
|
||||
auto LD{from_string<long double>(R[0].c_str())};
|
||||
PQXX_CHECK_BOUNDS(
|
||||
LD, F - 0.01, F + 0.01, "Bad long double from from_string.");
|
||||
|
||||
auto S{from_string<std::string>(R[0].c_str())},
|
||||
S2{from_string<std::string>(std::string{R[0].c_str()})},
|
||||
S3{from_string<std::string>(R[0])};
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
S2, S,
|
||||
"from_string(char const[], std::string &) "
|
||||
"is inconsistent with "
|
||||
"from_string(std::string const &, std::string &).");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
S3, S2,
|
||||
"from_string(result::field const &, std::string &) "
|
||||
"is inconsistent with "
|
||||
"from_string(std::string const &, std::string &).");
|
||||
|
||||
PQXX_CHECK(tx.query_value<bool>("SELECT 1=1"), "1=1 doesn't yield 'true.'");
|
||||
|
||||
PQXX_CHECK(not tx.query_value<bool>("SELECT 2+2=5"), "2+2=5 yields 'true.'");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_046);
|
||||
} // namespace
|
||||
@@ -0,0 +1,24 @@
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Simple test program for libpqxx. Issue invalid query and handle error.
|
||||
namespace
|
||||
{
|
||||
void test_056()
|
||||
{
|
||||
connection conn;
|
||||
work tx{conn};
|
||||
quiet_errorhandler d(conn);
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
tx.exec("DELIBERATELY INVALID TEST QUERY..."), sql_error,
|
||||
"SQL syntax error did not raise expected exception.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_056);
|
||||
} // namespace
|
||||
@@ -0,0 +1,85 @@
|
||||
#include <iostream>
|
||||
|
||||
#include <pqxx/nontransaction>
|
||||
#include <pqxx/pipeline>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Example program for libpqxx. Test session variable functionality.
|
||||
namespace
|
||||
{
|
||||
std::string GetDatestyle(connection &conn)
|
||||
{
|
||||
return conn.get_var("DATESTYLE");
|
||||
}
|
||||
|
||||
|
||||
std::string SetDatestyle(connection &conn, std::string style)
|
||||
{
|
||||
conn.set_session_var("DATESTYLE", style);
|
||||
std::string const fullname{GetDatestyle(conn)};
|
||||
PQXX_CHECK(
|
||||
not std::empty(fullname),
|
||||
"Setting datestyle to " + style + " makes it an empty string.");
|
||||
|
||||
return fullname;
|
||||
}
|
||||
|
||||
|
||||
void CheckDatestyle(connection &conn, std::string expected)
|
||||
{
|
||||
PQXX_CHECK_EQUAL(GetDatestyle(conn), expected, "Got wrong datestyle.");
|
||||
}
|
||||
|
||||
|
||||
void RedoDatestyle(
|
||||
connection &conn, std::string const &style, std::string const &expected)
|
||||
{
|
||||
PQXX_CHECK_EQUAL(
|
||||
SetDatestyle(conn, style), expected, "Set wrong datestyle.");
|
||||
}
|
||||
|
||||
|
||||
void ActivationTest(
|
||||
connection &conn, std::string const &style, std::string const &expected)
|
||||
{
|
||||
RedoDatestyle(conn, style, expected);
|
||||
CheckDatestyle(conn, expected);
|
||||
}
|
||||
|
||||
|
||||
void test_060()
|
||||
{
|
||||
connection conn;
|
||||
|
||||
PQXX_CHECK(not std::empty(GetDatestyle(conn)), "Initial datestyle not set.");
|
||||
|
||||
std::string const ISOname{SetDatestyle(conn, "ISO")};
|
||||
std::string const SQLname{SetDatestyle(conn, "SQL")};
|
||||
|
||||
PQXX_CHECK_NOT_EQUAL(ISOname, SQLname, "Same datestyle in SQL and ISO.");
|
||||
|
||||
RedoDatestyle(conn, "SQL", SQLname);
|
||||
|
||||
ActivationTest(conn, "ISO", ISOname);
|
||||
ActivationTest(conn, "SQL", SQLname);
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
conn.set_session_var("bonjour_name", std::optional<std::string>{}),
|
||||
pqxx::variable_set_to_null,
|
||||
"Setting a variable to null did not report the error correctly.");
|
||||
|
||||
// Prove that setting an unknown variable causes an error, as expected
|
||||
quiet_errorhandler d{conn};
|
||||
PQXX_CHECK_THROWS(
|
||||
conn.set_session_var("NONEXISTENT_VARIABLE_I_HOPE", 1), sql_error,
|
||||
"Setting unknown variable failed to fail.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_060);
|
||||
} // namespace
|
||||
@@ -0,0 +1,61 @@
|
||||
#include <iostream>
|
||||
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Example program for libpqxx. Test local variable functionality.
|
||||
namespace
|
||||
{
|
||||
std::string GetDatestyle(transaction_base &T)
|
||||
{
|
||||
return T.conn().get_var("DATESTYLE");
|
||||
}
|
||||
|
||||
|
||||
std::string SetDatestyle(transaction_base &T, std::string style)
|
||||
{
|
||||
T.conn().set_session_var("DATESTYLE", style);
|
||||
std::string const fullname{GetDatestyle(T)};
|
||||
PQXX_CHECK(
|
||||
not std::empty(fullname),
|
||||
"Setting datestyle to " + style + " makes it an empty string.");
|
||||
|
||||
return fullname;
|
||||
}
|
||||
|
||||
|
||||
void RedoDatestyle(
|
||||
transaction_base &T, std::string const &style, std::string const &expected)
|
||||
{
|
||||
PQXX_CHECK_EQUAL(SetDatestyle(T, style), expected, "Set wrong datestyle.");
|
||||
}
|
||||
|
||||
|
||||
void test_061()
|
||||
{
|
||||
connection conn;
|
||||
work tx{conn};
|
||||
|
||||
PQXX_CHECK(not std::empty(GetDatestyle(tx)), "Initial datestyle not set.");
|
||||
|
||||
std::string const ISOname{SetDatestyle(tx, "ISO")};
|
||||
std::string const SQLname{SetDatestyle(tx, "SQL")};
|
||||
|
||||
PQXX_CHECK_NOT_EQUAL(ISOname, SQLname, "Same datestyle in SQL and ISO.");
|
||||
|
||||
RedoDatestyle(tx, "SQL", SQLname);
|
||||
|
||||
// Prove that setting an unknown variable causes an error, as expected
|
||||
quiet_errorhandler d(tx.conn());
|
||||
PQXX_CHECK_THROWS(
|
||||
conn.set_session_var("NONEXISTENT_VARIABLE_I_HOPE", 1), sql_error,
|
||||
"Setting unknown variable failed to fail.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_061);
|
||||
} // namespace
|
||||
@@ -0,0 +1,61 @@
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Example program for libpqxx. Test binary string functionality.
|
||||
namespace
|
||||
{
|
||||
void test_062()
|
||||
{
|
||||
connection conn;
|
||||
work tx{conn};
|
||||
|
||||
std::string const TestStr{
|
||||
"Nasty\n\030Test\n\t String with \200\277 weird bytes "
|
||||
"\r\0 and Trailer\\\\\0"};
|
||||
|
||||
tx.exec0("CREATE TEMP TABLE pqxxbin (binfield bytea)");
|
||||
|
||||
std::string const Esc{tx.esc_raw(std::basic_string<std::byte>{
|
||||
reinterpret_cast<std::byte const *>(std::data(TestStr)),
|
||||
std::size(TestStr)})};
|
||||
|
||||
tx.exec0("INSERT INTO pqxxbin VALUES ('" + Esc + "')");
|
||||
|
||||
result R{tx.exec("SELECT * from pqxxbin")};
|
||||
tx.exec0("DELETE FROM pqxxbin");
|
||||
|
||||
auto const B{R.at(0).at(0).as<std::basic_string<std::byte>>()};
|
||||
|
||||
PQXX_CHECK(not std::empty(B), "Binary string became empty in conversion.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(B), std::size(TestStr), "Binary string was mangled.");
|
||||
|
||||
std::basic_string<std::byte>::const_iterator c;
|
||||
std::basic_string<std::byte>::size_type i;
|
||||
for (i = 0, c = std::begin(B); i < std::size(B); ++i, ++c)
|
||||
{
|
||||
PQXX_CHECK(c != std::end(B), "Premature end to binary string.");
|
||||
|
||||
char const x{TestStr.at(i)}, y{char(B.at(i))}, z{char(std::data(B)[i])};
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::string(&x, 1), std::string(&y, 1), "Binary string byte changed.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::string(&y, 1), std::string(&z, 1),
|
||||
"Inconsistent byte at offset " + to_string(i) + ".");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_062);
|
||||
} // namespace
|
||||
@@ -0,0 +1,55 @@
|
||||
#include <iostream>
|
||||
|
||||
#include <pqxx/pipeline>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Test program for libpqxx. Issue a query repeatedly through a pipeline, and
|
||||
// compare results.
|
||||
namespace
|
||||
{
|
||||
void TestPipeline(pipeline &P, int numqueries)
|
||||
{
|
||||
std::string const Q{"SELECT 99"};
|
||||
|
||||
for (int i{numqueries}; i > 0; --i) P.insert(Q);
|
||||
|
||||
PQXX_CHECK(
|
||||
(numqueries == 0) or not std::empty(P), "pipeline::empty() is broken.");
|
||||
|
||||
int res{0};
|
||||
for (int i{numqueries}; i > 0; --i)
|
||||
{
|
||||
PQXX_CHECK(
|
||||
not std::empty(P), "Got wrong number of queries from pipeline.");
|
||||
|
||||
auto R{P.retrieve()};
|
||||
|
||||
if (res != 0)
|
||||
PQXX_CHECK_EQUAL(
|
||||
R.second[0][0].as<int>(), res,
|
||||
"Got unexpected result out of pipeline.");
|
||||
|
||||
res = R.second[0][0].as<int>();
|
||||
}
|
||||
|
||||
PQXX_CHECK(std::empty(P), "Pipeline not empty after retrieval.");
|
||||
}
|
||||
|
||||
|
||||
void test_069()
|
||||
{
|
||||
connection conn;
|
||||
work tx{conn};
|
||||
pipeline P(tx);
|
||||
PQXX_CHECK(std::empty(P), "Pipeline is not empty initially.");
|
||||
for (int i{0}; i < 5; ++i) TestPipeline(P, i);
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_069);
|
||||
} // namespace
|
||||
@@ -0,0 +1,101 @@
|
||||
#include <pqxx/pipeline>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
void TestPipeline(pipeline &P, int numqueries)
|
||||
{
|
||||
std::string const Q{"SELECT * FROM generate_series(1, 10)"};
|
||||
result const Empty;
|
||||
PQXX_CHECK(std::empty(Empty), "Default-constructed result is not empty.");
|
||||
PQXX_CHECK(
|
||||
std::empty(Empty.query()), "Default-constructed result has query");
|
||||
|
||||
P.retain();
|
||||
for (int i{numqueries}; i > 0; --i) P.insert(Q);
|
||||
P.resume();
|
||||
|
||||
PQXX_CHECK(
|
||||
(numqueries == 0) || not std::empty(P), "pipeline::empty() is broken.");
|
||||
|
||||
int res{0};
|
||||
result Prev;
|
||||
PQXX_CHECK_EQUAL(Prev, Empty, "Default-constructed results are not equal.");
|
||||
|
||||
for (int i{numqueries}; i > 0; --i)
|
||||
{
|
||||
PQXX_CHECK(not std::empty(P), "Got no results from pipeline.");
|
||||
|
||||
auto R{P.retrieve()};
|
||||
|
||||
PQXX_CHECK_NOT_EQUAL(R.second, Empty, "Got empty result.");
|
||||
if (Prev != Empty)
|
||||
PQXX_CHECK_EQUAL(R.second, Prev, "Results to same query are different.");
|
||||
|
||||
Prev = R.second;
|
||||
PQXX_CHECK_EQUAL(Prev, R.second, "Assignment breaks result equality.");
|
||||
PQXX_CHECK_EQUAL(R.second.query(), Q, "Result is for unexpected query.");
|
||||
|
||||
if (res != 0)
|
||||
PQXX_CHECK_EQUAL(Prev[0][0].as<int>(), res, "Bad result from pipeline.");
|
||||
|
||||
res = Prev[0][0].as<int>();
|
||||
}
|
||||
|
||||
PQXX_CHECK(std::empty(P), "Pipeline was not empty after retrieval.");
|
||||
}
|
||||
|
||||
|
||||
// Test program for libpqxx. Issue a query repeatedly through a pipeline, and
|
||||
// compare results. Use retain() and resume() for performance.
|
||||
void test_070()
|
||||
{
|
||||
connection conn;
|
||||
work tx{conn};
|
||||
pipeline P(tx);
|
||||
|
||||
PQXX_CHECK(std::empty(P), "Pipeline is not empty initially.");
|
||||
|
||||
// Try to confuse the pipeline by feeding it a query and flushing
|
||||
P.retain();
|
||||
std::string const Q{"SELECT * FROM pg_tables"};
|
||||
P.insert(Q);
|
||||
P.flush();
|
||||
|
||||
PQXX_CHECK(std::empty(P), "Pipeline was not empty after flush().");
|
||||
|
||||
// See if complete() breaks retain() as it should
|
||||
P.retain();
|
||||
P.insert(Q);
|
||||
PQXX_CHECK(not std::empty(P), "Pipeline was empty after insert().");
|
||||
P.complete();
|
||||
PQXX_CHECK(not std::empty(P), "complete() emptied pipeline.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
P.retrieve().second.query(), Q, "Result is for wrong query.");
|
||||
|
||||
PQXX_CHECK(std::empty(P), "Pipeline not empty after retrieve().");
|
||||
|
||||
// See if retrieve() breaks retain() when it needs to
|
||||
P.retain();
|
||||
P.insert(Q);
|
||||
PQXX_CHECK_EQUAL(
|
||||
P.retrieve().second.query(), Q, "Got result for wrong query.");
|
||||
|
||||
// See if regular retain()/resume() works
|
||||
for (int i{0}; i < 5; ++i) TestPipeline(P, i);
|
||||
|
||||
// See if retrieve() fails on an empty pipeline, as it should
|
||||
quiet_errorhandler d(conn);
|
||||
PQXX_CHECK_THROWS_EXCEPTION(
|
||||
P.retrieve(), "Empty pipeline allows retrieve().");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_070);
|
||||
@@ -0,0 +1,74 @@
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
|
||||
#include <pqxx/pipeline>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Test program for libpqxx. Issue queries through a pipeline, and retrieve
|
||||
// results both in-order and out-of-order.
|
||||
namespace
|
||||
{
|
||||
using Exp = std::map<pipeline::query_id, int>;
|
||||
|
||||
template<typename PAIR> void checkresult(pipeline &P, PAIR c)
|
||||
{
|
||||
result const r{P.retrieve(c.first)};
|
||||
int const val{r.at(0).at(0).as(int(0))};
|
||||
PQXX_CHECK_EQUAL(val, c.second, "Wrong result from pipeline.");
|
||||
}
|
||||
|
||||
|
||||
void test_071()
|
||||
{
|
||||
connection conn;
|
||||
work tx{conn};
|
||||
pipeline P(tx);
|
||||
|
||||
// Keep expected result for every query we issue
|
||||
Exp values;
|
||||
|
||||
// Insert queries returning various numbers.
|
||||
for (int i{1}; i < 10; ++i) values[P.insert("SELECT " + to_string(i))] = i;
|
||||
|
||||
// Retrieve results in query_id order, and compare to expected values
|
||||
for (auto &c : values) checkresult(P, c);
|
||||
|
||||
PQXX_CHECK(std::empty(P), "Pipeline was not empty retrieving all results.");
|
||||
|
||||
values.clear();
|
||||
|
||||
// Insert more queries returning various numbers
|
||||
P.retain(20);
|
||||
for (int i{100}; i > 90; --i) values[P.insert("SELECT " + to_string(i))] = i;
|
||||
|
||||
P.resume();
|
||||
|
||||
// Retrieve results in reverse order
|
||||
for (auto c{std::rbegin(values)}; c != std::rend(values); ++c)
|
||||
checkresult(P, *c);
|
||||
|
||||
values.clear();
|
||||
P.retain(10);
|
||||
for (int i{1010}; i > 1000; --i)
|
||||
values[P.insert("SELECT " + to_string(i))] = i;
|
||||
for (auto &c : values)
|
||||
{
|
||||
if (P.is_finished(c.first))
|
||||
std::cout << "Query #" << c.first << " completed despite retain()"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// See that all results are retrieved by complete()
|
||||
P.complete();
|
||||
for (auto &c : values)
|
||||
PQXX_CHECK(P.is_finished(c.first), "Query not finished after complete().");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_071);
|
||||
@@ -0,0 +1,53 @@
|
||||
#include <iostream>
|
||||
|
||||
#include <pqxx/pipeline>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Test program for libpqxx. Test error handling for pipeline.
|
||||
namespace
|
||||
{
|
||||
void test_072()
|
||||
{
|
||||
connection conn;
|
||||
work tx{conn};
|
||||
pipeline P{tx};
|
||||
|
||||
// Ensure all queries are issued at once to make the test more interesting
|
||||
P.retain();
|
||||
|
||||
// The middle query should fail; the surrounding two should succeed
|
||||
auto const id_1{P.insert("SELECT 1")};
|
||||
auto const id_f{P.insert("SELECT * FROM pg_nonexist")};
|
||||
auto const id_2{P.insert("SELECT 2")};
|
||||
|
||||
// See that we can process the queries without stumbling over the error
|
||||
P.complete();
|
||||
|
||||
// We should be able to get the first result, which preceeds the error
|
||||
auto const res_1{P.retrieve(id_1).at(0).at(0).as<int>()};
|
||||
PQXX_CHECK_EQUAL(res_1, 1, "Got wrong result from pipeline.");
|
||||
|
||||
// We should *not* get a result for the query behind the error
|
||||
{
|
||||
quiet_errorhandler d{conn};
|
||||
PQXX_CHECK_THROWS(
|
||||
P.retrieve(id_2).at(0).at(0).as<int>(), std::runtime_error,
|
||||
"Pipeline wrongly resumed after SQL error.");
|
||||
}
|
||||
|
||||
// Now see that we get an exception when we touch the failed result
|
||||
{
|
||||
quiet_errorhandler d{conn};
|
||||
PQXX_CHECK_THROWS(
|
||||
P.retrieve(id_f), sql_error, "Pipeline failed to register SQL error.");
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_072);
|
||||
@@ -0,0 +1,72 @@
|
||||
#include <cmath>
|
||||
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
using namespace pqxx;
|
||||
|
||||
|
||||
// Test program for libpqxx. Test fieldstream.
|
||||
namespace
|
||||
{
|
||||
void test_074()
|
||||
{
|
||||
connection conn;
|
||||
work tx{conn};
|
||||
|
||||
result R{tx.exec("SELECT * FROM pg_tables")};
|
||||
std::string const sval{R.at(0).at(1).c_str()};
|
||||
std::string sval2;
|
||||
fieldstream fs1(R.front()[1]);
|
||||
fs1 >> sval2;
|
||||
PQXX_CHECK_EQUAL(sval2, sval, "fieldstream returned wrong value.");
|
||||
|
||||
R = tx.exec("SELECT count(*) FROM pg_tables");
|
||||
int ival;
|
||||
fieldstream fs2(R.at(0).at(0));
|
||||
fs2 >> ival;
|
||||
PQXX_CHECK_EQUAL(
|
||||
ival, R.front().front().as<int>(), "fieldstream::front() is broken.");
|
||||
|
||||
double dval;
|
||||
(fieldstream(R.at(0).at(0))) >> dval;
|
||||
PQXX_CHECK_BOUNDS(
|
||||
dval, R[0][0].as<double>() - 0.1, R[0][0].as<double>() + 0.1,
|
||||
"Got wrong double from fieldstream.");
|
||||
|
||||
auto const roughpi{static_cast<float>(3.1415926435)};
|
||||
R = tx.exec("SELECT " + to_string(roughpi));
|
||||
float pival;
|
||||
(fieldstream(R.at(0).at(0))) >> pival;
|
||||
PQXX_CHECK_BOUNDS(
|
||||
pival, roughpi - 0.001, roughpi + 0.001,
|
||||
"Pi approximation came back wrong from fieldstream.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
to_string(R[0][0]), R[0][0].c_str(),
|
||||
"to_string(result::field) is inconsistent with c_str().");
|
||||
|
||||
float float_pi;
|
||||
from_string(to_string(roughpi), float_pi);
|
||||
PQXX_CHECK_BOUNDS(
|
||||
float_pi, roughpi - 0.00001, roughpi + 0.00001,
|
||||
"Float changed in conversion.");
|
||||
|
||||
double double_pi;
|
||||
pqxx::from_string(pqxx::to_string(static_cast<double>(roughpi)), double_pi);
|
||||
PQXX_CHECK_BOUNDS(
|
||||
double_pi, roughpi - 0.00001, roughpi + 0.00001,
|
||||
"Double changed in conversion.");
|
||||
|
||||
long double const ld{roughpi};
|
||||
long double long_double_pi;
|
||||
from_string(to_string(ld), long_double_pi);
|
||||
PQXX_CHECK_BOUNDS(
|
||||
long_double_pi, roughpi - 0.00001, roughpi + 0.00001,
|
||||
"long double changed in conversion.");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_074);
|
||||
@@ -0,0 +1,108 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
|
||||
// Test program for libpqxx. Compare const_reverse_iterator iteration of a
|
||||
// result to a regular, const_iterator iteration.
|
||||
namespace
|
||||
{
|
||||
void test_075()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
|
||||
pqxx::test::create_pqxxevents(tx);
|
||||
auto const R(tx.exec("SELECT year FROM pqxxevents"));
|
||||
PQXX_CHECK(not std::empty(R), "No events found, cannot test.");
|
||||
|
||||
PQXX_CHECK_EQUAL(R[0], R.at(0), "Inconsistent result indexing.");
|
||||
PQXX_CHECK(not(R[0] != R.at(0)), "result::row::operator!=() is broken.");
|
||||
|
||||
PQXX_CHECK_EQUAL(R[0][0], R[0].at(0), "Inconsistent row indexing.");
|
||||
PQXX_CHECK(
|
||||
not(R[0][0] != R[0].at(0)), "result::field::operator!=() is broken.");
|
||||
|
||||
std::vector<std::string> contents;
|
||||
for (auto const &i : R) contents.push_back(i.at(0).as<std::string>());
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(contents), std::vector<std::string>::size_type(std::size(R)),
|
||||
"Number of values does not match result size.");
|
||||
|
||||
for (pqxx::result::size_type i{0}; i < std::size(R); ++i)
|
||||
PQXX_CHECK_EQUAL(
|
||||
contents[static_cast<std::size_t>(i)], R.at(i).at(0).c_str(),
|
||||
"Inconsistent iteration.");
|
||||
|
||||
// Thorough test for result::const_reverse_iterator
|
||||
pqxx::result::const_reverse_iterator ri1(std::rbegin(R)), ri2(ri1),
|
||||
ri3(std::end(R));
|
||||
ri2 = std::rbegin(R);
|
||||
|
||||
PQXX_CHECK(ri2 == ri1, "reverse_iterator copy constructor is broken.");
|
||||
PQXX_CHECK(ri3 == ri2, "result::end() does not generate rbegin().");
|
||||
PQXX_CHECK_EQUAL(
|
||||
ri2 - ri3, 0,
|
||||
"const_reverse_iterator is at nonzero distance from its own copy.");
|
||||
|
||||
PQXX_CHECK(ri2 == ri3 + 0, "reverse_iterator+0 gives strange result.");
|
||||
PQXX_CHECK(ri2 == ri3 - 0, "reverse_iterator-0 gives strange result.");
|
||||
PQXX_CHECK(not(ri3 < ri2), "operator<() breaks on equal reverse_iterators.");
|
||||
PQXX_CHECK(ri2 <= ri3, "operator<=() breaks on equal reverse_iterators.");
|
||||
|
||||
PQXX_CHECK(ri3++ == ri2, "reverse_iterator post-increment is broken.");
|
||||
|
||||
PQXX_CHECK_EQUAL(ri3 - ri2, 1, "Wrong nonzero reverse_iterator distance.");
|
||||
PQXX_CHECK(ri3 > ri2, "reverse_iterator operator>() is broken.");
|
||||
PQXX_CHECK(ri3 >= ri2, "reverse_iterator operator>=() is broken.");
|
||||
PQXX_CHECK(ri2 < ri3, "reverse_iterator operator<() is broken.");
|
||||
PQXX_CHECK(ri2 <= ri3, "reverse_iterator operator<=() is broken.");
|
||||
PQXX_CHECK(ri3 == ri2 + 1, "Adding int to reverse_iterator is broken.");
|
||||
PQXX_CHECK(
|
||||
ri2 == ri3 - 1, "Subtracting int from reverse_iterator is broken.");
|
||||
|
||||
PQXX_CHECK(ri3 == ++ri2, "reverse_iterator pre-increment is broken.");
|
||||
PQXX_CHECK(ri3 >= ri2, "operator>=() breaks on equal reverse_iterators.");
|
||||
PQXX_CHECK(ri3 >= ri2, "operator<=() breaks on equal reverse_iterators.");
|
||||
|
||||
PQXX_CHECK(
|
||||
*ri3.base() == R.back(), "reverse_iterator does not arrive at back().");
|
||||
|
||||
PQXX_CHECK(
|
||||
ri1->at(0) == (*ri1).at(0),
|
||||
"reverse_iterator operator->() is inconsistent with operator*().");
|
||||
|
||||
PQXX_CHECK(ri2-- == ri3, "reverse_iterator post-decrement is broken.");
|
||||
PQXX_CHECK(ri2 == --ri3, "reverse_iterator pre-decrement is broken.");
|
||||
PQXX_CHECK(ri2 == std::rbegin(R), "reverse_iterator decrement is broken.");
|
||||
|
||||
ri2 += 1;
|
||||
ri3 -= -1;
|
||||
|
||||
PQXX_CHECK(
|
||||
ri2 != std::rbegin(R), "Adding to reverse_iterator does not work.");
|
||||
PQXX_CHECK(
|
||||
ri3 == ri2, "reverse_iterator operator-=() breaks on negative distances.");
|
||||
|
||||
ri2 -= 1;
|
||||
PQXX_CHECK(
|
||||
ri2 == std::rbegin(R),
|
||||
"reverse_iterator operator+=() and operator-=() do not cancel out.");
|
||||
|
||||
// Now verify that reverse iterator also sees the same results...
|
||||
auto l{std::rbegin(contents)};
|
||||
for (auto i{std::rbegin(R)}; i != std::rend(R); ++i, ++l)
|
||||
PQXX_CHECK_EQUAL(*l, i->at(0).c_str(), "Inconsistent reverse iteration.");
|
||||
|
||||
PQXX_CHECK(l == std::rend(contents), "Reverse iteration ended too soon.");
|
||||
|
||||
PQXX_CHECK(not std::empty(R), "No events found in table, cannot test.");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_075);
|
||||
@@ -0,0 +1,52 @@
|
||||
#include <pqxx/nontransaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
// Simple test program for libpqxx. Test string conversion routines.
|
||||
namespace
|
||||
{
|
||||
void test_076()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::nontransaction tx{conn};
|
||||
|
||||
auto RFalse{tx.exec1("SELECT 1=0")}, RTrue{tx.exec1("SELECT 1=1")};
|
||||
auto False{pqxx::from_string<bool>(RFalse[0])},
|
||||
True{pqxx::from_string<bool>(RTrue[0])};
|
||||
PQXX_CHECK(not False, "False bool converted to true.");
|
||||
PQXX_CHECK(True, "True bool converted to false.");
|
||||
|
||||
RFalse = tx.exec1("SELECT " + pqxx::to_string(False));
|
||||
RTrue = tx.exec1("SELECT " + pqxx::to_string(True));
|
||||
False = pqxx::from_string<bool>(RFalse[0]);
|
||||
True = pqxx::from_string<bool>(RTrue[0]);
|
||||
PQXX_CHECK(not False, "False bool converted to true.");
|
||||
PQXX_CHECK(True, "True bool converted to false.");
|
||||
|
||||
short const svals[]{-1, 1, 999, -32767, -32768, 32767, 0};
|
||||
for (int i{0}; svals[i] != 0; ++i)
|
||||
{
|
||||
auto s{pqxx::from_string<short>(pqxx::to_string(svals[i]))};
|
||||
PQXX_CHECK_EQUAL(s, svals[i], "short/string conversion not bijective.");
|
||||
s = pqxx::from_string<short>(
|
||||
tx.exec1("SELECT " + pqxx::to_string(svals[i]))[0].c_str());
|
||||
PQXX_CHECK_EQUAL(s, svals[i], "Roundtrip through backend changed short.");
|
||||
}
|
||||
|
||||
unsigned short const uvals[]{1, 999, 32767, 32768, 65535, 0};
|
||||
for (int i{0}; uvals[i] != 0; ++i)
|
||||
{
|
||||
auto u{pqxx::from_string<unsigned short>(pqxx::to_string(uvals[i]))};
|
||||
PQXX_CHECK_EQUAL(
|
||||
u, uvals[i], "unsigned short/string conversion not bijective.");
|
||||
|
||||
u = pqxx::from_string<unsigned short>(
|
||||
tx.exec1("SELECT " + pqxx::to_string(uvals[i]))[0].c_str());
|
||||
PQXX_CHECK_EQUAL(
|
||||
u, uvals[i], "Roundtrip through backend changed unsigned short.");
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_076);
|
||||
@@ -0,0 +1,28 @@
|
||||
#include <pqxx/nontransaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
|
||||
// Test program for libpqxx. Test result::swap()
|
||||
namespace
|
||||
{
|
||||
void test_077()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::nontransaction tx{conn};
|
||||
|
||||
auto RFalse{tx.exec("SELECT 1=0")}, RTrue{tx.exec("SELECT 1=1")};
|
||||
auto f{pqxx::from_string<bool>(RFalse[0][0])};
|
||||
auto t{pqxx::from_string<bool>(RTrue[0][0])};
|
||||
PQXX_CHECK(
|
||||
not f and t, "Booleans converted incorrectly; can't trust this test.");
|
||||
|
||||
RFalse.swap(RTrue);
|
||||
f = pqxx::from_string<bool>(RFalse[0][0]);
|
||||
t = pqxx::from_string<bool>(RTrue[0][0]);
|
||||
PQXX_CHECK(f and not t, "result::swap() is broken.");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_077);
|
||||
@@ -0,0 +1,69 @@
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include <pqxx/notification>
|
||||
#include <pqxx/transaction>
|
||||
#include <pqxx/transactor>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
|
||||
// Example program for libpqxx. Send notification to self, using a
|
||||
// notification name with unusal characters, and without polling.
|
||||
namespace
|
||||
{
|
||||
// Sample implementation of notification receiver.
|
||||
class TestListener : public pqxx::notification_receiver
|
||||
{
|
||||
bool m_done;
|
||||
|
||||
public:
|
||||
explicit TestListener(pqxx::connection &conn, std::string const &Name) :
|
||||
pqxx::notification_receiver(conn, Name), m_done(false)
|
||||
{}
|
||||
|
||||
void operator()(std::string const &, int be_pid) override
|
||||
{
|
||||
m_done = true;
|
||||
PQXX_CHECK_EQUAL(
|
||||
be_pid, conn().backendpid(),
|
||||
"Got notification from wrong backend process.");
|
||||
|
||||
std::cout << "Received notification: " << channel() << " pid=" << be_pid
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
bool done() const { return m_done; }
|
||||
};
|
||||
|
||||
|
||||
void test_078()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
|
||||
std::string const NotifName{"my listener"};
|
||||
TestListener L{conn, NotifName};
|
||||
|
||||
pqxx::perform([&conn, &L] {
|
||||
pqxx::work tx{conn};
|
||||
tx.exec0("NOTIFY " + tx.quote_name(L.channel()));
|
||||
tx.commit();
|
||||
});
|
||||
|
||||
int notifs{0};
|
||||
for (int i{0}; (i < 20) and not L.done(); ++i)
|
||||
{
|
||||
PQXX_CHECK_EQUAL(notifs, 0, "Got unexpected notifications.");
|
||||
std::cout << ".";
|
||||
notifs = conn.await_notification();
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
PQXX_CHECK(L.done(), "No notification received.");
|
||||
PQXX_CHECK_EQUAL(notifs, 1, "Got unexpected number of notifications.");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_078);
|
||||
@@ -0,0 +1,70 @@
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include <pqxx/notification>
|
||||
#include <pqxx/transaction>
|
||||
#include <pqxx/transactor>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
|
||||
// Example program for libpqxx. Test waiting for notification with timeout.
|
||||
namespace
|
||||
{
|
||||
// Sample implementation of notification receiver.
|
||||
class TestListener final : public pqxx::notification_receiver
|
||||
{
|
||||
bool m_done;
|
||||
|
||||
public:
|
||||
explicit TestListener(pqxx::connection &conn, std::string const &Name) :
|
||||
pqxx::notification_receiver(conn, Name), m_done(false)
|
||||
{}
|
||||
|
||||
void operator()(std::string const &, int be_pid) override
|
||||
{
|
||||
m_done = true;
|
||||
PQXX_CHECK_EQUAL(
|
||||
be_pid, conn().backendpid(), "Notification came from wrong backend.");
|
||||
|
||||
std::cout << "Received notification: " << channel() << " pid=" << be_pid
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
bool done() const { return m_done; }
|
||||
};
|
||||
|
||||
|
||||
void test_079()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
|
||||
std::string const NotifName{"mylistener"};
|
||||
TestListener L(conn, NotifName);
|
||||
|
||||
// First see if the timeout really works: we're not expecting any notifs
|
||||
int notifs{conn.await_notification(0, 1)};
|
||||
PQXX_CHECK_EQUAL(notifs, 0, "Got unexpected notification.");
|
||||
|
||||
pqxx::perform([&conn, &L] {
|
||||
pqxx::work tx{conn};
|
||||
tx.exec0("NOTIFY " + L.channel());
|
||||
tx.commit();
|
||||
});
|
||||
|
||||
for (int i{0}; (i < 20) and not L.done(); ++i)
|
||||
{
|
||||
PQXX_CHECK_EQUAL(notifs, 0, "Got notifications, but no handler called.");
|
||||
std::cout << ".";
|
||||
notifs = conn.await_notification(1, 0);
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
PQXX_CHECK(L.done(), "No notifications received.");
|
||||
PQXX_CHECK_EQUAL(notifs, 1, "Got unexpected notifications.");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_079);
|
||||
@@ -0,0 +1,154 @@
|
||||
#include <iostream>
|
||||
|
||||
#include <pqxx/nontransaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
|
||||
// Test program for libpqxx. Read and print table using row iterators.
|
||||
namespace
|
||||
{
|
||||
void test_082()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::nontransaction tx{conn};
|
||||
|
||||
pqxx::test::create_pqxxevents(tx);
|
||||
std::string const Table{"pqxxevents"};
|
||||
pqxx::result R{tx.exec("SELECT * FROM " + Table)};
|
||||
|
||||
PQXX_CHECK(not std::empty(R), "Got empty result.");
|
||||
|
||||
std::string const nullstr("[null]");
|
||||
|
||||
for (auto const &r : R)
|
||||
{
|
||||
pqxx::row::const_iterator f2(r[0]);
|
||||
for (auto const &f : r)
|
||||
{
|
||||
PQXX_CHECK_EQUAL(
|
||||
(*f2).as(nullstr), f.as(nullstr), "Inconsistent iteration result.");
|
||||
++f2;
|
||||
}
|
||||
|
||||
PQXX_CHECK(
|
||||
std::begin(r) + pqxx::row::difference_type(std::size(r)) == std::end(r),
|
||||
"Row end() appears to be in the wrong place.");
|
||||
PQXX_CHECK(
|
||||
pqxx::row::difference_type(std::size(r)) + std::begin(r) == std::end(r),
|
||||
"Row iterator addition is not commutative.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::begin(r)->num(), 0, "Wrong column number at begin().");
|
||||
|
||||
pqxx::row::const_iterator f3(r[std::size(r)]);
|
||||
|
||||
PQXX_CHECK(f3 == std::end(r), "Did not get end() at end of row.");
|
||||
|
||||
PQXX_CHECK(
|
||||
f3 > std::begin(r), "Row end() appears to precede its begin().");
|
||||
|
||||
PQXX_CHECK(
|
||||
f3 >= std::end(r) and std::begin(r) < f3,
|
||||
"Row iterator operator<() is broken.");
|
||||
|
||||
PQXX_CHECK(f3 > std::begin(r), "Row end() not greater than begin().");
|
||||
|
||||
pqxx::row::const_iterator f4{r, std::size(r)};
|
||||
PQXX_CHECK(f4 == f3, "Row iterator constructor with offset is broken.");
|
||||
|
||||
--f3;
|
||||
f4 -= 1;
|
||||
|
||||
PQXX_CHECK(f3 < std::end(r), "Last field in row is not before end().");
|
||||
PQXX_CHECK(f3 >= std::begin(r), "Last field in row precedes begin().");
|
||||
PQXX_CHECK(
|
||||
f3 == std::end(r) - 1, "Back from end() doese not yield end()-1.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::end(r) - f3, 1, "Wrong distance from last row to end().");
|
||||
|
||||
PQXX_CHECK(f4 == f3, "Row iterator operator-=() is broken.");
|
||||
f4 += 1;
|
||||
PQXX_CHECK(f4 == std::end(r), "Row iterator operator+=() is broken.");
|
||||
|
||||
for (auto fr = std::rbegin(r); fr != std::rend(r); ++fr, --f3)
|
||||
PQXX_CHECK_EQUAL(
|
||||
*fr, *f3,
|
||||
"Reverse traversal is not consistent with forward traversal.");
|
||||
}
|
||||
|
||||
// Thorough test for row::const_reverse_iterator
|
||||
pqxx::row::const_reverse_iterator ri1(std::rbegin(R.front())), ri2(ri1),
|
||||
ri3(std::end(R.front()));
|
||||
ri2 = std::rbegin(R.front());
|
||||
|
||||
PQXX_CHECK(
|
||||
ri1 == ri2, "Copy-constructed reverse_iterator is not equal to original.");
|
||||
|
||||
PQXX_CHECK(ri2 == ri3, "result::end() does not generate rbegin().");
|
||||
PQXX_CHECK_EQUAL(
|
||||
ri2 - ri3, 0,
|
||||
"Distance between identical const_reverse_iterators was nonzero.");
|
||||
|
||||
PQXX_CHECK(
|
||||
pqxx::row::const_reverse_iterator(ri1.base()) == ri1,
|
||||
"Back-conversion of reverse_iterator base() fails.");
|
||||
|
||||
PQXX_CHECK(ri2 == ri3 + 0, "reverse_iterator+0 gives strange result.");
|
||||
PQXX_CHECK(ri2 == ri3 - 0, "reverse_iterator-0 gives strange result.");
|
||||
|
||||
PQXX_CHECK(
|
||||
not(ri3 < ri2),
|
||||
"reverse_iterator operator<() breaks on identical iterators.");
|
||||
PQXX_CHECK(
|
||||
ri2 <= ri3,
|
||||
"reverse_iterator operator<=() breaks on identical iterators.");
|
||||
PQXX_CHECK(ri3++ == ri2, "reverse_iterator post-increment is broken.");
|
||||
|
||||
PQXX_CHECK_EQUAL(ri3 - ri2, 1, "Wrong reverse_iterator distance.");
|
||||
PQXX_CHECK(ri3 > ri2, "reverse_iterator operator>() is broken.");
|
||||
PQXX_CHECK(ri3 >= ri2, "reverse_iterator operator>=() is broken.");
|
||||
PQXX_CHECK(ri2 < ri3, "reverse_iterator operator<() is broken.");
|
||||
PQXX_CHECK(ri2 <= ri3, "reverse_iterator operator<=() is broken.");
|
||||
PQXX_CHECK(ri3 == ri2 + 1, "Adding number to reverse_iterator goes wrong.");
|
||||
PQXX_CHECK(ri2 == ri3 - 1, "Subtracting from reverse_iterator goes wrong.");
|
||||
|
||||
PQXX_CHECK(
|
||||
ri3 == ++ri2, "reverse_iterator pre-incremen returns wrong result.");
|
||||
|
||||
PQXX_CHECK(
|
||||
ri3 >= ri2, "reverse_iterator operator>=() breaks on equal iterators.");
|
||||
PQXX_CHECK(
|
||||
ri3 >= ri2, "reverse_iterator operator<=() breaks on equal iterators.");
|
||||
PQXX_CHECK(
|
||||
*ri3.base() == R.front().back(),
|
||||
"reverse_iterator does not arrive at back().");
|
||||
PQXX_CHECK(
|
||||
ri1->c_str()[0] == (*ri1).c_str()[0],
|
||||
"reverse_iterator operator->() is inconsistent with operator*().");
|
||||
PQXX_CHECK(
|
||||
ri2-- == ri3, "reverse_iterator post-decrement returns wrong result.");
|
||||
PQXX_CHECK(
|
||||
ri2 == --ri3, "reverse_iterator pre-increment returns wrong result.");
|
||||
PQXX_CHECK(
|
||||
ri2 == std::rbegin(R.front()),
|
||||
"Moving iterator back and forth doesn't get it back to origin.");
|
||||
|
||||
ri2 += 1;
|
||||
ri3 -= -1;
|
||||
|
||||
PQXX_CHECK(
|
||||
ri2 != std::rbegin(R.front()), "Adding to reverse_iterator doesn't work.");
|
||||
PQXX_CHECK(
|
||||
ri2 != std::rbegin(R.front()), "Adding to reverse_iterator doesn't work.");
|
||||
PQXX_CHECK(
|
||||
ri3 == ri2, "reverse_iterator operator-=() breaks on negative numbers.");
|
||||
|
||||
ri2 -= 1;
|
||||
PQXX_CHECK(
|
||||
ri2 == std::rbegin(R.front()),
|
||||
"reverse_iterator operator+=() and operator-=() do not cancel out");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_082);
|
||||
@@ -0,0 +1,109 @@
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <pqxx/cursor>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
|
||||
// "Adopted SQL Cursor" test program for libpqxx. Create SQL cursor, wrap it
|
||||
// in a cursor stream, then use it to fetch data and check for consistent
|
||||
// results. Compare results against an icursor_iterator so that is tested as
|
||||
// well.
|
||||
namespace
|
||||
{
|
||||
void test_084()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::transaction<pqxx::serializable> tx{conn};
|
||||
|
||||
std::string const Table{"pg_tables"}, Key{"tablename"};
|
||||
|
||||
// Count rows.
|
||||
pqxx::result R(tx.exec("SELECT count(*) FROM " + Table));
|
||||
|
||||
PQXX_CHECK(
|
||||
R.at(0).at(0).as<long>() > 20,
|
||||
"Not enough rows in " + Table + ", cannot test.");
|
||||
|
||||
// Create an SQL cursor and, for good measure, muddle up its state a bit.
|
||||
std::string const CurName{"MYCUR"},
|
||||
Query{"SELECT * FROM " + Table + " ORDER BY " + Key};
|
||||
constexpr int InitialSkip{2}, GetRows{3};
|
||||
|
||||
tx.exec0("DECLARE " + tx.quote_name(CurName) + " CURSOR FOR " + Query);
|
||||
tx.exec0(
|
||||
"MOVE " + pqxx::to_string(InitialSkip * GetRows) +
|
||||
" "
|
||||
"IN " +
|
||||
tx.quote_name(CurName));
|
||||
|
||||
// Wrap cursor in cursor stream. Apply some trickery to get its name inside
|
||||
// a result field for this purpose. This isn't easy because it's not
|
||||
// supposed to be easy; normally we'd only construct streams around existing
|
||||
// SQL cursors if they were being returned by functions.
|
||||
pqxx::icursorstream C{
|
||||
tx, tx.exec("SELECT '" + tx.esc(CurName) + "'")[0][0], GetRows};
|
||||
|
||||
// Create parallel cursor to check results
|
||||
pqxx::icursorstream C2{tx, Query, "CHECKCUR", GetRows};
|
||||
pqxx::icursor_iterator i2{C2};
|
||||
|
||||
// Remember, our adopted cursor is at position (InitialSkip*GetRows)
|
||||
pqxx::icursor_iterator i3(i2);
|
||||
|
||||
PQXX_CHECK(
|
||||
(i3 == i2) and not(i3 != i2),
|
||||
"Equality on copy-constructed icursor_iterator is broken.");
|
||||
PQXX_CHECK(
|
||||
not(i3 > i2) and not(i3 < i2) and (i3 <= i2) and (i3 >= i2),
|
||||
"Comparison on identical icursor_iterators is broken.");
|
||||
|
||||
i3 += InitialSkip;
|
||||
|
||||
PQXX_CHECK(not(i3 <= i2), "icursor_iterator operator<=() is broken.");
|
||||
|
||||
pqxx::icursor_iterator iend, i4;
|
||||
PQXX_CHECK(i3 != iend, "Early end to icursor_iterator iteration.");
|
||||
i4 = iend;
|
||||
PQXX_CHECK(i4 == iend, "Assigning empty icursor_iterator fails.");
|
||||
|
||||
// Now start testing our new Cursor.
|
||||
C >> R;
|
||||
i2 = i3;
|
||||
pqxx::result R2(*i2++);
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(R), static_cast<pqxx::result::size_type>(GetRows),
|
||||
"Got unexpected number of rows.");
|
||||
|
||||
PQXX_CHECK_EQUAL(R, R2, "Unexpected result at [1]");
|
||||
|
||||
C.get(R);
|
||||
R2 = *i2;
|
||||
PQXX_CHECK_EQUAL(R, R2, "Unexpected result at [2]");
|
||||
i2 += 1;
|
||||
|
||||
C.ignore(GetRows);
|
||||
C.get(R);
|
||||
R2 = *++i2;
|
||||
|
||||
PQXX_CHECK_EQUAL(R, R2, "Unexpected result at [3]");
|
||||
|
||||
++i2;
|
||||
R2 = *i2++;
|
||||
for (int i{1}; C.get(R) and i2 != iend; R2 = *i2++, ++i)
|
||||
PQXX_CHECK_EQUAL(
|
||||
R, R2, "Unexpected result in iteration at " + pqxx::to_string(i));
|
||||
|
||||
PQXX_CHECK(i2 == iend, "Adopted cursor terminated early.");
|
||||
PQXX_CHECK(not(C >> R), "icursor_iterator terminated early.");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_084);
|
||||
@@ -0,0 +1,83 @@
|
||||
#include "pqxx/config-public-compiler.h"
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
|
||||
#include <pqxx/internal/header-pre.hxx>
|
||||
|
||||
#include <pqxx/internal/wait.hxx>
|
||||
|
||||
#include <pqxx/internal/header-post.hxx>
|
||||
|
||||
#include <pqxx/notification>
|
||||
#include <pqxx/transaction>
|
||||
#include <pqxx/transactor>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
|
||||
// Test program for libpqxx. Send notification to self, and wait on the
|
||||
// socket's connection for it to come in. In a simple situation you'd use
|
||||
// connection::await_notification() for this, but that won't let you wait for
|
||||
// multiple sockets.
|
||||
namespace
|
||||
{
|
||||
// Sample implementation of notification receiver.
|
||||
class TestListener final : public pqxx::notification_receiver
|
||||
{
|
||||
bool m_done;
|
||||
|
||||
public:
|
||||
explicit TestListener(pqxx::connection &conn, std::string Name) :
|
||||
pqxx::notification_receiver(conn, Name), m_done(false)
|
||||
{}
|
||||
|
||||
void operator()(std::string const &, int be_pid) override
|
||||
{
|
||||
m_done = true;
|
||||
PQXX_CHECK_EQUAL(
|
||||
be_pid, conn().backendpid(),
|
||||
"Notification came from wrong backend process.");
|
||||
|
||||
std::cout << "Received notification: " << channel() << " pid=" << be_pid
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
bool done() const { return m_done; }
|
||||
};
|
||||
|
||||
|
||||
void test_087()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
|
||||
std::string const NotifName{"my notification"};
|
||||
TestListener L{conn, NotifName};
|
||||
|
||||
pqxx::perform([&conn, &L] {
|
||||
pqxx::work tx{conn};
|
||||
tx.exec0("NOTIFY " + tx.quote_name(L.channel()));
|
||||
tx.commit();
|
||||
});
|
||||
|
||||
int notifs{0};
|
||||
for (int i{0}; (i < 20) and not L.done(); ++i)
|
||||
{
|
||||
PQXX_CHECK_EQUAL(notifs, 0, "Got unexpected notifications.");
|
||||
|
||||
std::cout << ".";
|
||||
|
||||
pqxx::internal::wait_fd(conn.sock(), true, false);
|
||||
notifs = conn.get_notifs();
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
PQXX_CHECK(L.done(), "No notification received.");
|
||||
PQXX_CHECK_EQUAL(notifs, 1, "Got unexpected number of notifications.");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_087);
|
||||
@@ -0,0 +1,91 @@
|
||||
#include <iostream>
|
||||
|
||||
#include <pqxx/subtransaction>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
|
||||
// Test program for libpqxx. Attempt to perform nested transactions.
|
||||
namespace
|
||||
{
|
||||
void test_088()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
|
||||
pqxx::work tx0{conn};
|
||||
pqxx::test::create_pqxxevents(tx0);
|
||||
|
||||
// Trivial test: create subtransactions, and commit/abort
|
||||
std::cout << tx0.exec1("SELECT 'tx0 starts'")[0].c_str() << std::endl;
|
||||
|
||||
pqxx::subtransaction T0a(static_cast<pqxx::dbtransaction &>(tx0), "T0a");
|
||||
T0a.commit();
|
||||
|
||||
pqxx::subtransaction T0b(static_cast<pqxx::dbtransaction &>(tx0), "T0b");
|
||||
T0b.abort();
|
||||
std::cout << tx0.exec1("SELECT 'tx0 ends'")[0].c_str() << std::endl;
|
||||
tx0.commit();
|
||||
|
||||
// Basic functionality: perform query in subtransaction; abort, continue
|
||||
pqxx::work tx1{conn, "tx1"};
|
||||
std::cout << tx1.exec1("SELECT 'tx1 starts'")[0].c_str() << std::endl;
|
||||
pqxx::subtransaction tx1a{tx1, "tx1a"};
|
||||
std::cout << tx1a.exec1("SELECT ' a'")[0].c_str() << std::endl;
|
||||
tx1a.commit();
|
||||
pqxx::subtransaction tx1b{tx1, "tx1b"};
|
||||
std::cout << tx1b.exec1("SELECT ' b'")[0].c_str() << std::endl;
|
||||
tx1b.abort();
|
||||
pqxx::subtransaction tx1c{tx1, "tx1c"};
|
||||
std::cout << tx1c.exec1("SELECT ' c'")[0].c_str() << std::endl;
|
||||
tx1c.commit();
|
||||
std::cout << tx1.exec1("SELECT 'tx1 ends'")[0].c_str() << std::endl;
|
||||
tx1.commit();
|
||||
|
||||
// Commit/rollback functionality
|
||||
pqxx::work tx2{conn, "tx2"};
|
||||
std::string const Table{"test088"};
|
||||
tx2.exec0("CREATE TEMP TABLE " + Table + "(no INTEGER, text VARCHAR)");
|
||||
|
||||
tx2.exec0("INSERT INTO " + Table + " VALUES(1,'tx2')");
|
||||
|
||||
pqxx::subtransaction tx2a{tx2, "tx2a"};
|
||||
tx2a.exec0("INSERT INTO " + Table + " VALUES(2,'tx2a')");
|
||||
tx2a.commit();
|
||||
pqxx::subtransaction tx2b{tx2, "tx2b"};
|
||||
tx2b.exec0("INSERT INTO " + Table + " VALUES(3,'tx2b')");
|
||||
tx2b.abort();
|
||||
pqxx::subtransaction tx2c{tx2, "tx2c"};
|
||||
tx2c.exec0("INSERT INTO " + Table + " VALUES(4,'tx2c')");
|
||||
tx2c.commit();
|
||||
auto const R{tx2.exec("SELECT * FROM " + Table + " ORDER BY no")};
|
||||
for (auto const &i : R)
|
||||
std::cout << '\t' << i[0].c_str() << '\t' << i[1].c_str() << std::endl;
|
||||
|
||||
PQXX_CHECK_EQUAL(std::size(R), 3, "Wrong number of results.");
|
||||
|
||||
int expected[3]{1, 2, 4};
|
||||
for (pqxx::result::size_type n{0}; n < std::size(R); ++n)
|
||||
PQXX_CHECK_EQUAL(
|
||||
R[n][0].as<int>(), expected[n], "Hit unexpected row number.");
|
||||
|
||||
tx2.abort();
|
||||
|
||||
// Auto-abort should only roll back the subtransaction.
|
||||
pqxx::work tx3{conn, "tx3"};
|
||||
pqxx::subtransaction tx3a(tx3, "tx3a");
|
||||
PQXX_CHECK_THROWS(
|
||||
tx3a.exec("SELECT * FROM nonexistent_table WHERE nonattribute=0"),
|
||||
pqxx::sql_error, "Bogus query did not fail.");
|
||||
|
||||
// Subtransaction can only be aborted now, because there was an error.
|
||||
tx3a.abort();
|
||||
// We're back in our top-level transaction. This did not abort.
|
||||
tx3.exec1("SELECT count(*) FROM pqxxevents");
|
||||
// Make sure we can commit exactly one more level of transaction.
|
||||
tx3.commit();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_088);
|
||||
@@ -0,0 +1,44 @@
|
||||
#include <iostream>
|
||||
|
||||
#include <pqxx/subtransaction>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
// Test program for libpqxx. Attempt to perform nested queries on various
|
||||
// types of connections.
|
||||
namespace
|
||||
{
|
||||
void test_089()
|
||||
{
|
||||
pqxx::connection C;
|
||||
|
||||
// Trivial test: create subtransactions, and commit/abort
|
||||
pqxx::work T0(C, "T0");
|
||||
T0.exec1("SELECT 'T0 starts'");
|
||||
pqxx::subtransaction T0a(T0, "T0a");
|
||||
T0a.commit();
|
||||
pqxx::subtransaction T0b(T0, "T0b");
|
||||
T0b.abort();
|
||||
T0.exec1("SELECT 'T0 ends'");
|
||||
T0.commit();
|
||||
|
||||
// Basic functionality: perform query in subtransaction; abort, continue
|
||||
pqxx::work T1(C, "T1");
|
||||
T1.exec1("SELECT 'T1 starts'");
|
||||
pqxx::subtransaction T1a(T1, "T1a");
|
||||
T1a.exec1("SELECT ' a'");
|
||||
T1a.commit();
|
||||
pqxx::subtransaction T1b(T1, "T1b");
|
||||
T1b.exec1("SELECT ' b'");
|
||||
T1b.abort();
|
||||
pqxx::subtransaction T1c(T1, "T1c");
|
||||
T1c.exec1("SELECT ' c'");
|
||||
T1c.commit();
|
||||
T1.exec1("SELECT 'T1 ends'");
|
||||
T1.commit();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_089);
|
||||
@@ -0,0 +1,23 @@
|
||||
#include <pqxx/connection>
|
||||
|
||||
#include "test_helpers.hxx"
|
||||
|
||||
// Test program for libpqxx. Test adorn_name.
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_090()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
|
||||
// Test connection's adorn_name() function for uniqueness
|
||||
std::string const nametest{"basename"};
|
||||
|
||||
PQXX_CHECK_NOT_EQUAL(
|
||||
conn.adorn_name(nametest), conn.adorn_name(nametest),
|
||||
"\"Unique\" names are not unique.");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_090);
|
||||
@@ -0,0 +1,305 @@
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include <pqxx/result>
|
||||
#include <pqxx/row>
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
class test_failure : public std::logic_error
|
||||
{
|
||||
std::string const m_file;
|
||||
int m_line;
|
||||
|
||||
public:
|
||||
test_failure(std::string const &ffile, int fline, std::string const &desc);
|
||||
|
||||
~test_failure() noexcept override;
|
||||
|
||||
std::string const &file() const noexcept { return m_file; }
|
||||
int line() const noexcept { return m_line; }
|
||||
};
|
||||
|
||||
|
||||
/// Drop a table, if it exists.
|
||||
void drop_table(transaction_base &, std::string const &table);
|
||||
|
||||
|
||||
using testfunc = void (*)();
|
||||
|
||||
|
||||
void register_test(char const name[], testfunc func);
|
||||
|
||||
|
||||
/// Register a test while not inside a function.
|
||||
struct registrar
|
||||
{
|
||||
registrar(char const name[], testfunc func)
|
||||
{
|
||||
pqxx::test::register_test(name, func);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Register a test function, so the runner will run it.
|
||||
#define PQXX_REGISTER_TEST(func) \
|
||||
pqxx::test::registrar tst_##func { #func, func }
|
||||
|
||||
|
||||
// Unconditional test failure.
|
||||
#define PQXX_CHECK_NOTREACHED(desc) \
|
||||
pqxx::test::check_notreached(__FILE__, __LINE__, (desc))
|
||||
[[noreturn]] void
|
||||
check_notreached(char const file[], int line, std::string desc);
|
||||
|
||||
// Verify that a condition is met, similar to assert()
|
||||
#define PQXX_CHECK(condition, desc) \
|
||||
pqxx::test::check(__FILE__, __LINE__, (condition), #condition, (desc))
|
||||
void check(
|
||||
char const file[], int line, bool condition, char const text[],
|
||||
std::string const &desc);
|
||||
|
||||
// Verify that variable has the expected value.
|
||||
#define PQXX_CHECK_EQUAL(actual, expected, desc) \
|
||||
pqxx::test::check_equal( \
|
||||
__FILE__, __LINE__, (actual), #actual, (expected), #expected, (desc))
|
||||
template<typename ACTUAL, typename EXPECTED>
|
||||
inline void check_equal(
|
||||
char const file[], int line, ACTUAL actual, char const actual_text[],
|
||||
EXPECTED expected, char const expected_text[], std::string const &desc)
|
||||
{
|
||||
if (expected == actual)
|
||||
return;
|
||||
std::string const fulldesc = desc + " (" + actual_text + " <> " +
|
||||
expected_text +
|
||||
": "
|
||||
"actual=" +
|
||||
to_string(actual) +
|
||||
", "
|
||||
"expected=" +
|
||||
to_string(expected) + ")";
|
||||
throw test_failure(file, line, fulldesc);
|
||||
}
|
||||
|
||||
// Verify that two values are not equal.
|
||||
#define PQXX_CHECK_NOT_EQUAL(value1, value2, desc) \
|
||||
pqxx::test::check_not_equal( \
|
||||
__FILE__, __LINE__, (value1), #value1, (value2), #value2, (desc))
|
||||
template<typename VALUE1, typename VALUE2>
|
||||
inline void check_not_equal(
|
||||
char const file[], int line, VALUE1 value1, char const text1[],
|
||||
VALUE2 value2, char const text2[], std::string const &desc)
|
||||
{
|
||||
if (value1 != value2)
|
||||
return;
|
||||
std::string const fulldesc = desc + " (" + text1 + " == " + text2 +
|
||||
": "
|
||||
"both are " +
|
||||
to_string(value2) + ")";
|
||||
throw test_failure(file, line, fulldesc);
|
||||
}
|
||||
|
||||
|
||||
// Verify that value1 is less than value2.
|
||||
#define PQXX_CHECK_LESS(value1, value2, desc) \
|
||||
pqxx::test::check_less( \
|
||||
__FILE__, __LINE__, (value1), #value1, (value2), #value2, (desc))
|
||||
// Verify that value1 is greater than value2.
|
||||
#define PQXX_CHECK_GREATER(value2, value1, desc) \
|
||||
pqxx::test::check_less( \
|
||||
__FILE__, __LINE__, (value1), #value1, (value2), #value2, (desc))
|
||||
template<typename VALUE1, typename VALUE2>
|
||||
inline void check_less(
|
||||
char const file[], int line, VALUE1 value1, char const text1[],
|
||||
VALUE2 value2, char const text2[], std::string const &desc)
|
||||
{
|
||||
if (value1 < value2)
|
||||
return;
|
||||
std::string const fulldesc = desc + " (" + text1 + " >= " + text2 +
|
||||
": "
|
||||
"\"lower\"=" +
|
||||
to_string(value1) +
|
||||
", "
|
||||
"\"upper\"=" +
|
||||
to_string(value2) + ")";
|
||||
throw test_failure(file, line, fulldesc);
|
||||
}
|
||||
|
||||
|
||||
// Verify that value1 is less than or equal to value2.
|
||||
#define PQXX_CHECK_LESS_EQUAL(value1, value2, desc) \
|
||||
pqxx::test::check_less_equal( \
|
||||
__FILE__, __LINE__, (value1), #value1, (value2), #value2, (desc))
|
||||
// Verify that value1 is greater than or equal to value2.
|
||||
#define PQXX_CHECK_GREATER_EQUAL(value2, value1, desc) \
|
||||
pqxx::test::check_less_equal( \
|
||||
__FILE__, __LINE__, (value1), #value1, (value2), #value2, (desc))
|
||||
template<typename VALUE1, typename VALUE2>
|
||||
inline void check_less_equal(
|
||||
char const file[], int line, VALUE1 value1, char const text1[],
|
||||
VALUE2 value2, char const text2[], std::string const &desc)
|
||||
{
|
||||
if (value1 <= value2)
|
||||
return;
|
||||
std::string const fulldesc = desc + " (" + text1 + " > " + text2 +
|
||||
": "
|
||||
"\"lower\"=" +
|
||||
to_string(value1) +
|
||||
", "
|
||||
"\"upper\"=" +
|
||||
to_string(value2) + ")";
|
||||
throw test_failure(file, line, fulldesc);
|
||||
}
|
||||
|
||||
|
||||
struct failure_to_fail
|
||||
{};
|
||||
|
||||
|
||||
namespace internal
|
||||
{
|
||||
/// Syntactic placeholder: require (and accept) semicolon after block.
|
||||
inline void end_of_statement() {}
|
||||
} // namespace internal
|
||||
|
||||
|
||||
// Verify that "action" does not throw an exception.
|
||||
#define PQXX_CHECK_SUCCEEDS(action, desc) \
|
||||
{ \
|
||||
try \
|
||||
{ \
|
||||
action; \
|
||||
} \
|
||||
catch (std::exception const &e) \
|
||||
{ \
|
||||
PQXX_CHECK_NOTREACHED( \
|
||||
std::string{desc} + " - \"" + \
|
||||
#action "\" threw exception: " + e.what()); \
|
||||
} \
|
||||
catch (...) \
|
||||
{ \
|
||||
PQXX_CHECK_NOTREACHED( \
|
||||
std::string{desc} + " - \"" + #action "\" threw a non-exception!"); \
|
||||
} \
|
||||
} \
|
||||
pqxx::test::internal::end_of_statement()
|
||||
|
||||
// Verify that "action" throws an exception, of any std::exception-based type.
|
||||
#define PQXX_CHECK_THROWS_EXCEPTION(action, desc) \
|
||||
{ \
|
||||
try \
|
||||
{ \
|
||||
action; \
|
||||
throw pqxx::test::failure_to_fail(); \
|
||||
} \
|
||||
catch (pqxx::test::failure_to_fail const &) \
|
||||
{ \
|
||||
PQXX_CHECK_NOTREACHED( \
|
||||
std::string{desc} + " (\"" #action "\" did not throw)"); \
|
||||
} \
|
||||
catch (std::exception const &) \
|
||||
{} \
|
||||
catch (...) \
|
||||
{ \
|
||||
PQXX_CHECK_NOTREACHED( \
|
||||
std::string{desc} + " (\"" #action "\" threw non-exception type)"); \
|
||||
} \
|
||||
} \
|
||||
pqxx::test::internal::end_of_statement()
|
||||
|
||||
// Verify that "action" throws "exception_type" (which is not std::exception).
|
||||
#define PQXX_CHECK_THROWS(action, exception_type, desc) \
|
||||
{ \
|
||||
try \
|
||||
{ \
|
||||
action; \
|
||||
throw pqxx::test::failure_to_fail(); \
|
||||
} \
|
||||
catch (pqxx::test::failure_to_fail const &) \
|
||||
{ \
|
||||
PQXX_CHECK_NOTREACHED( \
|
||||
std::string{desc} + " (\"" #action \
|
||||
"\" did not throw " #exception_type ")"); \
|
||||
} \
|
||||
catch (exception_type const &) \
|
||||
{} \
|
||||
catch (std::exception const &e) \
|
||||
{ \
|
||||
PQXX_CHECK_NOTREACHED( \
|
||||
std::string{desc} + \
|
||||
" (\"" #action \
|
||||
"\" " \
|
||||
"threw exception other than " #exception_type ": " + \
|
||||
e.what() + ")"); \
|
||||
} \
|
||||
catch (...) \
|
||||
{ \
|
||||
PQXX_CHECK_NOTREACHED( \
|
||||
std::string{desc} + " (\"" #action "\" threw non-exception type)"); \
|
||||
} \
|
||||
} \
|
||||
pqxx::test::internal::end_of_statement()
|
||||
|
||||
#define PQXX_CHECK_BOUNDS(value, lower, upper, desc) \
|
||||
pqxx::test::check_bounds( \
|
||||
__FILE__, __LINE__, (value), #value, (lower), #lower, (upper), #upper, \
|
||||
(desc))
|
||||
template<typename VALUE, typename LOWER, typename UPPER>
|
||||
inline void check_bounds(
|
||||
char const file[], int line, VALUE value, char const text[], LOWER lower,
|
||||
char const lower_text[], UPPER upper, char const upper_text[],
|
||||
std::string const &desc)
|
||||
{
|
||||
std::string const range_check = std::string{lower_text} + " < " + upper_text,
|
||||
lower_check =
|
||||
std::string{"!("} + text + " < " + lower_text + ")",
|
||||
upper_check = std::string{text} + " < " + upper_text;
|
||||
|
||||
pqxx::test::check(
|
||||
file, line, lower < upper, range_check.c_str(),
|
||||
desc + " (acceptable range is empty; value was " + text + ")");
|
||||
pqxx::test::check(
|
||||
file, line, not(value < lower), lower_check.c_str(),
|
||||
desc + " (" + text + " is below lower bound " + lower_text + ")");
|
||||
pqxx::test::check(
|
||||
file, line, value < upper, upper_check.c_str(),
|
||||
desc + " (" + text + " is not below upper bound " + upper_text + ")");
|
||||
}
|
||||
|
||||
|
||||
// Report expected exception
|
||||
void expected_exception(std::string const &);
|
||||
|
||||
|
||||
// Represent result row as string.
|
||||
std::string list_row(row);
|
||||
// Represent result as string.
|
||||
std::string list_result(result);
|
||||
// Represent result iterator as string.
|
||||
std::string list_result_iterator(result::const_iterator);
|
||||
|
||||
|
||||
// @deprecated Set up test data for legacy tests.
|
||||
void create_pqxxevents(transaction_base &);
|
||||
} // namespace test
|
||||
|
||||
|
||||
template<> inline std::string to_string(row const &value)
|
||||
{
|
||||
return pqxx::test::list_row(value);
|
||||
}
|
||||
|
||||
|
||||
template<> inline std::string to_string(result const &value)
|
||||
{
|
||||
return pqxx::test::list_result(value);
|
||||
}
|
||||
|
||||
|
||||
template<> inline std::string to_string(result::const_iterator const &value)
|
||||
{
|
||||
return pqxx::test::list_result_iterator(value);
|
||||
}
|
||||
} // namespace pqxx
|
||||
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Custom types for testing & libpqxx support those types
|
||||
*/
|
||||
|
||||
#include <pqxx/strconv>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <iomanip>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
template<> struct nullness<std::byte> : no_null<std::byte>
|
||||
{};
|
||||
|
||||
|
||||
constexpr static auto hex_digit{"0123456789abcdef"};
|
||||
|
||||
|
||||
template<> struct string_traits<std::byte>
|
||||
{
|
||||
static std::size_t size_buffer(std::byte const &) { return 3; }
|
||||
|
||||
static zview to_buf(char *begin, char *end, std::byte const &value)
|
||||
{
|
||||
if (pqxx::internal::cmp_less(end - begin, size_buffer(value)))
|
||||
throw pqxx::conversion_overrun{
|
||||
"Not enough buffer to convert std::byte."};
|
||||
auto uc{static_cast<unsigned char>(value)};
|
||||
begin[0] = hex_digit[uc >> 4];
|
||||
begin[1] = hex_digit[uc & 0x0f];
|
||||
return zview{begin, 2u};
|
||||
}
|
||||
|
||||
static char *into_buf(char *begin, char *end, std::byte const &value)
|
||||
{
|
||||
auto view{to_buf(begin, end, value)};
|
||||
return begin + std::size(view);
|
||||
}
|
||||
};
|
||||
} // namespace pqxx
|
||||
|
||||
|
||||
class ipv4
|
||||
{
|
||||
public:
|
||||
ipv4() : m_as_int{0u} {}
|
||||
ipv4(ipv4 const &) = default;
|
||||
ipv4(ipv4 &&) = default;
|
||||
explicit ipv4(uint32_t i) : m_as_int{i} {}
|
||||
ipv4(
|
||||
unsigned char b1, unsigned char b2, unsigned char b3, unsigned char b4) :
|
||||
ipv4()
|
||||
{
|
||||
set_byte(0, b1);
|
||||
set_byte(1, b2);
|
||||
set_byte(2, b3);
|
||||
set_byte(3, b4);
|
||||
}
|
||||
|
||||
bool operator==(ipv4 const &o) const { return m_as_int == o.m_as_int; }
|
||||
ipv4 &operator=(ipv4 const &) = default;
|
||||
|
||||
/// Index bytes, from 0 to 3, in network (i.e. Big-Endian) byte order.
|
||||
unsigned int operator[](int byte) const
|
||||
{
|
||||
if (byte < 0 or byte > 3)
|
||||
throw pqxx::usage_error("Byte out of range.");
|
||||
auto const shift = compute_shift(byte);
|
||||
return static_cast<unsigned int>((m_as_int >> shift) & 0xff);
|
||||
}
|
||||
|
||||
/// Set individual byte, in network byte order.
|
||||
void set_byte(int byte, uint32_t value)
|
||||
{
|
||||
auto const shift = compute_shift(byte);
|
||||
auto const blanked = (m_as_int & ~uint32_t(0xff << shift));
|
||||
m_as_int = (blanked | ((value & 0xff) << shift));
|
||||
}
|
||||
|
||||
private:
|
||||
static unsigned compute_shift(int byte)
|
||||
{
|
||||
if (byte < 0 or byte > 3)
|
||||
throw pqxx::usage_error("Byte out of range.");
|
||||
return static_cast<unsigned>((3 - byte) * 8);
|
||||
}
|
||||
|
||||
uint32_t m_as_int;
|
||||
};
|
||||
|
||||
|
||||
using bytea = std::vector<unsigned char>;
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
template<> struct nullness<ipv4> : no_null<ipv4>
|
||||
{};
|
||||
|
||||
|
||||
template<> struct string_traits<ipv4>
|
||||
{
|
||||
static ipv4 from_string(std::string_view text)
|
||||
{
|
||||
ipv4 ts;
|
||||
if (std::data(text) == nullptr)
|
||||
internal::throw_null_conversion(type_name<ipv4>);
|
||||
std::regex ipv4_regex{R"--((\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3}))--"};
|
||||
std::smatch match;
|
||||
// Need non-temporary for `std::regex_match()`
|
||||
std::string sstr{text};
|
||||
if (not std::regex_match(sstr, match, ipv4_regex) or std::size(match) != 5)
|
||||
throw std::runtime_error{"Invalid ipv4 format: " + std::string{text}};
|
||||
try
|
||||
{
|
||||
for (std::size_t i{0}; i < 4; ++i)
|
||||
ts.set_byte(int(i), uint32_t(std::stoi(match[i + 1])));
|
||||
}
|
||||
catch (std::invalid_argument const &)
|
||||
{
|
||||
throw std::runtime_error{"Invalid ipv4 format: " + std::string{text}};
|
||||
}
|
||||
catch (std::out_of_range const &)
|
||||
{
|
||||
throw std::runtime_error{"Invalid ipv4 format: " + std::string{text}};
|
||||
}
|
||||
return ts;
|
||||
}
|
||||
|
||||
static char *into_buf(char *begin, char *end, ipv4 const &value)
|
||||
{
|
||||
if (pqxx::internal::cmp_less(end - begin, size_buffer(value)))
|
||||
throw conversion_error{"Buffer too small for ipv4."};
|
||||
char *here = begin;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
here = string_traits<unsigned>::into_buf(here, end, value[i]);
|
||||
*(here - 1) = '.';
|
||||
}
|
||||
*(here - 1) = '\0';
|
||||
return here;
|
||||
}
|
||||
|
||||
static zview to_buf(char *begin, char *end, ipv4 const &value)
|
||||
{
|
||||
return zview{
|
||||
begin,
|
||||
static_cast<std::size_t>(into_buf(begin, end, value) - begin - 1)};
|
||||
}
|
||||
|
||||
static constexpr std::size_t size_buffer(ipv4 const &) noexcept
|
||||
{
|
||||
return 20;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
inline char nibble_to_hex(unsigned nibble)
|
||||
{
|
||||
if (nibble < 10)
|
||||
return char('0' + nibble);
|
||||
else if (nibble < 16)
|
||||
return char('a' + (nibble - 10));
|
||||
else
|
||||
throw std::runtime_error{"Invalid digit going into bytea."};
|
||||
}
|
||||
|
||||
|
||||
inline unsigned hex_to_digit(char hex)
|
||||
{
|
||||
auto x = static_cast<unsigned char>(hex);
|
||||
if (x >= '0' and x <= '9')
|
||||
return x - '0';
|
||||
else if (x >= 'a' and x <= 'f')
|
||||
return 10 + x - 'a';
|
||||
else if (x >= 'A' and x <= 'F')
|
||||
return 10 + x - 'A';
|
||||
else
|
||||
throw std::runtime_error{"Invalid hex in bytea."};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
template<> struct nullness<bytea> : no_null<bytea>
|
||||
{};
|
||||
|
||||
|
||||
template<> struct string_traits<bytea>
|
||||
{
|
||||
static bytea from_string(std::string_view text)
|
||||
{
|
||||
if ((std::size(text) & 1) != 0)
|
||||
throw std::runtime_error{"Odd hex size."};
|
||||
bytea value;
|
||||
value.reserve((std::size(text) - 2) / 2);
|
||||
for (std::size_t i = 2; i < std::size(text); i += 2)
|
||||
{
|
||||
auto hi = hex_to_digit(text[i]), lo = hex_to_digit(text[i + 1]);
|
||||
value.push_back(static_cast<unsigned char>((hi << 4) | lo));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static zview to_buf(char *begin, char *end, bytea const &value)
|
||||
{
|
||||
auto const need = size_buffer(value);
|
||||
auto const have = end - begin;
|
||||
if (std::size_t(have) < need)
|
||||
throw pqxx::conversion_overrun{"Not enough space in buffer for bytea."};
|
||||
char *pos = begin;
|
||||
*pos++ = '\\';
|
||||
*pos++ = 'x';
|
||||
for (unsigned char const u : value)
|
||||
{
|
||||
*pos++ = nibble_to_hex(unsigned(u) >> 4);
|
||||
*pos++ = nibble_to_hex(unsigned(u) & 0x0f);
|
||||
}
|
||||
*pos++ = '\0';
|
||||
return {begin, pos - begin - 1};
|
||||
}
|
||||
|
||||
static char *into_buf(char *begin, char *end, bytea const &value)
|
||||
{
|
||||
return begin + std::size(to_buf(begin, end, value)) + 1;
|
||||
}
|
||||
|
||||
static std::size_t size_buffer(bytea const &value)
|
||||
{
|
||||
return 2 + 2 * std::size(value) + 1;
|
||||
}
|
||||
};
|
||||
} // namespace pqxx
|
||||
@@ -0,0 +1,25 @@
|
||||
if(NOT PostgreSQL_FOUND)
|
||||
if(POLICY CMP0074)
|
||||
cmake_policy(PUSH)
|
||||
# CMP0074 is `OLD` by `cmake_minimum_required(VERSION 3.7)`, sets `NEW`
|
||||
# to enable support CMake variable `PostgreSQL_ROOT`.
|
||||
cmake_policy(SET CMP0074 NEW)
|
||||
endif()
|
||||
|
||||
find_package(PostgreSQL REQUIRED)
|
||||
|
||||
if(POLICY CMP0074)
|
||||
cmake_policy(POP)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
file(GLOB UNIT_TEST_SOURCES *.cxx)
|
||||
|
||||
add_executable(unit_runner ${UNIT_TEST_SOURCES})
|
||||
target_link_libraries(unit_runner PUBLIC pqxx)
|
||||
target_include_directories(unit_runner PRIVATE ${PostgreSQL_INCLUDE_DIRS})
|
||||
add_test(
|
||||
NAME unit_runner
|
||||
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
|
||||
COMMAND unit_runner
|
||||
)
|
||||
@@ -0,0 +1,548 @@
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
// Test program for libpqxx array parsing.
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
template<>
|
||||
struct nullness<array_parser::juncture> : no_null<array_parser::juncture>
|
||||
{};
|
||||
|
||||
|
||||
inline std::string to_string(pqxx::array_parser::juncture const &j)
|
||||
{
|
||||
using junc = pqxx::array_parser::juncture;
|
||||
switch (j)
|
||||
{
|
||||
case junc::row_start: return "row_start";
|
||||
case junc::row_end: return "row_end";
|
||||
case junc::null_value: return "null_value";
|
||||
case junc::string_value: return "string_value";
|
||||
case junc::done: return "done";
|
||||
default: return "UNKNOWN JUNCTURE: " + to_string(static_cast<int>(j));
|
||||
}
|
||||
}
|
||||
} // namespace pqxx
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_empty_arrays()
|
||||
{
|
||||
std::pair<pqxx::array_parser::juncture, std::string> output;
|
||||
|
||||
// Parsing a null pointer just immediately returns "done".
|
||||
output = pqxx::array_parser(std::string_view()).get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::done,
|
||||
"get_next on null array did not return done.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
|
||||
// Parsing an empty array string immediately returns "done".
|
||||
output = pqxx::array_parser("").get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::done,
|
||||
"get_next on an empty array string did not return done.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
|
||||
// Parsing an empty array returns "row_start", "row_end", "done".
|
||||
pqxx::array_parser empty_parser("{}");
|
||||
output = empty_parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_start,
|
||||
"Empty array did not start with row_start.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
|
||||
output = empty_parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_end,
|
||||
"Empty array did not end with row_end.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
|
||||
output = empty_parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::done,
|
||||
"Empty array did not conclude with done.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
}
|
||||
|
||||
|
||||
void test_null_value()
|
||||
{
|
||||
std::pair<pqxx::array_parser::juncture, std::string> output;
|
||||
pqxx::array_parser containing_null("{NULL}");
|
||||
|
||||
output = containing_null.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_start,
|
||||
"Array containing null did not start with row_start.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
|
||||
output = containing_null.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::null_value,
|
||||
"Array containing null did not return null_value.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Null value was not empty.");
|
||||
|
||||
output = containing_null.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_end,
|
||||
"Array containing null did not end with row_end.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
|
||||
output = containing_null.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::done,
|
||||
"Array containing null did not conclude with done.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
}
|
||||
|
||||
|
||||
void test_single_quoted_string()
|
||||
{
|
||||
std::pair<pqxx::array_parser::juncture, std::string> output;
|
||||
pqxx::array_parser parser("{'item'}");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_start,
|
||||
"Array did not start with row_start.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::string_value,
|
||||
"Array did not return string_value.");
|
||||
PQXX_CHECK_EQUAL(output.second, "item", "Unexpected string value.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_end,
|
||||
"Array did not end with row_end.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::done,
|
||||
"Array did not conclude with done.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
}
|
||||
|
||||
|
||||
void test_single_quoted_escaping()
|
||||
{
|
||||
std::pair<pqxx::array_parser::juncture, std::string> output;
|
||||
pqxx::array_parser parser("{'don''t\\\\ care'}");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_start,
|
||||
"Array did not start with row_start.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::string_value,
|
||||
"Array did not return string_value.");
|
||||
PQXX_CHECK_EQUAL(output.second, "don't\\ care", "Unexpected string value.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_end,
|
||||
"Array did not end with row_end.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::done,
|
||||
"Array did not conclude with done.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
}
|
||||
|
||||
|
||||
void test_double_quoted_string()
|
||||
{
|
||||
std::pair<pqxx::array_parser::juncture, std::string> output;
|
||||
pqxx::array_parser parser("{\"item\"}");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_start,
|
||||
"Array did not start with row_start.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::string_value,
|
||||
"Array did not return string_value.");
|
||||
PQXX_CHECK_EQUAL(output.second, "item", "Unexpected string value.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_end,
|
||||
"Array did not end with row_end.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::done,
|
||||
"Array did not conclude with done.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
}
|
||||
|
||||
|
||||
void test_double_quoted_escaping()
|
||||
{
|
||||
std::pair<pqxx::array_parser::juncture, std::string> output;
|
||||
pqxx::array_parser parser(R"--({"don''t\\ care"})--");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_start,
|
||||
"Array did not start with row_start.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::string_value,
|
||||
"Array did not return string_value.");
|
||||
PQXX_CHECK_EQUAL(output.second, "don''t\\ care", "Unexpected string value.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_end,
|
||||
"Array did not end with row_end.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::done,
|
||||
"Array did not conclude with done.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
}
|
||||
|
||||
|
||||
// A pair of double quotes in a double-quoted string is an escaped quote.
|
||||
void test_double_double_quoted_string()
|
||||
{
|
||||
std::pair<pqxx::array_parser::juncture, std::string> output;
|
||||
pqxx::array_parser parser{R"--({"3"" steel"})--"};
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_start,
|
||||
"Array did not start with row_start.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::string_value,
|
||||
"Array did not return string_value.");
|
||||
|
||||
PQXX_CHECK_EQUAL(output.second, "3\" steel", "Unexpected string value.");
|
||||
}
|
||||
|
||||
|
||||
void test_unquoted_string()
|
||||
{
|
||||
std::pair<pqxx::array_parser::juncture, std::string> output;
|
||||
pqxx::array_parser parser("{item}");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_start,
|
||||
"Array did not start with row_start.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::string_value,
|
||||
"Array did not return string_value.");
|
||||
PQXX_CHECK_EQUAL(output.second, "item", "Unexpected string value.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_end,
|
||||
"Array did not end with row_end.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::done,
|
||||
"Array did not conclude with done.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
}
|
||||
|
||||
|
||||
void test_multiple_values()
|
||||
{
|
||||
std::pair<pqxx::array_parser::juncture, std::string> output;
|
||||
pqxx::array_parser parser("{1,2}");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_start,
|
||||
"Array did not start with row_start.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::string_value,
|
||||
"Array did not return string_value.");
|
||||
PQXX_CHECK_EQUAL(output.second, "1", "Unexpected string value.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::string_value,
|
||||
"Array did not return string_value.");
|
||||
PQXX_CHECK_EQUAL(output.second, "2", "Unexpected string value.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_end,
|
||||
"Array did not end with row_end.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::done,
|
||||
"Array did not conclude with done.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
}
|
||||
|
||||
|
||||
void test_nested_array()
|
||||
{
|
||||
std::pair<pqxx::array_parser::juncture, std::string> output;
|
||||
pqxx::array_parser parser("{{item}}");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_start,
|
||||
"Array did not start with row_start.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_start,
|
||||
"Nested array did not start 2nd dimension with row_start.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::string_value,
|
||||
"Array did not return string_value.");
|
||||
PQXX_CHECK_EQUAL(output.second, "item", "Unexpected string value.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_end,
|
||||
"Nested array did not end 2nd dimension with row_end.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_end,
|
||||
"Array did not end with row_end.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::done,
|
||||
"Array did not conclude with done.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
}
|
||||
|
||||
|
||||
void test_nested_array_with_multiple_entries()
|
||||
{
|
||||
std::pair<pqxx::array_parser::juncture, std::string> output;
|
||||
pqxx::array_parser parser("{{1,2},{3,4}}");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_start,
|
||||
"Array did not start with row_start.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_start,
|
||||
"Nested array did not start 2nd dimension with row_start.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::string_value,
|
||||
"Array did not return string_value.");
|
||||
PQXX_CHECK_EQUAL(output.second, "1", "Unexpected string value.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::string_value,
|
||||
"Array did not return string_value.");
|
||||
PQXX_CHECK_EQUAL(output.second, "2", "Unexpected string value.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_end,
|
||||
"Nested array did not end 2nd dimension with row_end.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_start,
|
||||
"Nested array did not descend to 2nd dimension with row_start.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::string_value,
|
||||
"Array did not return string_value.");
|
||||
PQXX_CHECK_EQUAL(output.second, "3", "Unexpected string value.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::string_value,
|
||||
"Array did not return string_value.");
|
||||
PQXX_CHECK_EQUAL(output.second, "4", "Unexpected string value.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_end,
|
||||
"Nested array did not leave 2nd dimension with row_end.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::row_end,
|
||||
"Array did not end with row_end.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
|
||||
output = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
output.first, pqxx::array_parser::juncture::done,
|
||||
"Array did not conclude with done.");
|
||||
PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output.");
|
||||
}
|
||||
|
||||
|
||||
void test_array_parse()
|
||||
{
|
||||
test_empty_arrays();
|
||||
test_null_value();
|
||||
test_single_quoted_string();
|
||||
test_single_quoted_escaping();
|
||||
test_double_quoted_string();
|
||||
test_double_quoted_escaping();
|
||||
test_double_double_quoted_string();
|
||||
test_unquoted_string();
|
||||
test_multiple_values();
|
||||
test_nested_array();
|
||||
test_nested_array_with_multiple_entries();
|
||||
}
|
||||
|
||||
|
||||
void test_generate_empty_array()
|
||||
{
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(std::vector<int>{}), "{}",
|
||||
"Basic array output is not as expected.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(std::vector<std::string>{}), "{}",
|
||||
"String array comes out different.");
|
||||
}
|
||||
|
||||
|
||||
void test_generate_null_value()
|
||||
{
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(std::vector<char const *>{nullptr}), "{NULL}",
|
||||
"Null array value did not come out as expected.");
|
||||
}
|
||||
|
||||
|
||||
void test_generate_single_item()
|
||||
{
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(std::vector<int>{42}), "{42}",
|
||||
"Numeric conversion came out wrong.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(std::vector<char const *>{"foo"}), "{\"foo\"}",
|
||||
"String array conversion came out wrong.");
|
||||
}
|
||||
|
||||
|
||||
void test_generate_multiple_items()
|
||||
{
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(std::vector<int>{5, 4, 3, 2}), "{5,4,3,2}",
|
||||
"Array with multiple values is not correct.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(std::vector<std::string>{"foo", "bar"}),
|
||||
"{\"foo\",\"bar\"}", "Array with multiple strings came out wrong.");
|
||||
}
|
||||
|
||||
|
||||
void test_generate_nested_array()
|
||||
{
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(std::vector<std::vector<int>>{{1, 2}, {3, 4}}),
|
||||
"{{1,2},{3,4}}", "Nested arrays don't work right.");
|
||||
}
|
||||
|
||||
|
||||
void test_generate_escaped_strings()
|
||||
{
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(std::vector<std::string>{"a\\b"}), "{\"a\\\\b\"}",
|
||||
"Backslashes are not escaped properly.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(std::vector<std::string>{"x\"y\""}), "{\"x\\\"y\\\"\"}",
|
||||
"Double quotes are not escaped properly.");
|
||||
}
|
||||
|
||||
|
||||
void test_array_generate()
|
||||
{
|
||||
test_generate_empty_array();
|
||||
test_generate_null_value();
|
||||
test_generate_single_item();
|
||||
test_generate_multiple_items();
|
||||
test_generate_nested_array();
|
||||
test_generate_escaped_strings();
|
||||
}
|
||||
|
||||
|
||||
void test_array_roundtrip()
|
||||
{
|
||||
pqxx::connection c;
|
||||
pqxx::work w{c};
|
||||
|
||||
std::vector<int> const in{0, 1, 2, 3, 5};
|
||||
auto const r1{w.exec1("SELECT " + c.quote(in) + "::integer[]")};
|
||||
pqxx::array_parser parser{r1[0].view()};
|
||||
auto item{parser.get_next()};
|
||||
PQXX_CHECK_EQUAL(
|
||||
item.first, pqxx::array_parser::juncture::row_start,
|
||||
"Array did not start with row_start.");
|
||||
|
||||
std::vector<int> out;
|
||||
for (item = parser.get_next();
|
||||
item.first == pqxx::array_parser::juncture::string_value;
|
||||
item = parser.get_next())
|
||||
{
|
||||
out.push_back(pqxx::from_string<int>(item.second));
|
||||
}
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
item.first, pqxx::array_parser::juncture::row_end,
|
||||
"Array values did not end in row_end.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(out), std::size(in), "Array came back with different length.");
|
||||
|
||||
for (std::size_t i{0}; i < std::size(in); ++i)
|
||||
PQXX_CHECK_EQUAL(out[i], in[i], "Array element has changed.");
|
||||
|
||||
item = parser.get_next();
|
||||
PQXX_CHECK_EQUAL(
|
||||
item.first, pqxx::array_parser::juncture::done,
|
||||
"Array did not end in done.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_array_parse);
|
||||
PQXX_REGISTER_TEST(test_array_generate);
|
||||
PQXX_REGISTER_TEST(test_array_roundtrip);
|
||||
} // namespace
|
||||
@@ -0,0 +1,211 @@
|
||||
#include <pqxx/binarystring>
|
||||
#include <pqxx/stream_to>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
#include "../test_types.hxx"
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
pqxx::binarystring
|
||||
make_binarystring(pqxx::transaction_base &T, std::string content)
|
||||
{
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
return pqxx::binarystring(T.exec1("SELECT " + T.quote_raw(content))[0]);
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
}
|
||||
|
||||
|
||||
void test_binarystring()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
auto b{make_binarystring(tx, "")};
|
||||
PQXX_CHECK(std::empty(b), "Empty binarystring is not empty.");
|
||||
PQXX_CHECK_EQUAL(b.str(), "", "Empty binarystring doesn't work.");
|
||||
PQXX_CHECK_EQUAL(std::size(b), 0u, "Empty binarystring has nonzero size.");
|
||||
PQXX_CHECK_EQUAL(b.length(), 0u, "Length/size mismatch.");
|
||||
PQXX_CHECK(std::begin(b) == std::end(b), "Empty binarystring iterates.");
|
||||
PQXX_CHECK(
|
||||
std::cbegin(b) == std::begin(b), "Wrong cbegin for empty binarystring.");
|
||||
PQXX_CHECK(
|
||||
std::rbegin(b) == std::rend(b), "Empty binarystring reverse-iterates.");
|
||||
PQXX_CHECK(
|
||||
std::crbegin(b) == std::rbegin(b),
|
||||
"Wrong crbegin for empty binarystring.");
|
||||
PQXX_CHECK_THROWS(
|
||||
b.at(0), std::out_of_range, "Empty binarystring accepts at().");
|
||||
|
||||
b = make_binarystring(tx, "z");
|
||||
PQXX_CHECK_EQUAL(b.str(), "z", "Basic nonempty binarystring is broken.");
|
||||
PQXX_CHECK(not std::empty(b), "Nonempty binarystring is empty.");
|
||||
PQXX_CHECK_EQUAL(std::size(b), 1u, "Bad binarystring size.");
|
||||
PQXX_CHECK_EQUAL(b.length(), 1u, "Length/size mismatch.");
|
||||
PQXX_CHECK(
|
||||
std::begin(b) != std::end(b), "Nonempty binarystring does not iterate.");
|
||||
PQXX_CHECK(
|
||||
std::rbegin(b) != std::rend(b),
|
||||
"Nonempty binarystring does not reverse-iterate.");
|
||||
PQXX_CHECK(std::begin(b) + 1 == std::end(b), "Bad iteration.");
|
||||
PQXX_CHECK(std::rbegin(b) + 1 == std::rend(b), "Bad reverse iteration.");
|
||||
PQXX_CHECK(std::cbegin(b) == std::begin(b), "Wrong cbegin.");
|
||||
PQXX_CHECK(std::cend(b) == std::end(b), "Wrong cend.");
|
||||
PQXX_CHECK(std::crbegin(b) == std::rbegin(b), "Wrong crbegin.");
|
||||
PQXX_CHECK(std::crend(b) == std::rend(b), "Wrong crend.");
|
||||
PQXX_CHECK(b.front() == 'z', "Unexpected front().");
|
||||
PQXX_CHECK(b.back() == 'z', "Unexpected back().");
|
||||
PQXX_CHECK(b.at(0) == 'z', "Unexpected data at index 0.");
|
||||
PQXX_CHECK_THROWS(
|
||||
b.at(1), std::out_of_range, "Failed to catch range error.");
|
||||
|
||||
std::string const simple{"ab"};
|
||||
b = make_binarystring(tx, simple);
|
||||
PQXX_CHECK_EQUAL(
|
||||
b.str(), simple, "Binary (un)escaping went wrong somewhere.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(b), std::size(simple), "Escaping confuses length.");
|
||||
|
||||
std::string const simple_escaped{
|
||||
tx.esc_raw(std::basic_string_view<std::byte>{
|
||||
reinterpret_cast<std::byte const *>(std::data(simple)),
|
||||
std::size(simple)})};
|
||||
for (auto c : simple_escaped)
|
||||
{
|
||||
auto const uc{static_cast<unsigned char>(c)};
|
||||
PQXX_CHECK(uc <= 127, "Non-ASCII byte in escaped string.");
|
||||
}
|
||||
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
PQXX_CHECK_EQUAL(
|
||||
tx.quote_raw(
|
||||
reinterpret_cast<unsigned char const *>(simple.c_str()),
|
||||
std::size(simple)),
|
||||
tx.quote(b), "quote_raw is broken");
|
||||
PQXX_CHECK_EQUAL(
|
||||
tx.quote(b), tx.quote_raw(simple), "Binary quoting is broken.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::binarystring(tx.exec1("SELECT " + tx.quote(b))[0]).str(), simple,
|
||||
"Binary string is not idempotent.");
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
|
||||
std::string const bytes("\x01\x23\x23\xa1\x2b\x0c\xff");
|
||||
b = make_binarystring(tx, bytes);
|
||||
PQXX_CHECK_EQUAL(b.str(), bytes, "Binary data breaks (un)escaping.");
|
||||
|
||||
std::string const nully("a\0b", 3);
|
||||
b = make_binarystring(tx, nully);
|
||||
PQXX_CHECK_EQUAL(b.str(), nully, "Nul byte broke binary (un)escaping.");
|
||||
PQXX_CHECK_EQUAL(std::size(b), 3u, "Nul byte broke binarystring size.");
|
||||
|
||||
b = make_binarystring(tx, "foo");
|
||||
PQXX_CHECK_EQUAL(std::string(b.get(), 3), "foo", "get() appears broken.");
|
||||
|
||||
auto b1{make_binarystring(tx, "1")}, b2{make_binarystring(tx, "2")};
|
||||
PQXX_CHECK_NOT_EQUAL(b1.get(), b2.get(), "Madness rules.");
|
||||
PQXX_CHECK_NOT_EQUAL(b1.str(), b2.str(), "Logic has no more meaning.");
|
||||
b1.swap(b2);
|
||||
PQXX_CHECK_NOT_EQUAL(b1.str(), b2.str(), "swap() equalized binarystrings.");
|
||||
PQXX_CHECK_NOT_EQUAL(b1.str(), "1", "swap() did not happen.");
|
||||
PQXX_CHECK_EQUAL(b1.str(), "2", "swap() is broken.");
|
||||
PQXX_CHECK_EQUAL(b2.str(), "1", "swap() went insane.");
|
||||
|
||||
b = make_binarystring(tx, "bar");
|
||||
b.swap(b);
|
||||
PQXX_CHECK_EQUAL(b.str(), "bar", "Self-swap confuses binarystring.");
|
||||
|
||||
b = make_binarystring(tx, "\\x");
|
||||
PQXX_CHECK_EQUAL(b.str(), "\\x", "Hex-escape header confused (un)escaping.");
|
||||
}
|
||||
|
||||
|
||||
void test_binarystring_conversion()
|
||||
{
|
||||
constexpr char bytes[]{"f\to\0o\n\0"};
|
||||
std::string_view const data{bytes, std::size(bytes) - 1};
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
pqxx::binarystring bin{data};
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
auto const escaped{pqxx::to_string(bin)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
escaped, std::string_view{"\\x66096f006f0a00"}, "Unexpected hex escape.");
|
||||
auto const restored{pqxx::from_string<pqxx::binarystring>(escaped)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(restored), std::size(data), "Unescaping produced wrong length.");
|
||||
}
|
||||
|
||||
|
||||
void test_binarystring_stream()
|
||||
{
|
||||
constexpr char bytes[]{"a\tb\0c"};
|
||||
std::string_view const data{bytes, std::size(bytes) - 1};
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
pqxx::binarystring bin{data};
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
|
||||
pqxx::connection conn;
|
||||
pqxx::transaction tx{conn};
|
||||
tx.exec0("CREATE TEMP TABLE pqxxbinstream(id integer, bin bytea)");
|
||||
|
||||
auto to{pqxx::stream_to::table(tx, {"pqxxbinstream"})};
|
||||
to.write_values(0, bin);
|
||||
to.complete();
|
||||
|
||||
auto ptr{reinterpret_cast<std::byte const *>(std::data(data))};
|
||||
auto expect{
|
||||
tx.quote(std::basic_string_view<std::byte>{ptr, std::size(data)})};
|
||||
PQXX_CHECK(
|
||||
tx.query_value<bool>("SELECT bin = " + expect + " FROM pqxxbinstream"),
|
||||
"binarystring did not stream_to properly.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
tx.query_value<std::size_t>("SELECT octet_length(bin) FROM pqxxbinstream"),
|
||||
std::size(data), "Did the terminating zero break the bytea?");
|
||||
}
|
||||
|
||||
|
||||
void test_binarystring_array_stream()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::transaction tx{conn};
|
||||
tx.exec0("CREATE TEMP TABLE pqxxbinstream(id integer, vec bytea[])");
|
||||
|
||||
constexpr char bytes1[]{"a\tb\0c"}, bytes2[]{"1\0.2"};
|
||||
std::string_view const data1{bytes1}, data2{bytes2};
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
pqxx::binarystring bin1{data1}, bin2{data2};
|
||||
std::vector<pqxx::binarystring> const vec{bin1, bin2};
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
|
||||
auto to{pqxx::stream_to::table(tx, {"pqxxbinstream"})};
|
||||
to.write_values(0, vec);
|
||||
to.complete();
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
tx.query_value<std::size_t>(
|
||||
"SELECT array_length(vec, 1) FROM pqxxbinstream"),
|
||||
std::size(vec), "Array came out with wrong length.");
|
||||
|
||||
auto ptr1{reinterpret_cast<std::byte const *>(std::data(data1))},
|
||||
ptr2{reinterpret_cast<std::byte const *>(std::data(data2))};
|
||||
auto expect1{
|
||||
tx.quote(std::basic_string_view<std::byte>{ptr1, std::size(data1)})},
|
||||
expect2{
|
||||
tx.quote(std::basic_string_view<std::byte>{ptr2, std::size(data2)})};
|
||||
PQXX_CHECK(
|
||||
tx.query_value<bool>("SELECT vec[1] = " + expect1 + " FROM pqxxbinstream"),
|
||||
"Bytea in array came out wrong.");
|
||||
PQXX_CHECK(
|
||||
tx.query_value<bool>("SELECT vec[2] = " + expect2 + " FROM pqxxbinstream"),
|
||||
"First bytea in array worked, but second did not.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
tx.query_value<std::size_t>(
|
||||
"SELECT octet_length(vec[1]) FROM pqxxbinstream"),
|
||||
std::size(data1), "Bytea length broke inside array.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_binarystring);
|
||||
PQXX_REGISTER_TEST(test_binarystring_conversion);
|
||||
PQXX_REGISTER_TEST(test_binarystring_stream);
|
||||
PQXX_REGISTER_TEST(test_binarystring_array_stream);
|
||||
} // namespace
|
||||
@@ -0,0 +1,644 @@
|
||||
#include <cstdio>
|
||||
|
||||
#include <pqxx/blob>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
#include "../test_types.hxx"
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_blob_is_useless_by_default()
|
||||
{
|
||||
pqxx::blob b{};
|
||||
std::basic_string<std::byte> buf;
|
||||
PQXX_CHECK_THROWS(
|
||||
b.read(buf, 1), pqxx::usage_error,
|
||||
"Read on default-constructed blob did not throw failure.");
|
||||
PQXX_CHECK_THROWS(
|
||||
b.write(buf), pqxx::usage_error,
|
||||
"Write on default-constructed blob did not throw failure.");
|
||||
}
|
||||
|
||||
|
||||
void test_blob_create_makes_empty_blob()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
pqxx::oid id{pqxx::blob::create(tx)};
|
||||
auto b{pqxx::blob::open_r(tx, id)};
|
||||
b.seek_end(0);
|
||||
PQXX_CHECK_EQUAL(b.tell(), 0, "New blob is not empty.");
|
||||
}
|
||||
|
||||
|
||||
void test_blob_create_with_oid_requires_oid_be_free()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
auto id{pqxx::blob::create(tx)};
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::ignore_unused(pqxx::blob::create(tx, id)), pqxx::failure,
|
||||
"Not getting expected error when oid not free.");
|
||||
}
|
||||
|
||||
|
||||
void test_blob_create_with_oid_obeys_oid()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
auto id{pqxx::blob::create(tx)};
|
||||
pqxx::blob::remove(tx, id);
|
||||
|
||||
auto actual_id{pqxx::blob::create(tx, id)};
|
||||
PQXX_CHECK_EQUAL(actual_id, id, "Create with oid returned different oid.");
|
||||
}
|
||||
|
||||
|
||||
void test_blobs_are_transactional()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
pqxx::oid id{pqxx::blob::create(tx)};
|
||||
tx.abort();
|
||||
pqxx::work tx2{conn};
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::ignore_unused(pqxx::blob::open_r(tx2, id)), pqxx::failure,
|
||||
"Blob from aborted transaction still exists.");
|
||||
}
|
||||
|
||||
|
||||
void test_blob_remove_removes_blob()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
pqxx::oid id{pqxx::blob::create(tx)};
|
||||
pqxx::blob::remove(tx, id);
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::ignore_unused(pqxx::blob::open_r(tx, id)), pqxx::failure,
|
||||
"Attempt to open blob after removing should have failed.");
|
||||
}
|
||||
|
||||
|
||||
void test_blob_remove_is_not_idempotent()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
pqxx::oid id{pqxx::blob::create(tx)};
|
||||
pqxx::blob::remove(tx, id);
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::blob::remove(tx, id), pqxx::failure,
|
||||
"Redundant remove() did not throw failure.");
|
||||
}
|
||||
|
||||
|
||||
void test_blob_checks_open_mode()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
pqxx::oid id{pqxx::blob::create(tx)};
|
||||
pqxx::blob b_r{pqxx::blob::open_r(tx, id)};
|
||||
pqxx::blob b_w{pqxx::blob::open_w(tx, id)};
|
||||
pqxx::blob b_rw{pqxx::blob::open_rw(tx, id)};
|
||||
|
||||
std::basic_string<std::byte> buf{std::byte{3}, std::byte{2}, std::byte{1}};
|
||||
|
||||
// These are all allowed:
|
||||
b_w.write(buf);
|
||||
b_r.read(buf, 3);
|
||||
b_rw.seek_end(0);
|
||||
b_rw.write(buf);
|
||||
b_rw.seek_abs(0);
|
||||
b_rw.read(buf, 6);
|
||||
|
||||
// These are not:
|
||||
PQXX_CHECK_THROWS(
|
||||
b_r.write(buf), pqxx::failure, "Read-only blob did not stop write.");
|
||||
PQXX_CHECK_THROWS(
|
||||
b_w.read(buf, 10), pqxx::failure, "Write-only blob did not stop read.");
|
||||
}
|
||||
|
||||
|
||||
void test_blob_supports_move()
|
||||
{
|
||||
std::basic_string<std::byte> buf;
|
||||
buf.push_back(std::byte{'x'});
|
||||
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
pqxx::oid id{pqxx::blob::create(tx)};
|
||||
pqxx::blob b1{pqxx::blob::open_rw(tx, id)};
|
||||
b1.write(buf);
|
||||
|
||||
pqxx::blob b2{std::move(b1)};
|
||||
b2.seek_abs(0);
|
||||
b2.read(buf, 1u);
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
b1.read(buf, 1u), pqxx::usage_error,
|
||||
"Blob still works after move construction.");
|
||||
|
||||
b1 = std::move(b2);
|
||||
b1.read(buf, 1u);
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
b2.read(buf, 1u), pqxx::usage_error,
|
||||
"Blob still works after move assignment.");
|
||||
}
|
||||
|
||||
|
||||
void test_blob_read_reads_data()
|
||||
{
|
||||
std::basic_string<std::byte> const data{
|
||||
std::byte{'a'}, std::byte{'b'}, std::byte{'c'}};
|
||||
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
pqxx::oid id{pqxx::blob::from_buf(tx, data)};
|
||||
|
||||
std::basic_string<std::byte> buf;
|
||||
auto b{pqxx::blob::open_rw(tx, id)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
b.read(buf, 2), 2u, "Full read() returned an unexpected value.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
buf, (std::basic_string<std::byte>{std::byte{'a'}, std::byte{'b'}}),
|
||||
"Read back the wrong data.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
b.read(buf, 2), 1u, "Partial read() returned an unexpected value.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
buf, (std::basic_string<std::byte>{std::byte{'c'}}),
|
||||
"Continued read produced wrong data.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
b.read(buf, 2), 0u, "read at end returned an unexpected value.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
buf, (std::basic_string<std::byte>{}), "Read past end produced data.");
|
||||
}
|
||||
|
||||
|
||||
void test_blob_read_span()
|
||||
{
|
||||
#if defined(PQXX_HAVE_SPAN)
|
||||
std::basic_string<std::byte> const data{std::byte{'u'}, std::byte{'v'},
|
||||
std::byte{'w'}, std::byte{'x'},
|
||||
std::byte{'y'}, std::byte{'z'}};
|
||||
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
pqxx::oid id{pqxx::blob::from_buf(tx, data)};
|
||||
|
||||
auto b{pqxx::blob::open_r(tx, id)};
|
||||
std::basic_string<std::byte> string_buf;
|
||||
string_buf.resize(2);
|
||||
|
||||
std::span<std::byte> output;
|
||||
|
||||
output = b.read(std::span<std::byte>{});
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(output), 0u, "Empty read produced nonempty buffer.");
|
||||
output = b.read(string_buf);
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(output), 2u, "Got unexpected buf size from blob::read().");
|
||||
PQXX_CHECK_EQUAL(
|
||||
output[0], std::byte{'u'}, "Unexpected byte from blob::read().");
|
||||
PQXX_CHECK_EQUAL(
|
||||
output[1], std::byte{'v'}, "Unexpected byte from blob::read().");
|
||||
|
||||
string_buf.resize(100);
|
||||
output = b.read(std::span<std::byte>{string_buf.data(), 1});
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(output), 1u,
|
||||
"Did blob::read() follow string size instead of span size?");
|
||||
PQXX_CHECK_EQUAL(
|
||||
output[0], std::byte{'w'}, "Unexpected byte from blob::read().");
|
||||
|
||||
std::vector<std::byte> vec_buf;
|
||||
vec_buf.resize(2);
|
||||
auto output2{b.read(vec_buf)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(output2), 2u, "Got unexpected buf size from blob::read().");
|
||||
PQXX_CHECK_EQUAL(
|
||||
output2[0], std::byte{'x'}, "Unexpected byte from blob::read().");
|
||||
PQXX_CHECK_EQUAL(
|
||||
output2[1], std::byte{'y'}, "Unexpected byte from blob::read().");
|
||||
|
||||
vec_buf.resize(100);
|
||||
output2 = b.read(vec_buf);
|
||||
PQXX_CHECK_EQUAL(std::size(output2), 1u, "Weird things happened at EOF.");
|
||||
PQXX_CHECK_EQUAL(output2[0], std::byte{'z'}, "Bad data at EOF.");
|
||||
#endif // PQXX_HAVE_SPAN
|
||||
}
|
||||
|
||||
|
||||
void test_blob_reads_vector()
|
||||
{
|
||||
char const content[]{"abcd"};
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
auto id{pqxx::blob::from_buf(
|
||||
tx, std::basic_string_view<std::byte>{
|
||||
reinterpret_cast<std::byte const *>(content), std::size(content)})};
|
||||
std::vector<std::byte> buf;
|
||||
buf.resize(10);
|
||||
auto out{pqxx::blob::open_r(tx, id).read(buf)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(out), std::size(content),
|
||||
"Got wrong length back when reading as vector.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
out[0], std::byte{'a'}, "Got bad data when reading as vector.");
|
||||
}
|
||||
|
||||
|
||||
void test_blob_write_appends_at_insertion_point()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
auto id{pqxx::blob::create(tx)};
|
||||
|
||||
auto b{pqxx::blob::open_rw(tx, id)};
|
||||
b.write(std::basic_string<std::byte>{std::byte{'z'}});
|
||||
b.write(std::basic_string<std::byte>{std::byte{'a'}});
|
||||
|
||||
std::basic_string<std::byte> buf;
|
||||
b.read(buf, 5);
|
||||
PQXX_CHECK_EQUAL(
|
||||
buf, (std::basic_string<std::byte>{}), "Found data at the end.");
|
||||
b.seek_abs(0);
|
||||
b.read(buf, 5);
|
||||
PQXX_CHECK_EQUAL(
|
||||
buf, (std::basic_string<std::byte>{std::byte{'z'}, std::byte{'a'}}),
|
||||
"Consecutive writes did not append correctly.");
|
||||
|
||||
b.write(std::basic_string<std::byte>{std::byte{'x'}});
|
||||
// Blob now contains "zax". That's not we wanted... Rewind and rewrite.
|
||||
b.seek_abs(1);
|
||||
b.write(std::basic_string<std::byte>{std::byte{'y'}});
|
||||
b.seek_abs(0);
|
||||
b.read(buf, 5);
|
||||
PQXX_CHECK_EQUAL(
|
||||
buf,
|
||||
(std::basic_string<std::byte>{
|
||||
std::byte{'z'}, std::byte{'y'}, std::byte{'x'}}),
|
||||
"Rewriting in the middle did not work right.");
|
||||
}
|
||||
|
||||
|
||||
void test_blob_writes_span()
|
||||
{
|
||||
#if defined(PQXX_HAVE_SPAN)
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
constexpr char content[]{"gfbltk"};
|
||||
std::basic_string<std::byte> data{
|
||||
reinterpret_cast<std::byte const *>(content), std::size(content)};
|
||||
|
||||
auto id{pqxx::blob::create(tx)};
|
||||
auto b{pqxx::blob::open_rw(tx, id)};
|
||||
b.write(std::span<std::byte>{data.data() + 1, 3u});
|
||||
b.seek_abs(0);
|
||||
|
||||
std::vector<std::byte> buf;
|
||||
buf.resize(4);
|
||||
auto out{b.read(std::span<std::byte>{buf.data(), 4u})};
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(out), 3u, "Did not get expected number of bytes back.");
|
||||
PQXX_CHECK_EQUAL(out[0], std::byte{'f'}, "Data did not come back right.");
|
||||
PQXX_CHECK_EQUAL(out[2], std::byte{'l'}, "Data started right, ended wrong!");
|
||||
#endif // PQXX_HAVE_SPAN
|
||||
}
|
||||
|
||||
|
||||
void test_blob_resize_shortens_to_desired_length()
|
||||
{
|
||||
std::basic_string<std::byte> const data{
|
||||
std::byte{'w'}, std::byte{'o'}, std::byte{'r'}, std::byte{'k'}};
|
||||
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
auto id{pqxx::blob::from_buf(tx, data)};
|
||||
|
||||
pqxx::blob::open_w(tx, id).resize(2);
|
||||
std::basic_string<std::byte> buf;
|
||||
pqxx::blob::to_buf(tx, id, buf, 10);
|
||||
PQXX_CHECK_EQUAL(
|
||||
buf, (std::basic_string<std::byte>{std::byte{'w'}, std::byte{'o'}}),
|
||||
"Truncate did not shorten correctly.");
|
||||
}
|
||||
|
||||
|
||||
void test_blob_resize_extends_to_desired_length()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
auto id{
|
||||
pqxx::blob::from_buf(tx, std::basic_string<std::byte>{std::byte{100}})};
|
||||
pqxx::blob::open_w(tx, id).resize(3);
|
||||
std::basic_string<std::byte> buf;
|
||||
pqxx::blob::to_buf(tx, id, buf, 10);
|
||||
PQXX_CHECK_EQUAL(
|
||||
buf,
|
||||
(std::basic_string<std::byte>{std::byte{100}, std::byte{0}, std::byte{0}}),
|
||||
"Resize did not zero-extend correctly.");
|
||||
}
|
||||
|
||||
|
||||
void test_blob_tell_tracks_position()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
auto id{pqxx::blob::create(tx)};
|
||||
auto b{pqxx::blob::open_rw(tx, id)};
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
b.tell(), 0, "Empty blob started out in non-zero position.");
|
||||
b.write(std::basic_string<std::byte>{std::byte{'e'}, std::byte{'f'}});
|
||||
PQXX_CHECK_EQUAL(
|
||||
b.tell(), 2, "Empty blob started out in non-zero position.");
|
||||
b.seek_abs(1);
|
||||
PQXX_CHECK_EQUAL(b.tell(), 1, "tell() did not track seek.");
|
||||
}
|
||||
|
||||
|
||||
void test_blob_seek_sets_positions()
|
||||
{
|
||||
std::basic_string<std::byte> data{
|
||||
std::byte{0}, std::byte{1}, std::byte{2}, std::byte{3}, std::byte{4},
|
||||
std::byte{5}, std::byte{6}, std::byte{7}, std::byte{8}, std::byte{9}};
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
auto id{pqxx::blob::from_buf(tx, data)};
|
||||
auto b{pqxx::blob::open_r(tx, id)};
|
||||
|
||||
std::basic_string<std::byte> buf;
|
||||
b.seek_rel(3);
|
||||
b.read(buf, 1u);
|
||||
PQXX_CHECK_EQUAL(
|
||||
buf[0], std::byte{3},
|
||||
"seek_rel() from beginning did not take us to the right position.");
|
||||
|
||||
b.seek_abs(2);
|
||||
b.read(buf, 1u);
|
||||
PQXX_CHECK_EQUAL(
|
||||
buf[0], std::byte{2}, "seek_abs() did not take us to the right position.");
|
||||
|
||||
b.seek_end(-2);
|
||||
b.read(buf, 1u);
|
||||
PQXX_CHECK_EQUAL(
|
||||
buf[0], std::byte{8}, "seek_end() did not take us to the right position.");
|
||||
}
|
||||
|
||||
|
||||
void test_blob_from_buf_interoperates_with_to_buf()
|
||||
{
|
||||
std::basic_string<std::byte> const data{std::byte{'h'}, std::byte{'i'}};
|
||||
std::basic_string<std::byte> buf;
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
pqxx::blob::to_buf(tx, pqxx::blob::from_buf(tx, data), buf, 10);
|
||||
PQXX_CHECK_EQUAL(buf, data, "from_buf()/to_buf() roundtrip did not work.");
|
||||
}
|
||||
|
||||
|
||||
void test_blob_append_from_buf_appends()
|
||||
{
|
||||
std::basic_string<std::byte> const data{std::byte{'h'}, std::byte{'o'}};
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
auto id{pqxx::blob::create(tx)};
|
||||
pqxx::blob::append_from_buf(tx, data, id);
|
||||
pqxx::blob::append_from_buf(tx, data, id);
|
||||
std::basic_string<std::byte> buf;
|
||||
pqxx::blob::to_buf(tx, id, buf, 10);
|
||||
PQXX_CHECK_EQUAL(buf, data + data, "append_from_buf() wrote wrong data?");
|
||||
}
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
/// Wrap `std::fopen`.
|
||||
/** This is just here to stop Visual Studio from advertising its own
|
||||
* alternative.
|
||||
*/
|
||||
std::unique_ptr<FILE, std::function<int(FILE *)>>
|
||||
my_fopen(char const *path, char const *mode)
|
||||
{
|
||||
#if defined(_MSC_VER)
|
||||
# pragma warning(push)
|
||||
# pragma warning(disable : 4996)
|
||||
#endif
|
||||
return {std::fopen(path, mode), std::fclose};
|
||||
#if defined(_MSC_VER)
|
||||
# pragma warning(pop)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void read_file(
|
||||
char const path[], std::size_t len, std::basic_string<std::byte> &buf)
|
||||
{
|
||||
buf.resize(len);
|
||||
auto f{my_fopen(path, "rb")};
|
||||
auto bytes{
|
||||
std::fread(reinterpret_cast<char *>(buf.data()), 1, len, f.get())};
|
||||
if (bytes == 0)
|
||||
throw std::runtime_error{"Error reading test file."};
|
||||
buf.resize(bytes);
|
||||
}
|
||||
|
||||
|
||||
void write_file(char const path[], std::basic_string_view<std::byte> data)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto f{my_fopen(path, "wb")};
|
||||
if (
|
||||
std::fwrite(
|
||||
reinterpret_cast<char const *>(data.data()), 1, std::size(data),
|
||||
f.get()) < std::size(data))
|
||||
std::runtime_error{"File write failed."};
|
||||
}
|
||||
catch (const std::exception &)
|
||||
{
|
||||
std::remove(path);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Temporary file.
|
||||
class TempFile
|
||||
{
|
||||
public:
|
||||
/// Create (and later clean up) a file at path containing data.
|
||||
TempFile(char const path[], std::basic_string_view<std::byte> data) :
|
||||
m_path(path)
|
||||
{
|
||||
write_file(path, data);
|
||||
}
|
||||
|
||||
~TempFile() { std::remove(m_path.c_str()); }
|
||||
|
||||
private:
|
||||
std::string m_path;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
|
||||
void test_blob_from_file_creates_blob_from_file_contents()
|
||||
{
|
||||
char const temp_file[] = "blob-test-from_file.tmp";
|
||||
std::basic_string<std::byte> const data{std::byte{'4'}, std::byte{'2'}};
|
||||
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
std::basic_string<std::byte> buf;
|
||||
|
||||
pqxx::oid id;
|
||||
{
|
||||
TempFile f{temp_file, data};
|
||||
id = pqxx::blob::from_file(tx, temp_file);
|
||||
}
|
||||
pqxx::blob::to_buf(tx, id, buf, 10);
|
||||
PQXX_CHECK_EQUAL(buf, data, "Wrong data from blob::from_file().");
|
||||
}
|
||||
|
||||
|
||||
void test_blob_from_file_with_oid_writes_blob()
|
||||
{
|
||||
std::basic_string<std::byte> const data{std::byte{'6'}, std::byte{'9'}};
|
||||
char const temp_file[] = "blob-test-from_file-oid.tmp";
|
||||
std::basic_string<std::byte> buf;
|
||||
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
|
||||
// Guarantee (more or less) that id is not in use.
|
||||
auto id{pqxx::blob::create(tx)};
|
||||
pqxx::blob::remove(tx, id);
|
||||
|
||||
{
|
||||
TempFile f{temp_file, data};
|
||||
pqxx::blob::from_file(tx, temp_file, id);
|
||||
}
|
||||
pqxx::blob::to_buf(tx, id, buf, 10);
|
||||
PQXX_CHECK_EQUAL(buf, data, "Wrong data from blob::from_file().");
|
||||
}
|
||||
|
||||
|
||||
void test_blob_append_to_buf_appends()
|
||||
{
|
||||
std::basic_string<std::byte> const data{
|
||||
std::byte{'b'}, std::byte{'l'}, std::byte{'u'}, std::byte{'b'}};
|
||||
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
auto id{pqxx::blob::from_buf(tx, data)};
|
||||
|
||||
std::basic_string<std::byte> buf;
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::blob::append_to_buf(tx, id, 0u, buf, 1u), 1u,
|
||||
"append_to_buf() returned unexpected value.");
|
||||
PQXX_CHECK_EQUAL(std::size(buf), 1u, "Appended the wrong number of bytes.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::blob::append_to_buf(tx, id, 1u, buf, 5u), 3u,
|
||||
"append_to_buf() returned unexpected value.");
|
||||
PQXX_CHECK_EQUAL(std::size(buf), 4u, "Appended the wrong number of bytes.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
buf, data, "Reading using append_to_buf gave us wrong data.");
|
||||
}
|
||||
|
||||
|
||||
void test_blob_to_file_writes_file()
|
||||
{
|
||||
std::basic_string<std::byte> const data{
|
||||
std::byte{'C'}, std::byte{'+'}, std::byte{'+'}};
|
||||
|
||||
char const temp_file[] = "blob-test-to_file.tmp";
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
auto id{pqxx::blob::from_buf(tx, data)};
|
||||
std::basic_string<std::byte> buf;
|
||||
|
||||
try
|
||||
{
|
||||
pqxx::blob::to_file(tx, id, temp_file);
|
||||
read_file(temp_file, 10u, buf);
|
||||
std::remove(temp_file);
|
||||
}
|
||||
catch (std::exception const &)
|
||||
{
|
||||
std::remove(temp_file);
|
||||
throw;
|
||||
}
|
||||
PQXX_CHECK_EQUAL(buf, data, "Got wrong data from to_file().");
|
||||
}
|
||||
|
||||
|
||||
void test_blob_close_leaves_blob_unusable()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
auto id{
|
||||
pqxx::blob::from_buf(tx, std::basic_string<std::byte>{std::byte{1}})};
|
||||
auto b{pqxx::blob::open_rw(tx, id)};
|
||||
b.close();
|
||||
std::basic_string<std::byte> buf;
|
||||
PQXX_CHECK_THROWS(
|
||||
b.read(buf, 1), pqxx::usage_error,
|
||||
"Reading from closed blob did not fail right.");
|
||||
}
|
||||
|
||||
|
||||
void test_blob_accepts_std_filesystem_path()
|
||||
{
|
||||
#if defined(PQXX_HAVE_PATH) && !defined(_WIN32)
|
||||
// A bug in gcc 8's ~std::filesystem::path() causes a run-time crash.
|
||||
# if !defined(__GNUC__) || (__GNUC__ > 8)
|
||||
|
||||
char const temp_file[] = "blob-test-filesystem-path.tmp";
|
||||
std::basic_string<std::byte> const data{std::byte{'4'}, std::byte{'2'}};
|
||||
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
std::basic_string<std::byte> buf;
|
||||
|
||||
TempFile f{temp_file, data};
|
||||
std::filesystem::path const path{temp_file};
|
||||
auto id{pqxx::blob::from_file(tx, path)};
|
||||
pqxx::blob::to_buf(tx, id, buf, 10);
|
||||
PQXX_CHECK_EQUAL(buf, data, "Wrong data from blob::from_file().");
|
||||
|
||||
# endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_blob_is_useless_by_default);
|
||||
PQXX_REGISTER_TEST(test_blob_create_makes_empty_blob);
|
||||
PQXX_REGISTER_TEST(test_blob_create_with_oid_requires_oid_be_free);
|
||||
PQXX_REGISTER_TEST(test_blob_create_with_oid_obeys_oid);
|
||||
PQXX_REGISTER_TEST(test_blobs_are_transactional);
|
||||
PQXX_REGISTER_TEST(test_blob_remove_removes_blob);
|
||||
PQXX_REGISTER_TEST(test_blob_remove_is_not_idempotent);
|
||||
PQXX_REGISTER_TEST(test_blob_checks_open_mode);
|
||||
PQXX_REGISTER_TEST(test_blob_supports_move);
|
||||
PQXX_REGISTER_TEST(test_blob_read_reads_data);
|
||||
PQXX_REGISTER_TEST(test_blob_reads_vector);
|
||||
PQXX_REGISTER_TEST(test_blob_read_span);
|
||||
PQXX_REGISTER_TEST(test_blob_write_appends_at_insertion_point);
|
||||
PQXX_REGISTER_TEST(test_blob_writes_span);
|
||||
PQXX_REGISTER_TEST(test_blob_resize_shortens_to_desired_length);
|
||||
PQXX_REGISTER_TEST(test_blob_resize_extends_to_desired_length);
|
||||
PQXX_REGISTER_TEST(test_blob_tell_tracks_position);
|
||||
PQXX_REGISTER_TEST(test_blob_seek_sets_positions);
|
||||
PQXX_REGISTER_TEST(test_blob_from_buf_interoperates_with_to_buf);
|
||||
PQXX_REGISTER_TEST(test_blob_append_from_buf_appends);
|
||||
PQXX_REGISTER_TEST(test_blob_from_file_creates_blob_from_file_contents);
|
||||
PQXX_REGISTER_TEST(test_blob_from_file_with_oid_writes_blob);
|
||||
PQXX_REGISTER_TEST(test_blob_append_to_buf_appends);
|
||||
PQXX_REGISTER_TEST(test_blob_to_file_writes_file);
|
||||
PQXX_REGISTER_TEST(test_blob_close_leaves_blob_unusable);
|
||||
PQXX_REGISTER_TEST(test_blob_accepts_std_filesystem_path);
|
||||
} // namespace
|
||||
@@ -0,0 +1,25 @@
|
||||
#include <pqxx/pipeline>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_cancel_query()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
// Calling cancel_query() while none is in progress has no effect.
|
||||
conn.cancel_query();
|
||||
|
||||
// Nothing much is guaranteed about cancel_query, except that it doesn't make
|
||||
// the process die in flames.
|
||||
pqxx::pipeline p{tx, "test_cancel_query"};
|
||||
p.retain(0);
|
||||
p.insert("SELECT pg_sleep(1)");
|
||||
conn.cancel_query();
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_cancel_query);
|
||||
} // namespace
|
||||
@@ -0,0 +1,61 @@
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_table_column()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
|
||||
tx.exec0("CREATE TEMP TABLE pqxxfoo (x varchar, y integer, z integer)");
|
||||
tx.exec0("INSERT INTO pqxxfoo VALUES ('xx', 1, 2)");
|
||||
auto R{tx.exec("SELECT z,y,x FROM pqxxfoo")};
|
||||
auto X{tx.exec("SELECT x,y,z,99 FROM pqxxfoo")};
|
||||
|
||||
pqxx::row::size_type x{R.table_column(2)}, y{R.table_column(1)},
|
||||
z{R.table_column(static_cast<int>(0))};
|
||||
|
||||
PQXX_CHECK_EQUAL(x, 0, "Wrong column number.");
|
||||
PQXX_CHECK_EQUAL(y, 1, "Wrong column number.");
|
||||
PQXX_CHECK_EQUAL(z, 2, "Wrong column number.");
|
||||
|
||||
x = R.table_column("x");
|
||||
y = R.table_column("y");
|
||||
z = R.table_column("z");
|
||||
|
||||
PQXX_CHECK_EQUAL(x, 0, "Wrong number for named column.");
|
||||
PQXX_CHECK_EQUAL(y, 1, "Wrong number for named column.");
|
||||
PQXX_CHECK_EQUAL(z, 2, "Wrong number for named column.");
|
||||
|
||||
pqxx::row::size_type xx{X[0].table_column(static_cast<int>(0))},
|
||||
yx{X[0].table_column(pqxx::row::size_type(1))}, zx{X[0].table_column("z")};
|
||||
|
||||
PQXX_CHECK_EQUAL(xx, 0, "Bad result from table_column(int).");
|
||||
PQXX_CHECK_EQUAL(yx, 1, "Bad result from table_column(size_type).");
|
||||
PQXX_CHECK_EQUAL(zx, 2, "Bad result from table_column(string).");
|
||||
|
||||
for (pqxx::row::size_type i{0}; i < std::size(R[0]); ++i)
|
||||
PQXX_CHECK_EQUAL(
|
||||
R[0][i].table_column(), R.table_column(i),
|
||||
"Bad result from column_table().");
|
||||
|
||||
int col;
|
||||
PQXX_CHECK_THROWS_EXCEPTION(
|
||||
col = R.table_column(3), "table_column() with invalid index didn't fail.");
|
||||
pqxx::ignore_unused(col);
|
||||
|
||||
PQXX_CHECK_THROWS_EXCEPTION(
|
||||
col = R.table_column("nonexistent"),
|
||||
"table_column() with invalid column name didn't fail.");
|
||||
pqxx::ignore_unused(col);
|
||||
|
||||
PQXX_CHECK_THROWS_EXCEPTION(
|
||||
col = X.table_column(3), "table_column() on non-table didn't fail.");
|
||||
pqxx::ignore_unused(col);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_table_column);
|
||||
@@ -0,0 +1,98 @@
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
#include "pqxx/composite"
|
||||
#include "pqxx/transaction"
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_composite()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
tx.exec0("CREATE TYPE pqxxfoo AS (a integer, b text)");
|
||||
auto const r{tx.exec1("SELECT '(5,hello)'::pqxxfoo")};
|
||||
|
||||
int a;
|
||||
std::string b;
|
||||
pqxx::parse_composite(r[0].view(), a, b);
|
||||
|
||||
PQXX_CHECK_EQUAL(a, 5, "Integer composite field came back wrong.");
|
||||
PQXX_CHECK_EQUAL(b, "hello", "String composite field came back wrong.");
|
||||
}
|
||||
|
||||
|
||||
void test_composite_escapes()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
pqxx::row r;
|
||||
tx.exec0("CREATE TYPE pqxxsingle AS (x text)");
|
||||
std::string s;
|
||||
|
||||
r = tx.exec1(R"--(SELECT '("a""b")'::pqxxsingle)--");
|
||||
pqxx::parse_composite(r[0].view(), s);
|
||||
PQXX_CHECK_EQUAL(
|
||||
s, "a\"b", "Double-double-quotes escaping did not parse correctly.");
|
||||
|
||||
r = tx.exec1(R"--(SELECT '("a\"b")'::pqxxsingle)--");
|
||||
pqxx::parse_composite(r[0].view(), s);
|
||||
PQXX_CHECK_EQUAL(s, "a\"b", "Backslash escaping did not parse correctly.");
|
||||
}
|
||||
|
||||
|
||||
void test_composite_handles_nulls()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
pqxx::row r;
|
||||
|
||||
tx.exec0("CREATE TYPE pqxxnull AS (a integer)");
|
||||
int nonnull;
|
||||
r = tx.exec1("SELECT '()'::pqxxnull");
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::parse_composite(r[0].view(), nonnull), pqxx::conversion_error,
|
||||
"No conversion error when reading a null into a nulless variable.");
|
||||
std::optional<int> nullable{5};
|
||||
pqxx::parse_composite(r[0].view(), nullable);
|
||||
PQXX_CHECK(
|
||||
not nullable.has_value(), "Null integer came out as having a value.");
|
||||
|
||||
tx.exec0("CREATE TYPE pqxxnulls AS (a integer, b integer)");
|
||||
std::optional<int> a{2}, b{4};
|
||||
r = tx.exec1("SELECT '(,)'::pqxxnulls");
|
||||
pqxx::parse_composite(r[0].view(), a, b);
|
||||
PQXX_CHECK(not a.has_value(), "Null first integer stored as value.");
|
||||
PQXX_CHECK(not b.has_value(), "Null second integer stored as value.");
|
||||
}
|
||||
|
||||
|
||||
void test_composite_renders_to_string()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
char buf[1000];
|
||||
|
||||
pqxx::composite_into_buf(
|
||||
std::begin(buf), std::end(buf), 355, "foo", "b\na\\r");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::string{buf}, "(355,\"foo\",\"b\na\\\\r\")",
|
||||
"Composite was not rendered as expected.");
|
||||
|
||||
tx.exec0("CREATE TYPE pqxxcomp AS (a integer, b text, c text)");
|
||||
auto const r{tx.exec1("SELECT '" + std::string{buf} + "'::pqxxcomp")};
|
||||
|
||||
int a;
|
||||
std::string b, c;
|
||||
bool const nonnull{r[0].composite_to(a, b, c)};
|
||||
PQXX_CHECK(nonnull, "Mistaken nullness.");
|
||||
PQXX_CHECK_EQUAL(a, 355, "Int came back wrong.");
|
||||
PQXX_CHECK_EQUAL(b, "foo", "Simple string came back wrong.");
|
||||
PQXX_CHECK_EQUAL(c, "b\na\\r", "Escaping went wrong.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_composite);
|
||||
PQXX_REGISTER_TEST(test_composite_escapes);
|
||||
PQXX_REGISTER_TEST(test_composite_handles_nulls);
|
||||
PQXX_REGISTER_TEST(test_composite_renders_to_string);
|
||||
} // namespace
|
||||
@@ -0,0 +1,212 @@
|
||||
#include <numeric>
|
||||
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_connection_string_constructor()
|
||||
{
|
||||
pqxx::connection c1{""};
|
||||
pqxx::connection c2{std::string{}};
|
||||
}
|
||||
|
||||
|
||||
void test_move_constructor()
|
||||
{
|
||||
pqxx::connection c1;
|
||||
PQXX_CHECK(c1.is_open(), "New connection is not open.");
|
||||
|
||||
pqxx::connection c2{std::move(c1)};
|
||||
|
||||
PQXX_CHECK(not c1.is_open(), "Moving did not close original connection.");
|
||||
PQXX_CHECK(c2.is_open(), "Moved constructor is not open.");
|
||||
|
||||
pqxx::work tx{c2};
|
||||
PQXX_CHECK_EQUAL(tx.query_value<int>("SELECT 5"), 5, "Weird result!");
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::connection c3{std::move(c2)}, pqxx::usage_error,
|
||||
"Moving a connection with a transaction open should be an error.");
|
||||
}
|
||||
|
||||
|
||||
void test_move_assign()
|
||||
{
|
||||
pqxx::connection c1;
|
||||
pqxx::connection c2;
|
||||
|
||||
c2.close();
|
||||
|
||||
c2 = std::move(c1);
|
||||
|
||||
PQXX_CHECK(not c1.is_open(), "Connection still open after being moved out.");
|
||||
PQXX_CHECK(c2.is_open(), "Moved constructor is not open.");
|
||||
|
||||
{
|
||||
pqxx::work tx1{c2};
|
||||
PQXX_CHECK_EQUAL(tx1.query_value<int>("SELECT 8"), 8, "What!?");
|
||||
|
||||
pqxx::connection c3;
|
||||
PQXX_CHECK_THROWS(
|
||||
c3 = std::move(c2), pqxx::usage_error,
|
||||
"Moving a connection with a transaction open should be an error.");
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
c2 = std::move(c3), pqxx::usage_error,
|
||||
"Moving a connection onto one with a transaction open should be "
|
||||
"an error.");
|
||||
}
|
||||
|
||||
// After failed move attempts, the connection is still usable.
|
||||
pqxx::work tx2{c2};
|
||||
PQXX_CHECK_EQUAL(tx2.query_value<int>("SELECT 6"), 6, "Huh!?");
|
||||
}
|
||||
|
||||
|
||||
void test_encrypt_password()
|
||||
{
|
||||
pqxx::connection c;
|
||||
auto pw{c.encrypt_password("user", "password")};
|
||||
PQXX_CHECK(not std::empty(pw), "Encrypted password was empty.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::strlen(pw.c_str()), std::size(pw),
|
||||
"Encrypted password contains a null byte.");
|
||||
}
|
||||
|
||||
|
||||
void test_connection_string()
|
||||
{
|
||||
pqxx::connection c;
|
||||
std::string const connstr{c.connection_string()};
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# pragma warning(push)
|
||||
# pragma warning(disable : 4996)
|
||||
#endif
|
||||
if (std::getenv("PGUSER") == nullptr)
|
||||
#if defined(_MSC_VER)
|
||||
# pragma warning(pop)
|
||||
#endif
|
||||
{
|
||||
PQXX_CHECK(
|
||||
connstr.find("user=" + std::string{c.username()}) != std::string::npos,
|
||||
"Connection string did not specify user name: " + connstr);
|
||||
}
|
||||
else
|
||||
{
|
||||
PQXX_CHECK(
|
||||
connstr.find("user=" + std::string{c.username()}) == std::string::npos,
|
||||
"Connection string specified user name, even when using default: " +
|
||||
connstr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if defined(PQXX_HAVE_CONCEPTS)
|
||||
template<typename STR> std::size_t length(STR const &str)
|
||||
{
|
||||
return std::size(str);
|
||||
}
|
||||
|
||||
|
||||
std::size_t length(char const str[])
|
||||
{
|
||||
return std::strlen(str);
|
||||
}
|
||||
#endif // PQXX_HAVE_CONCEPTS
|
||||
|
||||
|
||||
template<typename MAP> void test_params_type()
|
||||
{
|
||||
#if defined(PQXX_HAVE_CONCEPTS)
|
||||
using item_t = std::remove_reference_t<
|
||||
decltype(*std::declval<std::ranges::iterator_t<MAP>>())>;
|
||||
using key_t = decltype(std::get<0>(std::declval<item_t>()));
|
||||
using value_t = decltype(std::get<1>(std::declval<item_t>()));
|
||||
|
||||
// Set some parameters that are relatively safe to change arbitrarily.
|
||||
MAP const params{{
|
||||
{key_t{"application_name"}, value_t{"pqxx-test"}},
|
||||
{key_t{"connect_timeout"}, value_t{"96"}},
|
||||
{key_t{"keepalives_idle"}, value_t{"771"}},
|
||||
}};
|
||||
|
||||
// Can we create a connection from these parameters?
|
||||
pqxx::connection c{params};
|
||||
|
||||
// Check that the parameters came through in the connection string.
|
||||
// We don't know the exact format, but the parameters have to be in there.
|
||||
auto const min_size{std::accumulate(
|
||||
std::cbegin(params), std::cend(params), std::size(params) - 1,
|
||||
[](auto count, auto item) {
|
||||
return count + length(std::get<0>(item)) + 1 + length(std::get<1>(item));
|
||||
})};
|
||||
|
||||
auto const connstr{c.connection_string()};
|
||||
PQXX_CHECK_GREATER_EQUAL(
|
||||
std::size(connstr), min_size,
|
||||
"Connection string can't possibly contain the options we gave.");
|
||||
for (auto const &[key, value] : params)
|
||||
{
|
||||
PQXX_CHECK_NOT_EQUAL(
|
||||
connstr.find(key), std::string::npos,
|
||||
"Could not find param name '" + std::string{key} +
|
||||
"' in connection string: " + connstr);
|
||||
PQXX_CHECK_NOT_EQUAL(
|
||||
connstr.find(value), std::string::npos,
|
||||
"Could not find value for '" + std::string{value} +
|
||||
"' in connection string: " + connstr);
|
||||
}
|
||||
#endif // PQXX_HAVE_CONCEPTS
|
||||
}
|
||||
|
||||
|
||||
void test_connection_params()
|
||||
{
|
||||
// Connecting in this way supports a wide variety of formats for the
|
||||
// parameters.
|
||||
test_params_type<std::map<char const *, char const *>>();
|
||||
test_params_type<std::map<pqxx::zview, pqxx::zview>>();
|
||||
test_params_type<std::map<std::string, std::string>>();
|
||||
test_params_type<std::map<std::string, pqxx::zview>>();
|
||||
test_params_type<std::map<pqxx::zview, char const *>>();
|
||||
test_params_type<std::vector<std::tuple<char const *, char const *>>>();
|
||||
test_params_type<std::vector<std::tuple<pqxx::zview, std::string>>>();
|
||||
test_params_type<std::vector<std::pair<std::string, char const *>>>();
|
||||
test_params_type<std::vector<std::array<std::string, 2>>>();
|
||||
}
|
||||
|
||||
|
||||
void test_raw_connection()
|
||||
{
|
||||
pqxx::connection conn1;
|
||||
PQXX_CHECK(conn1.is_open(), "Fresh connection is not open!");
|
||||
pqxx::work tx1{conn1};
|
||||
PQXX_CHECK_EQUAL(
|
||||
tx1.query_value<int>("SELECT 8"), 8, "Something weird happened.");
|
||||
pqxx::internal::pq::PGconn *raw{std::move(conn1).release_raw_connection()};
|
||||
PQXX_CHECK(raw != nullptr, "Raw connection is null.");
|
||||
PQXX_CHECK(
|
||||
not conn1.is_open(),
|
||||
"Releasing raw connection did not close pqxx::connection.");
|
||||
|
||||
pqxx::connection conn2{pqxx::connection::seize_raw_connection(raw)};
|
||||
PQXX_CHECK(
|
||||
conn2.is_open(), "Can't produce open connection from raw connection.");
|
||||
pqxx::work tx2{conn2};
|
||||
PQXX_CHECK_EQUAL(
|
||||
tx2.query_value<int>("SELECT 9"), 9,
|
||||
"Raw connection did not produce a working new connection.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_connection_string_constructor);
|
||||
PQXX_REGISTER_TEST(test_move_constructor);
|
||||
PQXX_REGISTER_TEST(test_move_assign);
|
||||
PQXX_REGISTER_TEST(test_encrypt_password);
|
||||
PQXX_REGISTER_TEST(test_connection_string);
|
||||
PQXX_REGISTER_TEST(test_connection_params);
|
||||
PQXX_REGISTER_TEST(test_raw_connection);
|
||||
} // namespace
|
||||
@@ -0,0 +1,50 @@
|
||||
#include <pqxx/cursor>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_stateless_cursor_provides_random_access(pqxx::connection &conn)
|
||||
{
|
||||
pqxx::work tx{conn};
|
||||
pqxx::stateless_cursor<
|
||||
pqxx::cursor_base::read_only, pqxx::cursor_base::owned>
|
||||
c{tx, "SELECT * FROM generate_series(0, 3)", "count", false};
|
||||
|
||||
auto r{c.retrieve(1, 2)};
|
||||
PQXX_CHECK_EQUAL(std::size(r), 1, "Wrong number of rows from retrieve().");
|
||||
PQXX_CHECK_EQUAL(r[0][0].as<int>(), 1, "Cursor retrieved wrong data.");
|
||||
|
||||
r = c.retrieve(3, 10);
|
||||
PQXX_CHECK_EQUAL(std::size(r), 1, "Expected 1 row retrieving past end.");
|
||||
PQXX_CHECK_EQUAL(r[0][0].as<int>(), 3, "Wrong data retrieved at end.");
|
||||
|
||||
r = c.retrieve(0, 1);
|
||||
PQXX_CHECK_EQUAL(std::size(r), 1, "Wrong number of rows back at beginning.");
|
||||
PQXX_CHECK_EQUAL(r[0][0].as<int>(), 0, "Wrong data back at beginning.");
|
||||
}
|
||||
|
||||
|
||||
void test_stateless_cursor_ignores_trailing_semicolon(pqxx::connection &conn)
|
||||
{
|
||||
pqxx::work tx{conn};
|
||||
pqxx::stateless_cursor<
|
||||
pqxx::cursor_base::read_only, pqxx::cursor_base::owned>
|
||||
c{tx, "SELECT * FROM generate_series(0, 3) ;; ; \n \t ", "count", false};
|
||||
|
||||
auto r{c.retrieve(1, 2)};
|
||||
PQXX_CHECK_EQUAL(std::size(r), 1, "Trailing semicolon confused retrieve().");
|
||||
}
|
||||
|
||||
|
||||
void test_cursor()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
test_stateless_cursor_provides_random_access(conn);
|
||||
test_stateless_cursor_ignores_trailing_semicolon(conn);
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_cursor);
|
||||
} // namespace
|
||||
@@ -0,0 +1,114 @@
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
#include "pqxx/internal/encodings.hxx"
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_scan_ascii()
|
||||
{
|
||||
auto const scan{pqxx::internal::get_glyph_scanner(
|
||||
pqxx::internal::encoding_group::MONOBYTE)};
|
||||
std::string const text{"hello"};
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
scan(text.c_str(), std::size(text), 0), 1ul,
|
||||
"Monobyte scanner acting up.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
scan(text.c_str(), std::size(text), 1), 2ul,
|
||||
"Monobyte scanner is inconsistent.");
|
||||
}
|
||||
|
||||
|
||||
void test_scan_utf8()
|
||||
{
|
||||
auto const scan{
|
||||
pqxx::internal::get_glyph_scanner(pqxx::internal::encoding_group::UTF8)};
|
||||
|
||||
// Thai: "Khrab".
|
||||
std::string const text{"\xe0\xb8\x95\xe0\xb8\xa3\xe0\xb8\xb1\xe0\xb8\x9a"};
|
||||
PQXX_CHECK_EQUAL(
|
||||
scan(text.c_str(), std::size(text), 0), 3ul,
|
||||
"UTF-8 scanner mis-scanned Thai khor khwai.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
scan(text.c_str(), std::size(text), 3), 6ul,
|
||||
"UTF-8 scanner mis-scanned Thai ror reua.");
|
||||
}
|
||||
|
||||
|
||||
void test_for_glyphs_empty()
|
||||
{
|
||||
bool iterated{false};
|
||||
pqxx::internal::for_glyphs(
|
||||
pqxx::internal::encoding_group::MONOBYTE,
|
||||
[&iterated](char const *, char const *) { iterated = true; }, "", 0);
|
||||
PQXX_CHECK(!iterated, "Empty string went through an iteration.");
|
||||
}
|
||||
|
||||
|
||||
void test_for_glyphs_ascii()
|
||||
{
|
||||
std::string const text{"hi"};
|
||||
std::vector<std::ptrdiff_t> points;
|
||||
|
||||
pqxx::internal::for_glyphs(
|
||||
pqxx::internal::encoding_group::UTF8,
|
||||
[&points](char const *gbegin, char const *gend) {
|
||||
points.push_back(gend - gbegin);
|
||||
},
|
||||
text.c_str(), std::size(text));
|
||||
|
||||
PQXX_CHECK_EQUAL(std::size(points), 2u, "Wrong number of ASCII iterations.");
|
||||
PQXX_CHECK_EQUAL(points[0], 1u, "ASCII iteration started off wrong.");
|
||||
PQXX_CHECK_EQUAL(points[1], 1u, "ASCII iteration was inconsistent.");
|
||||
}
|
||||
|
||||
|
||||
void test_for_glyphs_utf8()
|
||||
{
|
||||
// Greek: alpha omega.
|
||||
std::string const text{"\xce\x91\xce\xa9"};
|
||||
std::vector<std::ptrdiff_t> points;
|
||||
|
||||
pqxx::internal::for_glyphs(
|
||||
pqxx::internal::encoding_group::UTF8,
|
||||
[&points](char const *gbegin, char const *gend) {
|
||||
points.push_back(gend - gbegin);
|
||||
},
|
||||
text.c_str(), std::size(text));
|
||||
|
||||
PQXX_CHECK_EQUAL(std::size(points), 2u, "Wrong number of UTF-8 iterations.");
|
||||
PQXX_CHECK_EQUAL(points[0], 2u, "UTF-8 iteration started off wrong.");
|
||||
PQXX_CHECK_EQUAL(points[1], 2u, "ASCII iteration was inconsistent.");
|
||||
|
||||
// Greek lambda, ASCII plus sign, Old Persian Gu.
|
||||
std::string const mix{"\xce\xbb+\xf0\x90\x8e\xa6"};
|
||||
points.clear();
|
||||
|
||||
pqxx::internal::for_glyphs(
|
||||
pqxx::internal::encoding_group::UTF8,
|
||||
[&points](char const *gbegin, char const *gend) {
|
||||
points.push_back(gend - gbegin);
|
||||
},
|
||||
mix.c_str(), std::size(mix));
|
||||
|
||||
PQXX_CHECK_EQUAL(std::size(points), 3u, "Mixed UTF-8 iteration is broken.");
|
||||
PQXX_CHECK_EQUAL(points[0], 2u, "Mixed UTF-8 iteration started off wrong.");
|
||||
PQXX_CHECK_EQUAL(points[1], 1u, "Mixed UTF-8 iteration got ASCII wrong.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
points[2], 4u, "Mixed UTF-8 iteration got long char wrong.");
|
||||
}
|
||||
|
||||
|
||||
void test_encodings()
|
||||
{
|
||||
test_scan_ascii();
|
||||
test_scan_utf8();
|
||||
test_for_glyphs_empty();
|
||||
test_for_glyphs_ascii();
|
||||
test_for_glyphs_utf8();
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_encodings);
|
||||
} // namespace
|
||||
@@ -0,0 +1,37 @@
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <libpq-fe.h>
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_error_verbosity()
|
||||
{
|
||||
PQXX_CHECK_EQUAL(
|
||||
static_cast<int>(pqxx::error_verbosity::terse),
|
||||
static_cast<int>(PQERRORS_TERSE),
|
||||
"error_verbosity enum should match PGVerbosity.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
static_cast<int>(pqxx::error_verbosity::normal),
|
||||
static_cast<int>(PQERRORS_DEFAULT),
|
||||
"error_verbosity enum should match PGVerbosity.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
static_cast<int>(pqxx::error_verbosity::verbose),
|
||||
static_cast<int>(PQERRORS_VERBOSE),
|
||||
"error_verbosity enum should match PGVerbosity.");
|
||||
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
conn.set_verbosity(pqxx::error_verbosity::terse);
|
||||
tx.exec1("SELECT 1");
|
||||
conn.set_verbosity(pqxx::error_verbosity::verbose);
|
||||
tx.exec1("SELECT 2");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_error_verbosity);
|
||||
} // namespace
|
||||
@@ -0,0 +1,223 @@
|
||||
#include <vector>
|
||||
|
||||
#include <pqxx/connection>
|
||||
#include <pqxx/errorhandler>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
class TestErrorHandler final : public pqxx::errorhandler
|
||||
{
|
||||
public:
|
||||
TestErrorHandler(
|
||||
pqxx::connection &c, std::vector<TestErrorHandler *> &activated_handlers,
|
||||
bool retval = true) :
|
||||
pqxx::errorhandler(c),
|
||||
return_value(retval),
|
||||
message(),
|
||||
handler_list(activated_handlers)
|
||||
{}
|
||||
|
||||
bool operator()(char const msg[]) noexcept override
|
||||
{
|
||||
message = std::string{msg};
|
||||
handler_list.push_back(this);
|
||||
return return_value;
|
||||
}
|
||||
|
||||
bool return_value;
|
||||
std::string message;
|
||||
std::vector<TestErrorHandler *> &handler_list;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
template<> struct nullness<TestErrorHandler *>
|
||||
{
|
||||
// clang warns about these being unused. And clang 6 won't accept a
|
||||
// [[maybe_unused]] attribute on them either!
|
||||
|
||||
// static inline constexpr bool has_null{true};
|
||||
// static inline constexpr bool always_null{false};
|
||||
|
||||
static constexpr bool is_null(TestErrorHandler *e) noexcept
|
||||
{
|
||||
return e == nullptr;
|
||||
}
|
||||
static constexpr TestErrorHandler *null() noexcept { return nullptr; }
|
||||
};
|
||||
|
||||
|
||||
template<> struct string_traits<TestErrorHandler *>
|
||||
{
|
||||
static constexpr std::size_t size_buffer(TestErrorHandler *const &) noexcept
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
|
||||
static char *into_buf(char *begin, char *end, TestErrorHandler *const &value)
|
||||
{
|
||||
std::string text{"TestErrorHandler at " + pqxx::to_string(value)};
|
||||
if (pqxx::internal::cmp_greater_equal(std::size(text), end - begin))
|
||||
throw conversion_overrun{"Not enough buffer for TestErrorHandler."};
|
||||
std::memcpy(begin, text.c_str(), std::size(text) + 1);
|
||||
return begin + std::size(text) + 1;
|
||||
}
|
||||
};
|
||||
} // namespace pqxx
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_process_notice_calls_errorhandler(pqxx::connection &c)
|
||||
{
|
||||
std::vector<TestErrorHandler *> dummy;
|
||||
TestErrorHandler handler(c, dummy);
|
||||
c.process_notice("Error!\n");
|
||||
PQXX_CHECK_EQUAL(handler.message, "Error!\n", "Error not handled.");
|
||||
}
|
||||
|
||||
|
||||
void test_error_handlers_get_called_newest_to_oldest(pqxx::connection &c)
|
||||
{
|
||||
std::vector<TestErrorHandler *> handlers;
|
||||
TestErrorHandler h1(c, handlers);
|
||||
TestErrorHandler h2(c, handlers);
|
||||
TestErrorHandler h3(c, handlers);
|
||||
c.process_notice("Warning.\n");
|
||||
PQXX_CHECK_EQUAL(h3.message, "Warning.\n", "Message not handled.");
|
||||
PQXX_CHECK_EQUAL(h2.message, "Warning.\n", "Broken handling chain.");
|
||||
PQXX_CHECK_EQUAL(h1.message, "Warning.\n", "Insane handling chain.");
|
||||
PQXX_CHECK_EQUAL(std::size(handlers), 3u, "Wrong number of handler calls.");
|
||||
PQXX_CHECK_EQUAL(&h3, handlers[0], "Unexpected handling order.");
|
||||
PQXX_CHECK_EQUAL(&h2, handlers[1], "Insane handling order.");
|
||||
PQXX_CHECK_EQUAL(&h1, handlers[2], "Impossible handling order.");
|
||||
}
|
||||
|
||||
void test_returning_false_stops_error_handling(pqxx::connection &c)
|
||||
{
|
||||
std::vector<TestErrorHandler *> handlers;
|
||||
TestErrorHandler starved(c, handlers);
|
||||
TestErrorHandler blocker(c, handlers, false);
|
||||
c.process_notice("Error output.\n");
|
||||
PQXX_CHECK_EQUAL(std::size(handlers), 1u, "Handling chain was not stopped.");
|
||||
PQXX_CHECK_EQUAL(handlers[0], &blocker, "Wrong handler got message.");
|
||||
PQXX_CHECK_EQUAL(blocker.message, "Error output.\n", "Didn't get message.");
|
||||
PQXX_CHECK_EQUAL(starved.message, "", "Message received; it shouldn't be.");
|
||||
}
|
||||
|
||||
void test_destroyed_error_handlers_are_not_called(pqxx::connection &c)
|
||||
{
|
||||
std::vector<TestErrorHandler *> handlers;
|
||||
{
|
||||
TestErrorHandler doomed(c, handlers);
|
||||
}
|
||||
c.process_notice("Unheard output.");
|
||||
PQXX_CHECK(
|
||||
std::empty(handlers), "Message was received on dead errorhandler.");
|
||||
}
|
||||
|
||||
void test_destroying_connection_unregisters_handlers()
|
||||
{
|
||||
TestErrorHandler *survivor;
|
||||
std::vector<TestErrorHandler *> handlers;
|
||||
{
|
||||
pqxx::connection c;
|
||||
survivor = new TestErrorHandler(c, handlers);
|
||||
}
|
||||
// Make some pointless use of survivor just to prove that this doesn't crash.
|
||||
(*survivor)("Hi");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(handlers), 1u, "Ghost of dead ex-connection haunts handler.");
|
||||
delete survivor;
|
||||
}
|
||||
|
||||
|
||||
class MinimalErrorHandler final : public pqxx::errorhandler
|
||||
{
|
||||
public:
|
||||
explicit MinimalErrorHandler(pqxx::connection &c) : pqxx::errorhandler(c) {}
|
||||
bool operator()(char const[]) noexcept override { return true; }
|
||||
};
|
||||
|
||||
|
||||
void test_get_errorhandlers(pqxx::connection &c)
|
||||
{
|
||||
std::unique_ptr<MinimalErrorHandler> eh3;
|
||||
auto const handlers_before{c.get_errorhandlers()};
|
||||
std::size_t const base_handlers{std::size(handlers_before)};
|
||||
|
||||
{
|
||||
MinimalErrorHandler eh1(c);
|
||||
auto const handlers_with_eh1{c.get_errorhandlers()};
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(handlers_with_eh1), base_handlers + 1,
|
||||
"Registering a handler didn't create exactly one handler.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size_t(*std::rbegin(handlers_with_eh1)), std::size_t(&eh1),
|
||||
"Wrong handler or wrong order.");
|
||||
|
||||
{
|
||||
MinimalErrorHandler eh2(c);
|
||||
auto const handlers_with_eh2{c.get_errorhandlers()};
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(handlers_with_eh2), base_handlers + 2,
|
||||
"Adding second handler didn't work.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size_t(*(std::rbegin(handlers_with_eh2) + 1)), std::size_t(&eh1),
|
||||
"Second handler upset order.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size_t(*std::rbegin(handlers_with_eh2)), std::size_t(&eh2),
|
||||
"Second handler isn't right.");
|
||||
}
|
||||
auto const handlers_without_eh2{c.get_errorhandlers()};
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(handlers_without_eh2), base_handlers + 1,
|
||||
"Handler destruction produced wrong-sized handlers list.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size_t(*std::rbegin(handlers_without_eh2)), std::size_t(&eh1),
|
||||
"Destroyed wrong handler.");
|
||||
|
||||
eh3 = std::make_unique<MinimalErrorHandler>(c);
|
||||
auto const handlers_with_eh3{c.get_errorhandlers()};
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(handlers_with_eh3), base_handlers + 2,
|
||||
"Remove-and-add breaks.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size_t(*std::rbegin(handlers_with_eh3)), std::size_t(eh3.get()),
|
||||
"Added wrong third handler.");
|
||||
}
|
||||
auto const handlers_without_eh1{c.get_errorhandlers()};
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(handlers_without_eh1), base_handlers + 1,
|
||||
"Destroying oldest handler didn't work as expected.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size_t(*std::rbegin(handlers_without_eh1)), std::size_t(eh3.get()),
|
||||
"Destroyed wrong handler.");
|
||||
|
||||
eh3.reset();
|
||||
|
||||
auto const handlers_without_all{c.get_errorhandlers()};
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(handlers_without_all), base_handlers,
|
||||
"Destroying all custom handlers didn't work as expected.");
|
||||
}
|
||||
|
||||
|
||||
void test_errorhandler()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
test_process_notice_calls_errorhandler(conn);
|
||||
test_error_handlers_get_called_newest_to_oldest(conn);
|
||||
test_returning_false_stops_error_handling(conn);
|
||||
test_destroyed_error_handlers_are_not_called(conn);
|
||||
test_destroying_connection_unregisters_handlers();
|
||||
test_get_errorhandlers(conn);
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_errorhandler);
|
||||
} // namespace
|
||||
@@ -0,0 +1,228 @@
|
||||
#include <iostream>
|
||||
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace std::literals;
|
||||
|
||||
|
||||
void compare_esc(
|
||||
pqxx::connection &c, pqxx::transaction_base &t, char const text[])
|
||||
{
|
||||
std::size_t const len{std::size(std::string{text})};
|
||||
PQXX_CHECK_EQUAL(
|
||||
c.esc(std::string_view{text, len}), t.esc(std::string_view{text, len}),
|
||||
"Connection & transaction escape differently.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
t.esc(std::string_view{text, len}), t.esc(text),
|
||||
"Length argument to esc() changes result.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
t.esc(std::string{text}), t.esc(text),
|
||||
"esc(std::string()) differs from esc(char const[]).");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
text,
|
||||
t.query_value<std::string>(
|
||||
"SELECT '" + t.esc(std::string_view{text, len}) + "'"),
|
||||
"esc() is not idempotent.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
t.esc(std::string_view{text, len}), t.esc(text),
|
||||
"Oversized buffer affects esc().");
|
||||
}
|
||||
|
||||
|
||||
void test_esc(pqxx::connection &c, pqxx::transaction_base &t)
|
||||
{
|
||||
PQXX_CHECK_EQUAL(
|
||||
t.esc(std::string_view{"", 0}), "",
|
||||
"Empty string doesn't escape properly.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
t.esc(std::string_view{"'", 1}), "''",
|
||||
"Single quote escaped incorrectly.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
t.esc(std::string_view{"hello"}), "hello", "Trivial escape went wrong.");
|
||||
char const *const escstrings[]{"x", " ", "", nullptr};
|
||||
for (std::size_t i{0}; escstrings[i] != nullptr; ++i)
|
||||
compare_esc(c, t, escstrings[i]);
|
||||
}
|
||||
|
||||
|
||||
void test_quote(pqxx::connection &c, pqxx::transaction_base &t)
|
||||
{
|
||||
PQXX_CHECK_EQUAL(t.quote("x"), "'x'", "Basic quote() fails.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
t.quote(1), "'1'", "quote() not dealing with int properly.");
|
||||
PQXX_CHECK_EQUAL(t.quote(0), "'0'", "Quoting zero is a problem.");
|
||||
char const *const null_ptr{nullptr};
|
||||
PQXX_CHECK_EQUAL(t.quote(null_ptr), "NULL", "Not quoting NULL correctly.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
t.quote(std::string{"'"}), "''''", "Escaping quotes goes wrong.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
t.quote("x"), c.quote("x"),
|
||||
"Connection and transaction quote differently.");
|
||||
|
||||
char const *test_strings[]{"", "x", "\\", "\\\\", "'",
|
||||
"''", "\\'", "\t", "\n", nullptr};
|
||||
|
||||
for (std::size_t i{0}; test_strings[i] != nullptr; ++i)
|
||||
{
|
||||
auto r{t.query_value<std::string>("SELECT " + t.quote(test_strings[i]))};
|
||||
PQXX_CHECK_EQUAL(
|
||||
r, test_strings[i], "Selecting quoted string does not come back equal.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void test_quote_name(pqxx::transaction_base &t)
|
||||
{
|
||||
PQXX_CHECK_EQUAL(
|
||||
"\"A b\"", t.quote_name("A b"), "Escaped identifier is not as expected.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::string{"A b"},
|
||||
t.exec("SELECT 1 AS " + t.quote_name("A b")).column_name(0),
|
||||
"Escaped identifier does not work in SQL.");
|
||||
}
|
||||
|
||||
|
||||
void test_esc_raw_unesc_raw(pqxx::transaction_base &t)
|
||||
{
|
||||
constexpr char binary[]{"1\0023\\4x5"};
|
||||
std::basic_string<std::byte> const data(
|
||||
reinterpret_cast<std::byte const *>(binary), std::size(binary));
|
||||
std::string const escaped{t.esc_raw(
|
||||
std::basic_string_view<std::byte>{std::data(data), std::size(binary)})};
|
||||
|
||||
for (auto const i : escaped)
|
||||
{
|
||||
PQXX_CHECK_GREATER(
|
||||
static_cast<unsigned>(static_cast<unsigned char>(i)), 7u,
|
||||
"Non-ASCII character in escaped data: " + escaped);
|
||||
PQXX_CHECK_LESS(
|
||||
static_cast<unsigned>(static_cast<unsigned char>(i)), 127u,
|
||||
"Non-ASCII character in escaped data: " + escaped);
|
||||
}
|
||||
|
||||
for (auto const i : escaped)
|
||||
PQXX_CHECK(
|
||||
isprint(i), "Unprintable character in escaped data: " + escaped);
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
escaped, "\\x3102335c34783500", "Binary data escaped wrong.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(t.unesc_bin(escaped)), std::size(data),
|
||||
"Wrong size after unescaping.");
|
||||
auto unescaped{t.unesc_bin(escaped)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(unescaped), std::size(data),
|
||||
"Unescaping did not restore original size.");
|
||||
for (std::size_t i{0}; i < std::size(unescaped); ++i)
|
||||
PQXX_CHECK_EQUAL(
|
||||
int(unescaped[i]), int(data[i]),
|
||||
"Unescaping binary data did not restore byte #" + pqxx::to_string(i) +
|
||||
".");
|
||||
}
|
||||
|
||||
|
||||
void test_esc_like(pqxx::transaction_base &tx)
|
||||
{
|
||||
PQXX_CHECK_EQUAL(tx.esc_like(""), "", "esc_like breaks on empty string.");
|
||||
PQXX_CHECK_EQUAL(tx.esc_like("abc"), "abc", "esc_like is broken.");
|
||||
PQXX_CHECK_EQUAL(tx.esc_like("_"), "\\_", "esc_like fails on underscore.");
|
||||
PQXX_CHECK_EQUAL(tx.esc_like("%"), "\\%", "esc_like fails on %.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
tx.esc_like("a%b_c"), "a\\%b\\_c", "esc_like breaks on mix.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
tx.esc_like("_", '+'), "+_", "esc_like ignores escape character.");
|
||||
}
|
||||
|
||||
|
||||
void test_escaping()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
test_esc(conn, tx);
|
||||
test_quote(conn, tx);
|
||||
test_quote_name(tx);
|
||||
test_esc_raw_unesc_raw(tx);
|
||||
test_esc_like(tx);
|
||||
}
|
||||
|
||||
|
||||
void test_esc_escapes_into_buffer()
|
||||
{
|
||||
#if defined(PQXX_HAVE_CONCEPTS)
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
|
||||
std::string buffer;
|
||||
buffer.resize(20);
|
||||
|
||||
auto const text{"Ain't"sv};
|
||||
auto escaped_text{tx.esc(text, buffer)};
|
||||
PQXX_CHECK_EQUAL(escaped_text, "Ain''t", "Escaping into buffer went wrong.");
|
||||
|
||||
std::basic_string<std::byte> const data{std::byte{0x22}, std::byte{0x43}};
|
||||
auto escaped_data(tx.esc(data, buffer));
|
||||
PQXX_CHECK_EQUAL(escaped_data, "\\x2243", "Binary data escaped wrong.");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void test_esc_accepts_various_types()
|
||||
{
|
||||
#if defined(PQXX_HAVE_CONCEPTS) && defined(PQXX_HAVE_SPAN)
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
|
||||
std::string buffer;
|
||||
buffer.resize(20);
|
||||
|
||||
std::string const text{"it's"};
|
||||
auto escaped_text{tx.esc(text, buffer)};
|
||||
PQXX_CHECK_EQUAL(escaped_text, "it''s", "Escaping into buffer went wrong.");
|
||||
|
||||
std::vector<std::byte> const data{std::byte{0x23}, std::byte{0x44}};
|
||||
auto escaped_data(tx.esc(data, buffer));
|
||||
PQXX_CHECK_EQUAL(escaped_data, "\\x2344", "Binary data escaped wrong.");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void test_binary_esc_checks_buffer_length()
|
||||
{
|
||||
#if defined(PQXX_HAVE_CONCEPTS) && defined(PQXX_HAVE_SPAN)
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
|
||||
std::string buf;
|
||||
std::basic_string<std::byte> bin{
|
||||
std::byte{'b'}, std::byte{'o'}, std::byte{'o'}};
|
||||
|
||||
buf.resize(2 * std::size(bin) + 3);
|
||||
pqxx::ignore_unused(tx.esc(bin, buf));
|
||||
PQXX_CHECK_EQUAL(int{buf[0]}, int{'\\'}, "Unexpected binary escape format.");
|
||||
PQXX_CHECK_NOT_EQUAL(
|
||||
int(buf[std::size(buf) - 2]), int('\0'), "Escaped binary ends too soon.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
int(buf[std::size(buf) - 1]), int('\0'), "Terminating zero is missing.");
|
||||
|
||||
buf.resize(2 * std::size(bin) + 2);
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::ignore_unused(tx.esc(bin, buf)), pqxx::range_error,
|
||||
"Didn't get expected exception from escape overrun.");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_escaping);
|
||||
PQXX_REGISTER_TEST(test_esc_escapes_into_buffer);
|
||||
PQXX_REGISTER_TEST(test_esc_accepts_various_types);
|
||||
PQXX_REGISTER_TEST(test_binary_esc_checks_buffer_length);
|
||||
} // namespace
|
||||
@@ -0,0 +1,45 @@
|
||||
#include <pqxx/except>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_exceptions()
|
||||
{
|
||||
std::string const broken_query{"SELECT HORRIBLE ERROR"},
|
||||
err{"Error message"};
|
||||
|
||||
try
|
||||
{
|
||||
throw pqxx::sql_error{err, broken_query};
|
||||
}
|
||||
catch (std::exception const &e)
|
||||
{
|
||||
PQXX_CHECK_EQUAL(e.what(), err, "Exception contains wrong message.");
|
||||
auto downcast{dynamic_cast<pqxx::sql_error const *>(&e)};
|
||||
PQXX_CHECK(
|
||||
downcast != nullptr, "exception-to-sql_error downcast is broken.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
downcast->query(), broken_query,
|
||||
"Getting query from pqxx exception is broken.");
|
||||
}
|
||||
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
try
|
||||
{
|
||||
tx.exec("INVALID QUERY HERE");
|
||||
}
|
||||
catch (pqxx::syntax_error const &e)
|
||||
{
|
||||
// SQL syntax error has sqlstate error 42601.
|
||||
PQXX_CHECK_EQUAL(
|
||||
e.sqlstate(), "42601", "Unexpected sqlstate on syntax error.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_exceptions);
|
||||
} // namespace
|
||||
@@ -0,0 +1,57 @@
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_field()
|
||||
{
|
||||
pqxx::connection c;
|
||||
pqxx::work tx{c};
|
||||
auto const r1{tx.exec1("SELECT 9")};
|
||||
auto const &f1{r1[0]};
|
||||
|
||||
PQXX_CHECK_EQUAL(f1.as<std::string>(), "9", "as<string>() is broken.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
f1.as<std::string>("z"), "9", "as<string>(string) is broken.");
|
||||
|
||||
PQXX_CHECK_EQUAL(f1.as<int>(), 9, "as<int>() is broken.");
|
||||
PQXX_CHECK_EQUAL(f1.as<int>(10), 9, "as<int>(int) is broken.");
|
||||
|
||||
std::string s;
|
||||
PQXX_CHECK(f1.to(s), "to(string) failed.");
|
||||
PQXX_CHECK_EQUAL(s, "9", "to(string) is broken.");
|
||||
s = "x";
|
||||
PQXX_CHECK(f1.to(s, std::string{"7"}), "to(string, string) failed.");
|
||||
PQXX_CHECK_EQUAL(s, "9", "to(string, string) is broken.");
|
||||
|
||||
int i{};
|
||||
PQXX_CHECK(f1.to(i), "to(int) failed.");
|
||||
PQXX_CHECK_EQUAL(i, 9, "to(int) is broken.");
|
||||
i = 8;
|
||||
PQXX_CHECK(f1.to(i, 12), "to(int, int) failed.");
|
||||
PQXX_CHECK_EQUAL(i, 9, "to(int, int) is broken.");
|
||||
|
||||
auto const r2{tx.exec1("SELECT NULL")};
|
||||
auto const f2{r2[0]};
|
||||
i = 100;
|
||||
PQXX_CHECK_THROWS(
|
||||
f2.as<int>(), pqxx::conversion_error, "Null conversion failed to throw.");
|
||||
PQXX_CHECK_EQUAL(i, 100, "Null conversion touched its output.");
|
||||
|
||||
PQXX_CHECK_EQUAL(f2.as<int>(66), 66, "as<int> default is broken.");
|
||||
|
||||
PQXX_CHECK(!(f2.to(i)), "to(int) failed to report a null.");
|
||||
PQXX_CHECK(!(f2.to(i, 54)), "to(int, int) failed to report a null.");
|
||||
PQXX_CHECK_EQUAL(i, 54, "to(int, int) failed to default.");
|
||||
|
||||
auto const r3{tx.exec("SELECT generate_series(1, 5)")};
|
||||
PQXX_CHECK_EQUAL(r3.at(3, 0).as<int>(), 4, "Two-argument at() went wrong.");
|
||||
#if defined(PQXX_HAVE_MULTIDIMENSIONAL_SUBSCRIPT)
|
||||
PQXX_CHECK_EQUAL((r3[3, 0].as<int>()), 4, "Two-argument [] went wrong.");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_field);
|
||||
} // namespace
|
||||
@@ -0,0 +1,175 @@
|
||||
#include <cmath>
|
||||
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
/// Test conversions for some floating-point type.
|
||||
template<typename T> void infinity_test()
|
||||
{
|
||||
T inf{std::numeric_limits<T>::infinity()};
|
||||
std::string inf_string;
|
||||
T back_conversion;
|
||||
|
||||
inf_string = pqxx::to_string(inf);
|
||||
pqxx::from_string(inf_string, back_conversion);
|
||||
PQXX_CHECK_LESS(
|
||||
T(999999999), back_conversion,
|
||||
"Infinity doesn't convert back to something huge.");
|
||||
|
||||
inf_string = pqxx::to_string(-inf);
|
||||
pqxx::from_string(inf_string, back_conversion);
|
||||
PQXX_CHECK_LESS(
|
||||
back_conversion, -T(999999999), "Negative infinity is broken");
|
||||
}
|
||||
|
||||
void test_infinities()
|
||||
{
|
||||
infinity_test<float>();
|
||||
infinity_test<double>();
|
||||
infinity_test<long double>();
|
||||
}
|
||||
|
||||
|
||||
/// Reproduce bug #262: repeated float conversions break without charconv.
|
||||
template<typename T> void bug_262()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
conn.prepare("stmt", "select cast($1 as float)");
|
||||
pqxx::work tr{conn};
|
||||
|
||||
// We must use the same float type both for passing the value to the
|
||||
// statement and for retrieving result of the statement execution. This is
|
||||
// due to an internal stringstream being instantiated as a a parameterized
|
||||
// thread-local singleton. So, there are separate stream<float>,
|
||||
// stream<double>, stream<long double>, but every such instance is a
|
||||
// singleton. We should use only one of them for this test.
|
||||
|
||||
pqxx::row row;
|
||||
|
||||
// Nothing bad here, select a float value.
|
||||
// The stream is clear, so just fill it with the value and extract str().
|
||||
row = tr.exec1("SELECT 1.0");
|
||||
|
||||
// This works properly, but as we parse the value from the stream, the
|
||||
// seeking cursor moves towards the EOF. When the inevitable EOF happens
|
||||
// 'eof' flag is set in the stream and 'good' flag is unset.
|
||||
row[0].as<T>();
|
||||
|
||||
// The second try. Select a float value again. The stream is not clean, so
|
||||
// we need to put an empty string into its buffer {stream.str("");}. This
|
||||
// resets the seeking cursor to 0. Then we will put the value using
|
||||
// operator<<().
|
||||
// ...
|
||||
// ...
|
||||
// OOPS. stream.str("") does not reset 'eof' flag and 'good' flag! We are
|
||||
// trying to read from EOF! This is no good.
|
||||
// Throws on unpatched pqxx v6.4.5
|
||||
row = tr.exec1("SELECT 2.0");
|
||||
|
||||
// We won't get here without patch. The following statements are just for
|
||||
// demonstration of how are intended to work. If we
|
||||
// simply just reset the stream flags properly, this would work fine.
|
||||
// The most obvious patch is just explicitly stream.seekg(0).
|
||||
row[0].as<T>();
|
||||
row = tr.exec1("SELECT 3.0");
|
||||
row[0].as<T>();
|
||||
}
|
||||
|
||||
|
||||
/// Test for bug #262.
|
||||
void test_bug_262()
|
||||
{
|
||||
bug_262<float>();
|
||||
bug_262<double>();
|
||||
bug_262<long double>();
|
||||
}
|
||||
|
||||
|
||||
/// Test conversion of malformed floating-point values.
|
||||
void test_bad_float()
|
||||
{
|
||||
float x [[maybe_unused]];
|
||||
PQXX_CHECK_THROWS(
|
||||
x = pqxx::from_string<float>(""), pqxx::conversion_error,
|
||||
"Conversion of empty string to float was not caught.");
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
x = pqxx::from_string<float>("Infancy"), pqxx::conversion_error,
|
||||
"Misleading infinity was not caught.");
|
||||
PQXX_CHECK_THROWS(
|
||||
x = pqxx::from_string<float>("-Infighting"), pqxx::conversion_error,
|
||||
"Misleading negative infinity was not caught.");
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
x = pqxx::from_string<float>("Nanny"), pqxx::conversion_error,
|
||||
"Conversion of misleading NaN was not caught.");
|
||||
}
|
||||
|
||||
|
||||
template<typename T> void test_float_length(T value)
|
||||
{
|
||||
auto const text{pqxx::to_string(value)};
|
||||
PQXX_CHECK_GREATER_EQUAL(
|
||||
pqxx::size_buffer(value), std::size(text) + 1,
|
||||
"Not enough buffer space for " + text + ".");
|
||||
}
|
||||
|
||||
|
||||
/// Test conversion of long float values to strings.
|
||||
void test_long_float()
|
||||
{
|
||||
test_float_length(0.1f);
|
||||
test_float_length(0.1);
|
||||
|
||||
test_float_length(std::numeric_limits<float>::denorm_min());
|
||||
test_float_length(-std::numeric_limits<float>::denorm_min());
|
||||
test_float_length(std::numeric_limits<float>::min());
|
||||
test_float_length(-std::numeric_limits<float>::min());
|
||||
test_float_length(std::numeric_limits<float>::max());
|
||||
test_float_length(-std::numeric_limits<float>::max());
|
||||
test_float_length(-std::nextafter(1.0f, 2.0f));
|
||||
|
||||
test_float_length(std::numeric_limits<double>::denorm_min());
|
||||
test_float_length(-std::numeric_limits<double>::denorm_min());
|
||||
test_float_length(std::numeric_limits<double>::min());
|
||||
test_float_length(-std::numeric_limits<double>::min());
|
||||
test_float_length(std::numeric_limits<double>::max());
|
||||
test_float_length(-std::numeric_limits<double>::max());
|
||||
test_float_length(-std::nextafter(1.0, 2.0));
|
||||
|
||||
test_float_length(std::numeric_limits<long double>::denorm_min());
|
||||
test_float_length(-std::numeric_limits<long double>::denorm_min());
|
||||
test_float_length(std::numeric_limits<long double>::min());
|
||||
test_float_length(-std::numeric_limits<long double>::min());
|
||||
test_float_length(std::numeric_limits<long double>::max());
|
||||
test_float_length(-std::numeric_limits<long double>::max());
|
||||
test_float_length(-std::nextafter(1.0L, 2.0L));
|
||||
|
||||
// Ahem. I'm not proud of this. We really can't assume much about the
|
||||
// floating-point types, but I'd really like to try a few things to see that
|
||||
// buffer sizes are in the right ballpark. So, if "double" is at least 64
|
||||
// bits, check for some examples of long conversions.
|
||||
if constexpr (sizeof(double) >= 8)
|
||||
{
|
||||
auto constexpr awkward{-2.2250738585072014e-308};
|
||||
auto const text{pqxx::to_string(awkward)};
|
||||
PQXX_CHECK_LESS_EQUAL(
|
||||
std::size(text), 25u, text + " converted to too long a string.");
|
||||
}
|
||||
if constexpr (sizeof(double) <= 8)
|
||||
{
|
||||
auto const text{pqxx::to_string(0.99)};
|
||||
PQXX_CHECK_LESS_EQUAL(
|
||||
pqxx::size_buffer(0.99), 25u, text + " converted to too long a string.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_infinities);
|
||||
PQXX_REGISTER_TEST(test_bug_262);
|
||||
PQXX_REGISTER_TEST(test_bad_float);
|
||||
PQXX_REGISTER_TEST(test_long_float);
|
||||
} // namespace
|
||||
@@ -0,0 +1,58 @@
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include <pqxx/largeobject>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_stream_large_object()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
|
||||
// Construct a really nasty string. (Don't just construct a std::string from
|
||||
// a char[] constant, because it'll terminate at the embedded zero.)
|
||||
//
|
||||
// The crucial thing is the "ff" byte at the beginning. It tests for
|
||||
// possible conflation between "eof" (-1) and a char which just happens to
|
||||
// have the same bit pattern as an 8-bit value of -1. This conflation can be
|
||||
// a problem when it occurs at buffer boundaries.
|
||||
constexpr char bytes[]{"\xff\0end"};
|
||||
std::string const contents{bytes, std::size(bytes)};
|
||||
|
||||
pqxx::work tx{conn};
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
pqxx::largeobject new_obj{tx};
|
||||
|
||||
pqxx::olostream write{tx, new_obj};
|
||||
write << contents;
|
||||
write.flush();
|
||||
|
||||
pqxx::largeobjectaccess check{tx, new_obj, std::ios::in | std::ios::binary};
|
||||
std::array<char, 50> buf;
|
||||
std::size_t const len{
|
||||
static_cast<std::size_t>(check.read(std::data(buf), std::size(buf)))};
|
||||
PQXX_CHECK_EQUAL(len, std::size(contents), "olostream truncated data.");
|
||||
std::string const check_str{std::data(buf), len};
|
||||
PQXX_CHECK_EQUAL(check_str, contents, "olostream mangled data.");
|
||||
|
||||
pqxx::ilostream read{tx, new_obj};
|
||||
std::string read_back;
|
||||
std::string chunk;
|
||||
while (read >> chunk) read_back += chunk;
|
||||
|
||||
new_obj.remove(tx);
|
||||
|
||||
PQXX_CHECK_EQUAL(read_back, contents, "Got wrong data from ilostream.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(read_back), std::size(contents), "ilostream truncated data.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(read_back), std::size(bytes), "ilostream truncated data.");
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_stream_large_object);
|
||||
} // namespace
|
||||
@@ -0,0 +1,27 @@
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include <pqxx/internal/wait.hxx>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_nonblocking_connect()
|
||||
{
|
||||
pqxx::connecting nbc;
|
||||
while (not nbc.done())
|
||||
{
|
||||
pqxx::internal::wait_fd(
|
||||
nbc.sock(), nbc.wait_to_read(), nbc.wait_to_write());
|
||||
nbc.process();
|
||||
}
|
||||
|
||||
pqxx::connection conn{std::move(nbc).produce()};
|
||||
pqxx::work tx{conn};
|
||||
PQXX_CHECK_EQUAL(tx.query_value<int>("SELECT 10"), 10, "Bad value!?");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_nonblocking_connect);
|
||||
} // namespace
|
||||
@@ -0,0 +1,86 @@
|
||||
#include <chrono>
|
||||
|
||||
#include <pqxx/internal/header-pre.hxx>
|
||||
|
||||
#include <pqxx/internal/wait.hxx>
|
||||
|
||||
#include <pqxx/internal/header-post.hxx>
|
||||
|
||||
#include <pqxx/nontransaction>
|
||||
#include <pqxx/notification>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
class TestReceiver final : public pqxx::notification_receiver
|
||||
{
|
||||
public:
|
||||
std::string payload;
|
||||
int backend_pid;
|
||||
|
||||
TestReceiver(pqxx::connection &c, std::string const &channel_name) :
|
||||
pqxx::notification_receiver(c, channel_name),
|
||||
payload(),
|
||||
backend_pid(0)
|
||||
{}
|
||||
|
||||
virtual void
|
||||
operator()(std::string const &payload_string, int backend) override
|
||||
{
|
||||
this->payload = payload_string;
|
||||
this->backend_pid = backend;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void test_receive(
|
||||
pqxx::transaction_base &t, std::string const &channel,
|
||||
char const payload[] = nullptr)
|
||||
{
|
||||
pqxx::connection &conn(t.conn());
|
||||
|
||||
std::string SQL{"NOTIFY \"" + channel + "\""};
|
||||
if (payload != nullptr)
|
||||
SQL += ", " + t.quote(payload);
|
||||
|
||||
TestReceiver receiver{t.conn(), channel};
|
||||
|
||||
// Clear out any previously pending notifications that might otherwise
|
||||
// confuse the test.
|
||||
conn.get_notifs();
|
||||
|
||||
// Notify, and receive.
|
||||
t.exec(SQL);
|
||||
t.commit();
|
||||
|
||||
int notifs{0};
|
||||
for (int i{0}; (i < 10) and (notifs == 0);
|
||||
++i, pqxx::internal::wait_for(1'000'000u))
|
||||
notifs = conn.get_notifs();
|
||||
|
||||
PQXX_CHECK_EQUAL(notifs, 1, "Got wrong number of notifications.");
|
||||
PQXX_CHECK_EQUAL(receiver.backend_pid, conn.backendpid(), "Bad pid.");
|
||||
if (payload == nullptr)
|
||||
PQXX_CHECK(std::empty(receiver.payload), "Unexpected payload.");
|
||||
else
|
||||
PQXX_CHECK_EQUAL(receiver.payload, payload, "Bad payload.");
|
||||
}
|
||||
|
||||
|
||||
void test_notification()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
TestReceiver receiver(conn, "mychannel");
|
||||
PQXX_CHECK_EQUAL(receiver.channel(), "mychannel", "Bad channel.");
|
||||
|
||||
pqxx::work tx{conn};
|
||||
test_receive(tx, "channel1");
|
||||
|
||||
pqxx::nontransaction u(conn);
|
||||
test_receive(u, "channel2", "payload");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_notification);
|
||||
} // namespace
|
||||
@@ -0,0 +1,64 @@
|
||||
#include <chrono>
|
||||
|
||||
#include <pqxx/pipeline>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_pipeline()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
|
||||
// A pipeline grabs transaction focus, blocking regular queries and such.
|
||||
pqxx::pipeline pipe(tx, "test_pipeline_detach");
|
||||
PQXX_CHECK_THROWS(
|
||||
tx.exec("SELECT 1"), std::logic_error,
|
||||
"Pipeline does not block regular queries");
|
||||
|
||||
// Flushing a pipeline relinquishes transaction focus.
|
||||
pipe.flush();
|
||||
auto r{tx.exec("SELECT 2")};
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(r), 1, "Wrong query result after flushing pipeline.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
r[0][0].as<int>(), 2, "Query returns wrong data after flushing pipeline.");
|
||||
|
||||
// Inserting a query makes the pipeline grab transaction focus back.
|
||||
auto q{pipe.insert("SELECT 2")};
|
||||
PQXX_CHECK_THROWS(
|
||||
tx.exec("SELECT 3"), std::logic_error,
|
||||
"Pipeline does not block regular queries");
|
||||
|
||||
// Invoking complete() also detaches the pipeline from the transaction.
|
||||
pipe.complete();
|
||||
r = tx.exec("SELECT 4");
|
||||
PQXX_CHECK_EQUAL(std::size(r), 1, "Wrong query result after complete().");
|
||||
PQXX_CHECK_EQUAL(
|
||||
r[0][0].as<int>(), 4, "Query returns wrong data after complete().");
|
||||
|
||||
// The complete() also received any pending query results from the backend.
|
||||
r = pipe.retrieve(q);
|
||||
PQXX_CHECK_EQUAL(std::size(r), 1, "Wrong result from pipeline.");
|
||||
PQXX_CHECK_EQUAL(r[0][0].as<int>(), 2, "Pipeline returned wrong data.");
|
||||
|
||||
// We can cancel while the pipe is empty, and things will still work.
|
||||
pipe.cancel();
|
||||
|
||||
// Issue a query and cancel it. Measure time to see that we don't really
|
||||
// wait.
|
||||
using clock = std::chrono::steady_clock;
|
||||
auto const start{clock::now()};
|
||||
pipe.retain(0);
|
||||
pipe.insert("pg_sleep(10)");
|
||||
pipe.cancel();
|
||||
auto const finish{clock::now()};
|
||||
auto const seconds{
|
||||
std::chrono::duration_cast<std::chrono::seconds>(finish - start).count()};
|
||||
PQXX_CHECK_LESS(seconds, 5, "Canceling a sleep took suspiciously long.");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
PQXX_REGISTER_TEST(test_pipeline);
|
||||
@@ -0,0 +1,334 @@
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <list>
|
||||
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
// Test program for libpqxx. Define and use prepared statements.
|
||||
|
||||
#define COMPARE_RESULTS(name, lhs, rhs) \
|
||||
PQXX_CHECK_EQUAL( \
|
||||
rhs, lhs, \
|
||||
"Executing " name " as prepared statement yields different results.");
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace std::literals;
|
||||
|
||||
|
||||
template<typename T> std::string stringize(pqxx::transaction_base &t, T i)
|
||||
{
|
||||
return stringize(t, pqxx::to_string(i));
|
||||
}
|
||||
|
||||
|
||||
// Substitute variables in raw query. This is not likely to be very robust,
|
||||
// but it should do for just this test. The main shortcomings are escaping,
|
||||
// and not knowing when to quote the variables.
|
||||
// Note we do the replacement backwards (meaning forward_only iterators won't
|
||||
// do!) to avoid substituting e.g. "$12" as "$1" first.
|
||||
template<typename ITER>
|
||||
std::string
|
||||
subst(pqxx::transaction_base &t, std::string q, ITER patbegin, ITER patend)
|
||||
{
|
||||
ptrdiff_t i{distance(patbegin, patend)};
|
||||
for (ITER arg{patend}; i > 0; --i)
|
||||
{
|
||||
--arg;
|
||||
std::string const marker{"$" + pqxx::to_string(i)},
|
||||
var{stringize(t, *arg)};
|
||||
std::string::size_type const msz{std::size(marker)};
|
||||
while (q.find(marker) != std::string::npos)
|
||||
q.replace(q.find(marker), msz, var);
|
||||
}
|
||||
return q;
|
||||
}
|
||||
|
||||
|
||||
template<typename CNTNR>
|
||||
std::string
|
||||
subst(pqxx::transaction_base &t, std::string const &q, CNTNR const &patterns)
|
||||
{
|
||||
return subst(t, q, std::begin(patterns), std::end(patterns));
|
||||
}
|
||||
|
||||
|
||||
void test_registration_and_invocation()
|
||||
{
|
||||
constexpr auto count_to_5{"SELECT * FROM generate_series(1, 5)"};
|
||||
|
||||
pqxx::connection c;
|
||||
pqxx::work tx1{c};
|
||||
|
||||
// Prepare a simple statement.
|
||||
tx1.conn().prepare("CountToFive", count_to_5);
|
||||
|
||||
// The statement returns exactly what you'd expect.
|
||||
COMPARE_RESULTS(
|
||||
"CountToFive", tx1.exec_prepared("CountToFive"), tx1.exec(count_to_5));
|
||||
|
||||
// Re-preparing it is an error.
|
||||
PQXX_CHECK_THROWS(
|
||||
tx1.conn().prepare("CountToFive", count_to_5), pqxx::sql_error,
|
||||
"Did not report re-definition of prepared statement.");
|
||||
|
||||
tx1.abort();
|
||||
pqxx::work tx2{c};
|
||||
|
||||
// Executing a nonexistent prepared statement is also an error.
|
||||
PQXX_CHECK_THROWS(
|
||||
tx2.exec_prepared("NonexistentStatement"), pqxx::sql_error,
|
||||
"Did not report invocation of nonexistent prepared statement.");
|
||||
}
|
||||
|
||||
|
||||
void test_basic_args()
|
||||
{
|
||||
pqxx::connection c;
|
||||
c.prepare("EchoNum", "SELECT $1::int");
|
||||
pqxx::work tx{c};
|
||||
auto r{tx.exec_prepared("EchoNum", 7)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(r), 1, "Did not get 1 row from prepared statement.");
|
||||
PQXX_CHECK_EQUAL(std::size(r.front()), 1, "Did not get exactly one column.");
|
||||
PQXX_CHECK_EQUAL(r[0][0].as<int>(), 7, "Got wrong result.");
|
||||
|
||||
auto rw{tx.exec_prepared1("EchoNum", 8)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(rw), 1, "Did not get 1 column from exec_prepared1.");
|
||||
PQXX_CHECK_EQUAL(rw[0].as<int>(), 8, "Got wrong result.");
|
||||
}
|
||||
|
||||
|
||||
void test_multiple_params()
|
||||
{
|
||||
pqxx::connection c;
|
||||
c.prepare("CountSeries", "SELECT * FROM generate_series($1::int, $2::int)");
|
||||
pqxx::work tx{c};
|
||||
auto r{tx.exec_prepared_n(4, "CountSeries", 7, 10)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(r), 4, "Wrong number of rows, but no error raised.");
|
||||
PQXX_CHECK_EQUAL(r.front().front().as<int>(), 7, "Wrong $1.");
|
||||
PQXX_CHECK_EQUAL(r.back().front().as<int>(), 10, "Wrong $2.");
|
||||
|
||||
c.prepare("Reversed", "SELECT * FROM generate_series($2::int, $1::int)");
|
||||
r = tx.exec_prepared_n(3, "Reversed", 8, 6);
|
||||
PQXX_CHECK_EQUAL(
|
||||
r.front().front().as<int>(), 6, "Did parameters get reordered?");
|
||||
PQXX_CHECK_EQUAL(
|
||||
r.back().front().as<int>(), 8, "$2 did not come through properly.");
|
||||
}
|
||||
|
||||
|
||||
void test_nulls()
|
||||
{
|
||||
pqxx::connection c;
|
||||
pqxx::work tx{c};
|
||||
c.prepare("EchoStr", "SELECT $1::varchar");
|
||||
auto rw{tx.exec_prepared1("EchoStr", nullptr)};
|
||||
PQXX_CHECK(rw.front().is_null(), "nullptr did not translate to null.");
|
||||
|
||||
char const *n{nullptr};
|
||||
rw = tx.exec_prepared1("EchoStr", n);
|
||||
PQXX_CHECK(rw.front().is_null(), "Null pointer did not translate to null.");
|
||||
}
|
||||
|
||||
|
||||
void test_strings()
|
||||
{
|
||||
pqxx::connection c;
|
||||
pqxx::work tx{c};
|
||||
c.prepare("EchoStr", "SELECT $1::varchar");
|
||||
auto rw{tx.exec_prepared1("EchoStr", "foo")};
|
||||
PQXX_CHECK_EQUAL(
|
||||
rw.front().as<std::string>(), "foo", "Wrong string result.");
|
||||
|
||||
char const nasty_string[]{R"--('\"\)--"};
|
||||
rw = tx.exec_prepared1("EchoStr", nasty_string);
|
||||
PQXX_CHECK_EQUAL(
|
||||
rw.front().as<std::string>(), std::string(nasty_string),
|
||||
"Prepared statement did not quote/escape correctly.");
|
||||
|
||||
rw = tx.exec_prepared1("EchoStr", std::string{nasty_string});
|
||||
PQXX_CHECK_EQUAL(
|
||||
rw.front().as<std::string>(), std::string(nasty_string),
|
||||
"Quoting/escaping went wrong in std::string.");
|
||||
|
||||
char nonconst[]{"non-const C string"};
|
||||
rw = tx.exec_prepared1("EchoStr", nonconst);
|
||||
PQXX_CHECK_EQUAL(
|
||||
rw.front().as<std::string>(), std::string(nonconst),
|
||||
"Non-const C string passed incorrectly.");
|
||||
}
|
||||
|
||||
|
||||
void test_binary()
|
||||
{
|
||||
pqxx::connection c;
|
||||
pqxx::work tx{c};
|
||||
c.prepare("EchoBin", "SELECT $1::bytea");
|
||||
constexpr char raw_bytes[]{"Binary\0bytes'\"with\tweird\xff bytes"};
|
||||
std::string const input{raw_bytes, std::size(raw_bytes)};
|
||||
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
{
|
||||
pqxx::binarystring const bin{input};
|
||||
auto rw{tx.exec_prepared1("EchoBin", bin)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::binarystring(rw[0]).str(), input,
|
||||
"Binary string came out damaged.");
|
||||
}
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
|
||||
{
|
||||
std::basic_string<std::byte> bytes{
|
||||
reinterpret_cast<std::byte const *>(raw_bytes), std::size(raw_bytes)};
|
||||
auto bp{tx.exec_prepared1("EchoBin", bytes)};
|
||||
auto bval{bp[0].as<std::basic_string<std::byte>>()};
|
||||
PQXX_CHECK_EQUAL(
|
||||
(std::string_view{
|
||||
reinterpret_cast<char const *>(bval.c_str()), std::size(bval)}),
|
||||
input, "Binary string parameter went wrong.");
|
||||
}
|
||||
|
||||
// Now try it with a complex type that ultimately uses the conversions of
|
||||
// std::basic_string<std::byte>, but complex enough that the call may
|
||||
// convert the data to a text string on the libpqxx side. Which would be
|
||||
// okay, except of course it's likely to be slower.
|
||||
|
||||
{
|
||||
auto ptr{std::make_shared<std::basic_string<std::byte>>(
|
||||
reinterpret_cast<std::byte const *>(raw_bytes), std::size(raw_bytes))};
|
||||
auto rp{tx.exec_prepared1("EchoBin", ptr)};
|
||||
auto pval{rp[0].as<std::basic_string<std::byte>>()};
|
||||
PQXX_CHECK_EQUAL(
|
||||
(std::string_view{
|
||||
reinterpret_cast<char const *>(pval.c_str()), std::size(pval)}),
|
||||
input, "Binary string as shared_ptr-to-optional went wrong.");
|
||||
}
|
||||
|
||||
{
|
||||
auto opt{std::optional<std::basic_string<std::byte>>{
|
||||
std::in_place, reinterpret_cast<std::byte const *>(raw_bytes),
|
||||
std::size(raw_bytes)}};
|
||||
auto op{tx.exec_prepared1("EchoBin", opt)};
|
||||
auto oval{op[0].as<std::basic_string<std::byte>>()};
|
||||
PQXX_CHECK_EQUAL(
|
||||
(std::string_view{
|
||||
reinterpret_cast<char const *>(oval.c_str()), std::size(oval)}),
|
||||
input, "Binary string as shared_ptr-to-optional went wrong.");
|
||||
}
|
||||
|
||||
#if defined(PQXX_HAVE_CONCEPTS)
|
||||
// By the way, it doesn't have to be a std::basic_string. Any contiguous
|
||||
// range will do.
|
||||
{
|
||||
std::vector<std::byte> data{std::byte{'x'}, std::byte{'v'}};
|
||||
auto op{tx.exec_prepared1("EchoBin", data)};
|
||||
auto oval{op[0].as<std::basic_string<std::byte>>()};
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(oval), 2u, "Binary data came back as wrong length.");
|
||||
PQXX_CHECK_EQUAL(static_cast<int>(oval[0]), int('x'), "Wrong data.");
|
||||
PQXX_CHECK_EQUAL(static_cast<int>(oval[1]), int('v'), "Wrong data.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void test_params()
|
||||
{
|
||||
pqxx::connection c;
|
||||
pqxx::work tx{c};
|
||||
c.prepare("Concat2Numbers", "SELECT 10 * $1 + $2");
|
||||
std::vector<int> values{3, 9};
|
||||
pqxx::params params;
|
||||
params.reserve(std::size(values));
|
||||
params.append_multi(values);
|
||||
|
||||
auto const rw39{tx.exec_prepared1("Concat2Numbers", params)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
rw39.front().as<int>(), 39,
|
||||
"Dynamic prepared-statement parameters went wrong.");
|
||||
|
||||
c.prepare("Concat4Numbers", "SELECT 1000*$1 + 100*$2 + 10*$3 + $4");
|
||||
auto const rw1396{tx.exec_prepared1("Concat4Numbers", 1, params, 6)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
rw1396.front().as<int>(), 1396,
|
||||
"Dynamic params did not interleave with static ones properly.");
|
||||
}
|
||||
|
||||
|
||||
void test_optional()
|
||||
{
|
||||
pqxx::connection c;
|
||||
pqxx::work tx{c};
|
||||
c.prepare("EchoNum", "SELECT $1::int");
|
||||
pqxx::row rw{
|
||||
tx.exec_prepared1("EchoNum", std::optional<int>{std::in_place, 10})};
|
||||
PQXX_CHECK_EQUAL(
|
||||
rw.front().as<int>(), 10,
|
||||
"optional (with value) did not return the right value.");
|
||||
|
||||
rw = tx.exec_prepared1("EchoNum", std::optional<int>{});
|
||||
PQXX_CHECK(
|
||||
rw.front().is_null(), "optional without value did not come out as null.");
|
||||
}
|
||||
|
||||
|
||||
void test_prepared_statements()
|
||||
{
|
||||
test_registration_and_invocation();
|
||||
test_basic_args();
|
||||
test_multiple_params();
|
||||
test_nulls();
|
||||
test_strings();
|
||||
test_binary();
|
||||
test_params();
|
||||
|
||||
test_optional();
|
||||
}
|
||||
|
||||
|
||||
void test_placeholders_generates_names()
|
||||
{
|
||||
using pqxx::operator""_zv;
|
||||
pqxx::placeholders name;
|
||||
PQXX_CHECK_EQUAL(name.view(), "$1"_zv, "Bad placeholders initial zview.");
|
||||
PQXX_CHECK_EQUAL(name.view(), "$1"sv, "Bad placeholders string_view.");
|
||||
PQXX_CHECK_EQUAL(name.get(), "$1", "Bad placeholders::get().");
|
||||
|
||||
name.next();
|
||||
PQXX_CHECK_EQUAL(name.view(), "$2"_zv, "Incorrect placeholders::next().");
|
||||
|
||||
name.next();
|
||||
PQXX_CHECK_EQUAL(name.view(), "$3"_zv, "Incorrect placeholders::next().");
|
||||
name.next();
|
||||
PQXX_CHECK_EQUAL(name.view(), "$4"_zv, "Incorrect placeholders::next().");
|
||||
name.next();
|
||||
PQXX_CHECK_EQUAL(name.view(), "$5"_zv, "Incorrect placeholders::next().");
|
||||
name.next();
|
||||
PQXX_CHECK_EQUAL(name.view(), "$6"_zv, "Incorrect placeholders::next().");
|
||||
name.next();
|
||||
PQXX_CHECK_EQUAL(name.view(), "$7"_zv, "Incorrect placeholders::next().");
|
||||
name.next();
|
||||
PQXX_CHECK_EQUAL(name.view(), "$8"_zv, "Incorrect placeholders::next().");
|
||||
name.next();
|
||||
PQXX_CHECK_EQUAL(name.view(), "$9"_zv, "Incorrect placeholders::next().");
|
||||
name.next();
|
||||
PQXX_CHECK_EQUAL(name.view(), "$10"_zv, "Incorrect placeholders carry.");
|
||||
name.next();
|
||||
PQXX_CHECK_EQUAL(name.view(), "$11"_zv, "Incorrect placeholders 11.");
|
||||
|
||||
while (name.count() < 999) name.next();
|
||||
PQXX_CHECK_EQUAL(name.view(), "$999"_zv, "Incorrect placeholders 999.");
|
||||
name.next();
|
||||
PQXX_CHECK_EQUAL(name.view(), "$1000"_zv, "Incorrect large placeholder.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_prepared_statements);
|
||||
PQXX_REGISTER_TEST(test_placeholders_generates_names);
|
||||
} // namespace
|
||||
@@ -0,0 +1,555 @@
|
||||
#include <pqxx/range>
|
||||
#include <pqxx/strconv>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_range_construct()
|
||||
{
|
||||
using optint = std::optional<int>;
|
||||
using oibound = pqxx::inclusive_bound<std::optional<int>>;
|
||||
using oxbound = pqxx::inclusive_bound<std::optional<int>>;
|
||||
PQXX_CHECK_THROWS(
|
||||
(pqxx::range<optint>{oibound{optint{}}, oibound{optint{}}}),
|
||||
pqxx::argument_error, "Inclusive bound accepted a null.");
|
||||
PQXX_CHECK_THROWS(
|
||||
(pqxx::range<optint>{oxbound{optint{}}, oxbound{optint{}}}),
|
||||
pqxx::argument_error, "Exclusive bound accepted a null.");
|
||||
|
||||
using ibound = pqxx::inclusive_bound<int>;
|
||||
PQXX_CHECK_THROWS(
|
||||
(pqxx::range<int>{ibound{1}, ibound{0}}), pqxx::range_error,
|
||||
"Range constructor accepted backwards range.");
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
(pqxx::range<float>{
|
||||
pqxx::inclusive_bound<float>{-1000.0},
|
||||
pqxx::inclusive_bound<float>{-std::numeric_limits<float>::infinity()}}),
|
||||
pqxx::range_error,
|
||||
"Was able to construct range with infinity bound at the wrong end.");
|
||||
}
|
||||
|
||||
|
||||
void test_range_equality()
|
||||
{
|
||||
using range = pqxx::range<int>;
|
||||
using ibound = pqxx::inclusive_bound<int>;
|
||||
using xbound = pqxx::exclusive_bound<int>;
|
||||
using ubound = pqxx::no_bound;
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
range{}, range{}, "Default-constructed range is not consistent.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
(range{xbound{0}, xbound{0}}), (range{xbound{5}, xbound{5}}),
|
||||
"Empty ranges at different values are not equal.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
(range{ubound{}, ubound{}}), (range{ubound{}, ubound{}}),
|
||||
"Universal range is inconsistent.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
(range{ibound{5}, ibound{8}}), (range{ibound{5}, ibound{8}}),
|
||||
"Inclusive range is inconsistent.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
(range{xbound{5}, xbound{8}}), (range{xbound{5}, xbound{8}}),
|
||||
"Exclusive range is inconsistent.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
(range{xbound{5}, ibound{8}}), (range{xbound{5}, ibound{8}}),
|
||||
"Left-exclusive interval is not equal to itself.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
(range{ibound{5}, xbound{8}}), (range{ibound{5}, xbound{8}}),
|
||||
"Right-exclusive interval is not equal to itself.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
(range{ubound{}, ibound{8}}), (range{ubound{}, ibound{8}}),
|
||||
"Unlimited lower bound does not compare equal to same.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
(range{ibound{8}, ubound{}}), (range{ibound{8}, ubound{}}),
|
||||
"Unlimited upper bound does not compare equal to same.");
|
||||
|
||||
PQXX_CHECK_NOT_EQUAL(
|
||||
(range{ibound{5}, ibound{8}}), (range{xbound{5}, ibound{8}}),
|
||||
"Equality does not detect inclusive vs. exclusive lower bound.");
|
||||
PQXX_CHECK_NOT_EQUAL(
|
||||
(range{ibound{5}, ibound{8}}), (range{ubound{}, ibound{8}}),
|
||||
"Equality does not detect inclusive vs. unlimited lower bound.");
|
||||
PQXX_CHECK_NOT_EQUAL(
|
||||
(range{xbound{5}, ibound{8}}), (range{ubound{}, ibound{8}}),
|
||||
"Equality does not detect exclusive vs. unlimited lower bound.");
|
||||
PQXX_CHECK_NOT_EQUAL(
|
||||
(range{ibound{5}, ibound{8}}), (range{ibound{5}, xbound{8}}),
|
||||
"Equality does not detect inclusive vs. exclusive upper bound.");
|
||||
PQXX_CHECK_NOT_EQUAL(
|
||||
(range{ibound{5}, ibound{8}}), (range{ibound{5}, ubound{}}),
|
||||
"Equality does not detect inclusive vs. unlimited upper bound.");
|
||||
PQXX_CHECK_NOT_EQUAL(
|
||||
(range{ibound{5}, xbound{8}}), (range{ibound{5}, ubound{}}),
|
||||
"Equality does not detect exclusive vs. unlimited upper bound.");
|
||||
|
||||
PQXX_CHECK_NOT_EQUAL(
|
||||
(range{ibound{5}, ibound{8}}), (range{ibound{4}, ibound{8}}),
|
||||
"Equality does not compare lower inclusive bound value.");
|
||||
PQXX_CHECK_NOT_EQUAL(
|
||||
(range{xbound{5}, ibound{8}}), (range{xbound{4}, ibound{8}}),
|
||||
"Equality does not compare lower exclusive bound value.");
|
||||
PQXX_CHECK_NOT_EQUAL(
|
||||
(range{xbound{5}, ibound{8}}), (range{xbound{5}, ibound{7}}),
|
||||
"Equality does not compare upper inclusive bound value.");
|
||||
PQXX_CHECK_NOT_EQUAL(
|
||||
(range{xbound{5}, xbound{8}}), (range{xbound{5}, xbound{7}}),
|
||||
"Equality does not compare lower exclusive bound value.");
|
||||
}
|
||||
|
||||
|
||||
void test_range_empty()
|
||||
{
|
||||
using range = pqxx::range<int>;
|
||||
using ibound = pqxx::inclusive_bound<int>;
|
||||
using xbound = pqxx::exclusive_bound<int>;
|
||||
using ubound = pqxx::no_bound;
|
||||
PQXX_CHECK((range{}.empty()), "Default-constructed range is not empty.");
|
||||
PQXX_CHECK(
|
||||
(range{ibound{10}, xbound{10}}).empty(),
|
||||
"Right-exclusive zero-length interval is not empty.");
|
||||
PQXX_CHECK(
|
||||
(range{xbound{10}, ibound{10}}).empty(),
|
||||
"Left-exclusive zero-length interval is not empty.");
|
||||
PQXX_CHECK(
|
||||
(range{xbound{10}, xbound{10}}).empty(),
|
||||
"Exclusive zero-length interval is not empty.");
|
||||
|
||||
PQXX_CHECK(
|
||||
not(range{ibound{10}, ibound{10}}).empty(),
|
||||
"Inclusive zero-length interval is empty.");
|
||||
PQXX_CHECK(
|
||||
not(range{xbound{10}, ibound{11}}.empty()),
|
||||
"Interval is incorrectly empty.");
|
||||
PQXX_CHECK(
|
||||
not(range{ubound{}, ubound{}}.empty()),
|
||||
"Double-unlimited interval is empty.");
|
||||
PQXX_CHECK(
|
||||
not(range{ubound{}, xbound{0}}.empty()),
|
||||
"Left-unlimited interval is empty.");
|
||||
PQXX_CHECK(
|
||||
not(range{xbound{0}, ubound{}}.empty()),
|
||||
"Right-unlimited interval is empty.");
|
||||
}
|
||||
|
||||
|
||||
void test_range_contains()
|
||||
{
|
||||
using range = pqxx::range<int>;
|
||||
using ibound = pqxx::inclusive_bound<int>;
|
||||
using xbound = pqxx::exclusive_bound<int>;
|
||||
using ubound = pqxx::no_bound;
|
||||
|
||||
PQXX_CHECK(not(range{}.contains(-1)), "Empty range contains a value.");
|
||||
PQXX_CHECK(not(range{}.contains(0)), "Empty range contains a value.");
|
||||
PQXX_CHECK(not(range{}.contains(1)), "Empty range contains a value.");
|
||||
|
||||
PQXX_CHECK(
|
||||
not(range{ibound{5}, ibound{8}}.contains(4)),
|
||||
"Inclusive range contains value outside its left bound.");
|
||||
PQXX_CHECK(
|
||||
(range{ibound{5}, ibound{8}}.contains(5)),
|
||||
"Inclusive range does not contain value on its left bound.");
|
||||
PQXX_CHECK(
|
||||
(range{ibound{5}, ibound{8}}.contains(6)),
|
||||
"Inclusive range does not contain value inside it.");
|
||||
PQXX_CHECK(
|
||||
(range{ibound{5}, ibound{8}}.contains(8)),
|
||||
"Inclusive range does not contain value on its right bound.");
|
||||
PQXX_CHECK(
|
||||
not(range{ibound{5}, ibound{8}}.contains(9)),
|
||||
"Inclusive range contains value outside its right bound.");
|
||||
|
||||
PQXX_CHECK(
|
||||
not(range{ibound{5}, xbound{8}}.contains(4)),
|
||||
"Left-inclusive range contains value outside its left bound.");
|
||||
PQXX_CHECK(
|
||||
(range{ibound{5}, xbound{8}}.contains(5)),
|
||||
"Left-inclusive range does not contain value on its left bound.");
|
||||
PQXX_CHECK(
|
||||
(range{ibound{5}, xbound{8}}.contains(6)),
|
||||
"Left-inclusive range does not contain value inside it.");
|
||||
PQXX_CHECK(
|
||||
not(range{ibound{5}, xbound{8}}.contains(8)),
|
||||
"Left-inclusive range contains value on its right bound.");
|
||||
PQXX_CHECK(
|
||||
not(range{ibound{5}, xbound{8}}.contains(9)),
|
||||
"Left-inclusive range contains value outside its right bound.");
|
||||
|
||||
PQXX_CHECK(
|
||||
not(range{xbound{5}, ibound{8}}.contains(4)),
|
||||
"Right-inclusive range contains value outside its left bound.");
|
||||
PQXX_CHECK(
|
||||
not(range{xbound{5}, ibound{8}}.contains(5)),
|
||||
"Right-inclusive range does contains value on its left bound.");
|
||||
PQXX_CHECK(
|
||||
(range{xbound{5}, ibound{8}}.contains(6)),
|
||||
"Right-inclusive range does not contain value inside it.");
|
||||
PQXX_CHECK(
|
||||
(range{xbound{5}, ibound{8}}.contains(8)),
|
||||
"Right-inclusive range does not contain value on its right bound.");
|
||||
PQXX_CHECK(
|
||||
not(range{xbound{5}, ibound{8}}.contains(9)),
|
||||
"Right-inclusive range contains value outside its right bound.");
|
||||
|
||||
PQXX_CHECK(
|
||||
not(range{xbound{5}, xbound{8}}.contains(4)),
|
||||
"Exclusive range contains value outside its left bound.");
|
||||
PQXX_CHECK(
|
||||
not(range{xbound{5}, xbound{8}}.contains(5)),
|
||||
"Exclusive range contains value on its left bound.");
|
||||
PQXX_CHECK(
|
||||
(range{xbound{5}, xbound{8}}.contains(6)),
|
||||
"Exclusive range does not contain value inside it.");
|
||||
PQXX_CHECK(
|
||||
not(range{xbound{5}, xbound{8}}.contains(8)),
|
||||
"Exclusive range does contains value on its right bound.");
|
||||
PQXX_CHECK(
|
||||
not(range{xbound{5}, xbound{8}}.contains(9)),
|
||||
"Exclusive range contains value outside its right bound.");
|
||||
|
||||
PQXX_CHECK(
|
||||
(range{ubound{}, ibound{8}}.contains(7)),
|
||||
"Right-inclusive range does not contain value inside it.");
|
||||
PQXX_CHECK(
|
||||
(range{ubound{}, ibound{8}}.contains(8)),
|
||||
"Right-inclusive range does not contain value on its right bound.");
|
||||
PQXX_CHECK(
|
||||
not(range{ubound{}, ibound{8}}.contains(9)),
|
||||
"Right-inclusive range contains value outside its right bound.");
|
||||
|
||||
PQXX_CHECK(
|
||||
(range{ubound{}, xbound{8}}.contains(7)),
|
||||
"Right-exclusive range does not contain value inside it.");
|
||||
PQXX_CHECK(
|
||||
not(range{ubound{}, xbound{8}}.contains(8)),
|
||||
"Right-exclusive range contains value on its right bound.");
|
||||
PQXX_CHECK(
|
||||
not(range{ubound{}, xbound{8}}.contains(9)),
|
||||
"Right-exclusive range contains value outside its right bound.");
|
||||
|
||||
PQXX_CHECK(
|
||||
not(range{ibound{5}, ubound{}}.contains(4)),
|
||||
"Left-inclusive range contains value outside its left bound.");
|
||||
PQXX_CHECK(
|
||||
(range{ibound{5}, ubound{}}.contains(5)),
|
||||
"Left-inclusive range does not contain value on its left bound.");
|
||||
PQXX_CHECK(
|
||||
(range{ibound{5}, ubound{}}.contains(6)),
|
||||
"Left-inclusive range does not contain value inside it.");
|
||||
|
||||
PQXX_CHECK(
|
||||
not(range{xbound{5}, ubound{}}.contains(4)),
|
||||
"Left-exclusive range contains value outside its left bound.");
|
||||
PQXX_CHECK(
|
||||
not(range{xbound{5}, ubound{}}.contains(5)),
|
||||
"Left-exclusive range contains value on its left bound.");
|
||||
PQXX_CHECK(
|
||||
(range{xbound{5}, ubound{}}.contains(6)),
|
||||
"Left-exclusive range does not contain value inside it.");
|
||||
|
||||
PQXX_CHECK(
|
||||
(range{ubound{}, ubound{}}.contains(-1)), "Value not in universal range.");
|
||||
PQXX_CHECK(
|
||||
(range{ubound{}, ubound{}}.contains(0)), "Value not in universal range.");
|
||||
PQXX_CHECK(
|
||||
(range{ubound{}, ubound{}}.contains(1)), "Value not in universal range.");
|
||||
}
|
||||
|
||||
|
||||
void test_float_range_contains()
|
||||
{
|
||||
using range = pqxx::range<double>;
|
||||
using ibound = pqxx::inclusive_bound<double>;
|
||||
using xbound = pqxx::exclusive_bound<double>;
|
||||
using ubound = pqxx::no_bound;
|
||||
using limits = std::numeric_limits<double>;
|
||||
constexpr auto inf{limits::infinity()};
|
||||
|
||||
PQXX_CHECK(
|
||||
not(range{ibound{4.0}, ibound{8.0}}.contains(3.9)),
|
||||
"Float inclusive range contains value beyond its lower bound.");
|
||||
PQXX_CHECK(
|
||||
(range{ibound{4.0}, ibound{8.0}}.contains(4.0)),
|
||||
"Float inclusive range does not contain its lower bound value.");
|
||||
PQXX_CHECK(
|
||||
(range{ibound{4.0}, ibound{8.0}}.contains(5.0)),
|
||||
"Float inclusive range does not contain value inside it.");
|
||||
|
||||
PQXX_CHECK(
|
||||
(range{ibound{0}, ibound{inf}}).contains(9999.0),
|
||||
"Range to infinity did not include large number.");
|
||||
PQXX_CHECK(
|
||||
not(range{ibound{0}, ibound{inf}}.contains(-0.1)),
|
||||
"Range to infinity includes number outside it.");
|
||||
PQXX_CHECK(
|
||||
(range{ibound{0}, xbound{inf}}.contains(9999.0)),
|
||||
"Range to exclusive infinity did not include large number.");
|
||||
PQXX_CHECK(
|
||||
(range{ibound{0}, ibound{inf}}).contains(inf),
|
||||
"Range to inclusive infinity does not include infinity.");
|
||||
PQXX_CHECK(
|
||||
not(range{ibound{0}, xbound{inf}}.contains(inf)),
|
||||
"Range to exclusive infinity includes infinity.");
|
||||
PQXX_CHECK(
|
||||
(range{ibound{0}, ubound{}}).contains(inf),
|
||||
"Right-unlimited range does not include infinity.");
|
||||
|
||||
PQXX_CHECK(
|
||||
(range{ibound{-inf}, ibound{0}}).contains(-9999.0),
|
||||
"Range from infinity did not include large negative number.");
|
||||
PQXX_CHECK(
|
||||
not(range{ibound{-inf}, ibound{0}}.contains(0.1)),
|
||||
"Range from infinity includes number outside it.");
|
||||
PQXX_CHECK(
|
||||
(range{xbound{-inf}, ibound{0}}).contains(-9999.0),
|
||||
"Range from exclusive infinity did not include large negative number.");
|
||||
PQXX_CHECK(
|
||||
(range{ibound{-inf}, ibound{0}}).contains(-inf),
|
||||
"Range from inclusive infinity does not include negative infinity.");
|
||||
PQXX_CHECK(
|
||||
not(range{xbound{-inf}, ibound{0}}).contains(-inf),
|
||||
"Range to infinity exclusive includes negative infinity.");
|
||||
PQXX_CHECK(
|
||||
(range{ubound{}, ibound{0}}).contains(-inf),
|
||||
"Left-unlimited range does not include negative infinity.");
|
||||
}
|
||||
|
||||
|
||||
void test_range_subset()
|
||||
{
|
||||
using range = pqxx::range<int>;
|
||||
using traits = pqxx::string_traits<range>;
|
||||
|
||||
std::string_view subsets[][2]{
|
||||
{"empty", "empty"}, {"(,)", "empty"}, {"(0,1)", "empty"},
|
||||
{"(,)", "[-10,10]"}, {"(,)", "(-10,10)"}, {"(,)", "(,)"},
|
||||
{"(,10)", "(,10)"}, {"(,10)", "(,9)"}, {"(,10]", "(,10)"},
|
||||
{"(,10]", "(,10]"}, {"(1,)", "(10,)"}, {"(1,)", "(9,)"},
|
||||
{"[1,)", "(10,)"}, {"[1,)", "[10,)"}, {"[0,5]", "[1,4]"},
|
||||
{"(0,5)", "[1,4]"},
|
||||
};
|
||||
for (auto const [super, sub] : subsets)
|
||||
PQXX_CHECK(
|
||||
traits::from_string(super).contains(traits::from_string(sub)),
|
||||
pqxx::internal::concat(
|
||||
"Range '", super, "' did not contain '", sub, "'."));
|
||||
|
||||
std::string_view non_subsets[][2]{
|
||||
{"empty", "[0,0]"}, {"empty", "(,)"}, {"[-10,10]", "(,)"},
|
||||
{"(-10,10)", "(,)"}, {"(,9)", "(,10)"}, {"(,10)", "(,10]"},
|
||||
{"[1,4]", "[0,4]"}, {"[1,4]", "[1,5]"}, {"(0,10)", "[0,10]"},
|
||||
{"(0,10)", "(0,10]"}, {"(0,10)", "[0,10)"},
|
||||
};
|
||||
for (auto const [super, sub] : non_subsets)
|
||||
PQXX_CHECK(
|
||||
not traits::from_string(super).contains(traits::from_string(sub)),
|
||||
pqxx::internal::concat("Range '", super, "' contained '", sub, "'."));
|
||||
}
|
||||
|
||||
|
||||
void test_range_to_string()
|
||||
{
|
||||
using range = pqxx::range<int>;
|
||||
using ibound = pqxx::inclusive_bound<int>;
|
||||
using xbound = pqxx::exclusive_bound<int>;
|
||||
using ubound = pqxx::no_bound;
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(range{}), "empty", "Empty range came out wrong.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(range{ibound{5}, ibound{8}}), "[5,8]",
|
||||
"Inclusive range came out wrong.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(range{xbound{5}, ibound{8}}), "(5,8]",
|
||||
"Left-exclusive range came out wrong.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(range{ibound{5}, xbound{8}}), "[5,8)",
|
||||
"Right-exclusive range came out wrong.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(range{xbound{5}, xbound{8}}), "(5,8)",
|
||||
"Exclusive range came out wrong.");
|
||||
|
||||
// Unlimited boundaries can use brackets or parentheses. Doesn't matter.
|
||||
// We cheat and use some white-box knowledge of our implementation here.
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(range{ubound{}, ubound{}}), "(,)",
|
||||
"Universal range came out unexpected.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(range{ubound{}, ibound{8}}), "(,8]",
|
||||
"Left-unlimited range came out unexpected.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(range{ubound{}, xbound{8}}), "(,8)",
|
||||
"Left-unlimited range came out unexpected.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(range{ibound{5}, ubound{}}), "[5,)",
|
||||
"Right-unlimited range came out unexpected.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(range{xbound{5}, ubound{}}), "(5,)",
|
||||
"Right-unlimited range came out unexpected.");
|
||||
}
|
||||
|
||||
|
||||
void test_parse_range()
|
||||
{
|
||||
using range = pqxx::range<int>;
|
||||
using ubound = pqxx::no_bound;
|
||||
using traits = pqxx::string_traits<range>;
|
||||
|
||||
constexpr std::string_view empties[]{"empty", "EMPTY", "eMpTy"};
|
||||
for (auto empty : empties)
|
||||
PQXX_CHECK(
|
||||
traits::from_string(empty).empty(),
|
||||
pqxx::internal::concat(
|
||||
"This was supposed to produce an empty range: '", empty, "'"));
|
||||
|
||||
constexpr std::string_view universals[]{"(,)", "[,)", "(,]", "[,]"};
|
||||
for (auto univ : universals)
|
||||
PQXX_CHECK_EQUAL(
|
||||
traits::from_string(univ), (range{ubound{}, ubound{}}),
|
||||
pqxx::internal::concat(
|
||||
"This was supposed to produce a universal range: '", univ, "'"));
|
||||
|
||||
PQXX_CHECK(
|
||||
traits::from_string("(0,10]").lower_bound().is_exclusive(),
|
||||
"Exclusive lower bound did not parse right.");
|
||||
PQXX_CHECK(
|
||||
traits::from_string("[0,10]").lower_bound().is_inclusive(),
|
||||
"Inclusive lower bound did not parse right.");
|
||||
PQXX_CHECK(
|
||||
traits::from_string("(0,10)").upper_bound().is_exclusive(),
|
||||
"Exclusive upper bound did not parse right.");
|
||||
PQXX_CHECK(
|
||||
traits::from_string("[0,10]").upper_bound().is_inclusive(),
|
||||
"Inclusive upper bound did not parse right.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
*traits::from_string("(\"0\",\"10\")").lower_bound().value(), 0,
|
||||
"Quoted range boundary did not parse right.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
*traits::from_string("(\"0\",\"10\")").upper_bound().value(), 10,
|
||||
"Quoted upper boundary did not parse right.");
|
||||
|
||||
auto floats{
|
||||
pqxx::string_traits<pqxx::range<double>>::from_string("(0,1.0)")};
|
||||
PQXX_CHECK_GREATER(
|
||||
*floats.lower_bound().value(), -0.001,
|
||||
"Float lower bound is out of range.");
|
||||
PQXX_CHECK_LESS(
|
||||
*floats.lower_bound().value(), 0.001,
|
||||
"Float lower bound is out of range.");
|
||||
PQXX_CHECK_GREATER(
|
||||
*floats.upper_bound().value(), 0.999,
|
||||
"Float upper bound is out of range.");
|
||||
PQXX_CHECK_LESS(
|
||||
*floats.upper_bound().value(), 1.001,
|
||||
"Float upper bound is out of range.");
|
||||
}
|
||||
|
||||
|
||||
void test_parse_bad_range()
|
||||
{
|
||||
using range = pqxx::range<int>;
|
||||
using conv_err = pqxx::conversion_error;
|
||||
using traits = pqxx::string_traits<range>;
|
||||
constexpr std::string_view bad_ranges[]{
|
||||
"", "x", "e", "empt", "emptyy", "()",
|
||||
"[]", "(empty)", "(empty, 0)", "(0, empty)", ",", "(,",
|
||||
",)", "(1,2,3)", "(4,5x)", "(null, 0)", "[0, 1.0]", "[1.0, 0]",
|
||||
};
|
||||
|
||||
for (auto bad : bad_ranges)
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::ignore_unused(traits::from_string(bad)), conv_err,
|
||||
pqxx::internal::concat(
|
||||
"This range wasn't supposed to parse: '", bad, "'"));
|
||||
}
|
||||
|
||||
|
||||
/// Parse ranges lhs and rhs, return their intersection as a string.
|
||||
template<typename TYPE>
|
||||
std::string intersect(std::string_view lhs, std::string_view rhs)
|
||||
{
|
||||
using traits = pqxx::string_traits<pqxx::range<TYPE>>;
|
||||
return pqxx::to_string(traits::from_string(lhs) & traits::from_string(rhs));
|
||||
}
|
||||
|
||||
|
||||
void test_range_intersection()
|
||||
{
|
||||
// Intersections and their expected results, in text form.
|
||||
// Each row contains two ranges, and their intersection.
|
||||
std::string_view intersections[][3]{
|
||||
{"empty", "empty", "empty"},
|
||||
{"(,)", "empty", "empty"},
|
||||
{"[,]", "empty", "empty"},
|
||||
{"empty", "[0,10]", "empty"},
|
||||
{"(,)", "(,)", "(,)"},
|
||||
{"(,)", "(5,8)", "(5,8)"},
|
||||
{"(,)", "[5,8)", "[5,8)"},
|
||||
{"(,)", "(5,8]", "(5,8]"},
|
||||
{"(,)", "[5,8]", "[5,8]"},
|
||||
{"(-1000,10)", "(0,1000)", "(0,10)"},
|
||||
{"[-1000,10)", "(0,1000)", "(0,10)"},
|
||||
{"(-1000,10]", "(0,1000)", "(0,10]"},
|
||||
{"[-1000,10]", "(0,1000)", "(0,10]"},
|
||||
{"[0,100]", "[0,100]", "[0,100]"},
|
||||
{"[0,100]", "[0,100)", "[0,100)"},
|
||||
{"[0,100]", "(0,100]", "(0,100]"},
|
||||
{"[0,100]", "(0,100)", "(0,100)"},
|
||||
{"[0,10]", "[11,20]", "empty"},
|
||||
{"[0,10]", "(11,20]", "empty"},
|
||||
{"[0,10]", "[11,20)", "empty"},
|
||||
{"[0,10]", "(11,20)", "empty"},
|
||||
{"[0,10]", "[10,11]", "[10,10]"},
|
||||
{"[0,10)", "[10,11]", "empty"},
|
||||
{"[0,10]", "(10,11]", "empty"},
|
||||
{"[0,10)", "(10,11]", "empty"},
|
||||
};
|
||||
for (auto [left, right, expected] : intersections)
|
||||
{
|
||||
PQXX_CHECK_EQUAL(
|
||||
intersect<int>(left, right), expected,
|
||||
pqxx::internal::concat(
|
||||
"Intersection of '", left, "' and '", right,
|
||||
" produced unexpected result."));
|
||||
PQXX_CHECK_EQUAL(
|
||||
intersect<int>(right, left), expected,
|
||||
pqxx::internal::concat(
|
||||
"Intersection of '", left, "' and '", right, " was asymmetric."));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void test_range_conversion()
|
||||
{
|
||||
std::string_view const ranges[]{
|
||||
"empty", "(,)", "(,10)", "(0,)", "[0,10]", "[0,10)", "(0,10]", "(0,10)",
|
||||
};
|
||||
|
||||
for (auto r : ranges)
|
||||
{
|
||||
auto const shortr{pqxx::from_string<pqxx::range<short>>(r)};
|
||||
pqxx::range<int> intr{shortr};
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(intr), r, "Converted range looks different.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_range_construct);
|
||||
PQXX_REGISTER_TEST(test_range_equality);
|
||||
PQXX_REGISTER_TEST(test_range_empty);
|
||||
PQXX_REGISTER_TEST(test_range_contains);
|
||||
PQXX_REGISTER_TEST(test_float_range_contains);
|
||||
PQXX_REGISTER_TEST(test_range_subset);
|
||||
PQXX_REGISTER_TEST(test_range_to_string);
|
||||
PQXX_REGISTER_TEST(test_parse_range);
|
||||
PQXX_REGISTER_TEST(test_parse_bad_range);
|
||||
PQXX_REGISTER_TEST(test_range_intersection);
|
||||
PQXX_REGISTER_TEST(test_range_conversion);
|
||||
} // namespace
|
||||
@@ -0,0 +1,22 @@
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_read_transaction()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::read_transaction tx{conn};
|
||||
PQXX_CHECK_EQUAL(
|
||||
tx.exec("SELECT 1")[0][0].as<int>(), 1,
|
||||
"Bad result from read transaction.");
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
tx.exec("CREATE TABLE should_not_exist(x integer)"), pqxx::sql_error,
|
||||
"Read-only transaction allows database to be modified.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_read_transaction);
|
||||
} // namespace
|
||||
@@ -0,0 +1,137 @@
|
||||
#include <pqxx/stream_to>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_result_iteration()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
pqxx::result r{tx.exec("SELECT generate_series(1, 3)")};
|
||||
|
||||
PQXX_CHECK(std::end(r) != std::begin(r), "Broken begin/end.");
|
||||
PQXX_CHECK(std::rend(r) != std::rbegin(r), "Broken rbegin/rend.");
|
||||
|
||||
PQXX_CHECK(std::cbegin(r) == std::begin(r), "Wrong cbegin.");
|
||||
PQXX_CHECK(std::cend(r) == std::end(r), "Wrong cend.");
|
||||
PQXX_CHECK(std::crbegin(r) == std::rbegin(r), "Wrong crbegin.");
|
||||
PQXX_CHECK(std::crend(r) == std::rend(r), "Wrong crend.");
|
||||
|
||||
PQXX_CHECK_EQUAL(r.front().front().as<int>(), 1, "Unexpected front().");
|
||||
PQXX_CHECK_EQUAL(r.back().front().as<int>(), 3, "Unexpected back().");
|
||||
}
|
||||
|
||||
|
||||
void test_result_iter()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
pqxx::result r{tx.exec("SELECT generate_series(1, 3)")};
|
||||
|
||||
int total{0};
|
||||
for (auto const &[i] : r.iter<int>()) total += i;
|
||||
PQXX_CHECK_EQUAL(total, 6, "iter() loop did not get the right values.");
|
||||
}
|
||||
|
||||
|
||||
void test_result_iterator_swap()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
pqxx::result r{tx.exec("SELECT generate_series(1, 3)")};
|
||||
|
||||
auto head{std::begin(r)}, next{head + 1};
|
||||
head.swap(next);
|
||||
PQXX_CHECK_EQUAL(head[0].as<int>(), 2, "Result iterator swap is wrong.");
|
||||
PQXX_CHECK_EQUAL(next[0].as<int>(), 1, "Result iterator swap is crazy.");
|
||||
|
||||
auto tail{std::rbegin(r)}, prev{tail + 1};
|
||||
tail.swap(prev);
|
||||
PQXX_CHECK_EQUAL(tail[0].as<int>(), 2, "Reverse iterator swap is wrong.");
|
||||
PQXX_CHECK_EQUAL(prev[0].as<int>(), 3, "Reverse iterator swap is crazy.");
|
||||
}
|
||||
|
||||
|
||||
void test_result_iterator_assignment()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
pqxx::result r{tx.exec("SELECT generate_series(1, 3)")};
|
||||
|
||||
pqxx::result::const_iterator fwd;
|
||||
pqxx::result::const_reverse_iterator rev;
|
||||
|
||||
fwd = std::begin(r);
|
||||
PQXX_CHECK_EQUAL(
|
||||
fwd[0].as<int>(), std::begin(r)[0].as<int>(),
|
||||
"Result iterator assignment is wrong.");
|
||||
|
||||
rev = std::rbegin(r);
|
||||
PQXX_CHECK_EQUAL(
|
||||
rev[0].as<int>(), std::rbegin(r)[0].as<int>(),
|
||||
"Reverse iterator assignment is wrong.");
|
||||
}
|
||||
|
||||
|
||||
void check_employee(std::string name, int salary)
|
||||
{
|
||||
PQXX_CHECK(name == "x" or name == "y" or name == "z", "Unknown name.");
|
||||
PQXX_CHECK(
|
||||
salary == 1000 or salary == 1200 or salary == 1500, "Unknown salary.");
|
||||
}
|
||||
|
||||
|
||||
void test_result_for_each()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
tx.exec0("CREATE TEMP TABLE employee(name varchar, salary int)");
|
||||
auto fill{pqxx::stream_to::table(tx, {"employee"}, {"name", "salary"})};
|
||||
fill.write_values("x", 1000);
|
||||
fill.write_values("y", 1200);
|
||||
fill.write_values("z", 1500);
|
||||
fill.complete();
|
||||
|
||||
auto const res{tx.exec("SELECT name, salary FROM employee ORDER BY name")};
|
||||
|
||||
// Use for_each with a function.
|
||||
res.for_each(check_employee);
|
||||
|
||||
// Use for_each with a simple lambda.
|
||||
res.for_each(
|
||||
[](std::string name, int salary) { check_employee(name, salary); });
|
||||
|
||||
// Use for_each with a lambda closure.
|
||||
std::string names{};
|
||||
int total{0};
|
||||
|
||||
res.for_each([&names, &total](std::string name, int salary) {
|
||||
names.append(name);
|
||||
total += salary;
|
||||
});
|
||||
PQXX_CHECK_EQUAL(
|
||||
names, "xyz", "result::for_each did not accumulate names correctly.");
|
||||
PQXX_CHECK_EQUAL(total, 1000 + 1200 + 1500, "Salaries added up wrong.");
|
||||
|
||||
// In addition to regular conversions, you can receive arguments as
|
||||
// string_view, or as references.
|
||||
names.clear();
|
||||
total = 0;
|
||||
res.for_each([&names, &total](std::string_view &&name, int const &salary) {
|
||||
names.append(name);
|
||||
total += salary;
|
||||
});
|
||||
PQXX_CHECK_EQUAL(
|
||||
names, "xyz", "result::for_each did not accumulate names correctly.");
|
||||
PQXX_CHECK_EQUAL(total, 1000 + 1200 + 1500, "Salaries added up wrong.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_result_iteration);
|
||||
PQXX_REGISTER_TEST(test_result_iter);
|
||||
PQXX_REGISTER_TEST(test_result_iterator_swap);
|
||||
PQXX_REGISTER_TEST(test_result_iterator_assignment);
|
||||
PQXX_REGISTER_TEST(test_result_for_each);
|
||||
} // namespace
|
||||
@@ -0,0 +1,157 @@
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
template<> struct nullness<row::const_iterator> : no_null<row::const_iterator>
|
||||
{};
|
||||
|
||||
template<>
|
||||
struct nullness<row::const_reverse_iterator>
|
||||
: no_null<const_reverse_row_iterator>
|
||||
{};
|
||||
|
||||
|
||||
template<> struct string_traits<row::const_iterator>
|
||||
{
|
||||
static constexpr zview text{"[row::const_iterator]"};
|
||||
static zview to_buf(char *, char *, row::const_iterator const &)
|
||||
{
|
||||
return text;
|
||||
}
|
||||
static char *into_buf(char *begin, char *end, row::const_iterator const &)
|
||||
{
|
||||
if ((end - begin) <= 30)
|
||||
throw conversion_overrun{"Not enough buffer for const row iterator."};
|
||||
std::memcpy(begin, text.c_str(), std::size(text) + 1);
|
||||
return begin + std::size(text);
|
||||
}
|
||||
static constexpr std::size_t
|
||||
size_buffer(row::const_iterator const &) noexcept
|
||||
{
|
||||
return std::size(text) + 1;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<> struct string_traits<row::const_reverse_iterator>
|
||||
{
|
||||
static constexpr zview text{"[row::const_reverse_iterator]"};
|
||||
static pqxx::zview
|
||||
to_buf(char *, char *, row::const_reverse_iterator const &)
|
||||
{
|
||||
return text;
|
||||
}
|
||||
static char *
|
||||
into_buf(char *begin, char *end, row::const_reverse_iterator const &)
|
||||
{
|
||||
if ((end - begin) <= 30)
|
||||
throw conversion_overrun{"Not enough buffer for const row iterator."};
|
||||
std::memcpy(begin, text.c_str(), std::size(text) + 1);
|
||||
return begin + std::size(text);
|
||||
}
|
||||
static constexpr std::size_t
|
||||
size_buffer(row::const_reverse_iterator const &) noexcept
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
};
|
||||
} // namespace pqxx
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_result_slicing()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
auto r{tx.exec("SELECT 1")};
|
||||
|
||||
PQXX_CHECK(not std::empty(r[0]), "A plain row shows up as empty.");
|
||||
|
||||
// Empty slice at beginning of row.
|
||||
pqxx::row s{r[0].slice(0, 0)};
|
||||
PQXX_CHECK(std::empty(s), "Empty slice does not show up as empty.");
|
||||
PQXX_CHECK_EQUAL(std::size(s), 0, "Slicing produces wrong row size.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::begin(s), std::end(s), "Slice begin()/end() are broken.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::rbegin(s), std::rend(s), "Slice rbegin()/rend() are broken.");
|
||||
|
||||
PQXX_CHECK_THROWS(s.at(0), pqxx::range_error, "at() does not throw.");
|
||||
pqxx::row slice;
|
||||
PQXX_CHECK_THROWS(
|
||||
slice = r[0].slice(0, 2), pqxx::range_error, "No range check.");
|
||||
pqxx::ignore_unused(slice);
|
||||
PQXX_CHECK_THROWS(
|
||||
slice = r[0].slice(1, 0), pqxx::range_error, "Can reverse-slice.");
|
||||
pqxx::ignore_unused(slice);
|
||||
|
||||
// Empty slice at end of row.
|
||||
s = r[0].slice(1, 1);
|
||||
PQXX_CHECK(std::empty(s), "empty() is broken.");
|
||||
PQXX_CHECK_EQUAL(std::size(s), 0, "size() is broken.");
|
||||
PQXX_CHECK_EQUAL(std::begin(s), std::end(s), "begin()/end() are broken.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::rbegin(s), std::rend(s), "rbegin()/rend() are broken.");
|
||||
|
||||
PQXX_CHECK_THROWS(s.at(0), pqxx::range_error, "at() is inconsistent.");
|
||||
|
||||
// Slice that matches the entire row.
|
||||
s = r[0].slice(0, 1);
|
||||
PQXX_CHECK(not std::empty(s), "Nonempty slice shows up as empty.");
|
||||
PQXX_CHECK_EQUAL(std::size(s), 1, "size() breaks for non-empty slice.");
|
||||
PQXX_CHECK_EQUAL(std::begin(s) + 1, std::end(s), "Iteration is broken.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::rbegin(s) + 1, std::rend(s), "Reverse iteration is broken.");
|
||||
PQXX_CHECK_EQUAL(s.at(0).as<int>(), 1, "Accessing a slice is broken.");
|
||||
PQXX_CHECK_EQUAL(s[0].as<int>(), 1, "operator[] is broken.");
|
||||
PQXX_CHECK_THROWS(s.at(1).as<int>(), pqxx::range_error, "at() is off.");
|
||||
|
||||
// Meaningful slice at beginning of row.
|
||||
r = tx.exec("SELECT 1, 2, 3");
|
||||
s = r[0].slice(0, 1);
|
||||
PQXX_CHECK(not std::empty(s), "Slicing confuses empty().");
|
||||
PQXX_CHECK_THROWS(
|
||||
s.at(1).as<int>(), pqxx::range_error, "at() does not enforce slice.");
|
||||
|
||||
// Meaningful slice that skips an initial column.
|
||||
s = r[0].slice(1, 2);
|
||||
PQXX_CHECK(
|
||||
not std::empty(s), "Slicing away leading columns confuses empty().");
|
||||
PQXX_CHECK_EQUAL(s[0].as<int>(), 2, "Slicing offset is broken.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::begin(s)->as<int>(), 2, "Iteration uses wrong offset.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::begin(s) + 1, std::end(s), "Iteration has wrong range.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::rbegin(s) + 1, std::rend(s), "Reverse iteration has wrong range.");
|
||||
PQXX_CHECK_THROWS(
|
||||
s.at(1).as<int>(), pqxx::range_error, "Offset slicing is broken.");
|
||||
|
||||
// Column names in a slice.
|
||||
r = tx.exec("SELECT 1 AS one, 2 AS two, 3 AS three");
|
||||
s = r[0].slice(1, 2);
|
||||
PQXX_CHECK_EQUAL(s["two"].as<int>(), 2, "Column addressing breaks.");
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::ignore_unused(s.column_number("one")), pqxx::argument_error,
|
||||
"Can access column name before slice.");
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::ignore_unused(s.column_number("three")), pqxx::argument_error,
|
||||
"Can access column name after slice.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
s.column_number("Two"), 0, "Column name is case sensitive.");
|
||||
|
||||
// Identical column names.
|
||||
r = tx.exec("SELECT 1 AS x, 2 AS x");
|
||||
s = r[0].slice(1, 2);
|
||||
PQXX_CHECK_EQUAL(s["x"].as<int>(), 2, "Identical column names break slice.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_result_slicing);
|
||||
} // namespace
|
||||
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
@@ -0,0 +1,84 @@
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_row()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
pqxx::result rows{tx.exec("SELECT 1, 2, 3")};
|
||||
pqxx::row r{rows[0]};
|
||||
PQXX_CHECK_EQUAL(std::size(r), 3, "Unexpected row size.");
|
||||
PQXX_CHECK_EQUAL(r.at(0).as<int>(), 1, "Wrong value at index 0.");
|
||||
PQXX_CHECK(std::begin(r) != std::end(r), "Broken row iteration.");
|
||||
PQXX_CHECK(std::begin(r) < std::end(r), "Row begin does not precede end.");
|
||||
PQXX_CHECK(std::cbegin(r) == std::begin(r), "Wrong cbegin.");
|
||||
PQXX_CHECK(std::cend(r) == std::end(r), "Wrong cend.");
|
||||
PQXX_CHECK(std::rbegin(r) != std::rend(r), "Broken reverse row iteration.");
|
||||
PQXX_CHECK(std::crbegin(r) == std::rbegin(r), "Wrong crbegin.");
|
||||
PQXX_CHECK(std::crend(r) == std::rend(r), "Wrong crend.");
|
||||
PQXX_CHECK_EQUAL(r.front().as<int>(), 1, "Wrong row front().");
|
||||
PQXX_CHECK_EQUAL(r.back().as<int>(), 3, "Wrong row back().");
|
||||
}
|
||||
|
||||
|
||||
void test_row_iterator()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
pqxx::result rows{tx.exec("SELECT 1, 2, 3")};
|
||||
|
||||
auto i{std::begin(rows[0])};
|
||||
PQXX_CHECK_EQUAL(i->as<int>(), 1, "Row iterator is wrong.");
|
||||
auto i2{i};
|
||||
PQXX_CHECK_EQUAL(i2->as<int>(), 1, "Row iterator copy is wrong.");
|
||||
i2++;
|
||||
PQXX_CHECK_EQUAL(i2->as<int>(), 2, "Row iterator increment is wrong.");
|
||||
pqxx::row::const_iterator i3;
|
||||
i3 = i2;
|
||||
PQXX_CHECK_EQUAL(i3->as<int>(), 2, "Row iterator assignment is wrong.");
|
||||
|
||||
auto r{std::rbegin(rows[0])};
|
||||
PQXX_CHECK_EQUAL(r->as<int>(), 3, "Row reverse iterator is wrong.");
|
||||
auto r2{r};
|
||||
PQXX_CHECK_EQUAL(r2->as<int>(), 3, "Row reverse iterator copy is wrong.");
|
||||
r2++;
|
||||
PQXX_CHECK_EQUAL(
|
||||
r2->as<int>(), 2, "Row reverse iterator increment is wrong.");
|
||||
pqxx::row::const_reverse_iterator r3;
|
||||
r3 = r2;
|
||||
PQXX_CHECK_EQUAL(
|
||||
i3->as<int>(), 2, "Row reverse iterator assignment is wrong.");
|
||||
}
|
||||
|
||||
|
||||
void test_row_as()
|
||||
{
|
||||
using pqxx::operator"" _zv;
|
||||
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
|
||||
pqxx::row const r{tx.exec1("SELECT 1, 2, 3")};
|
||||
auto [one, two, three]{r.as<int, float, pqxx::zview>()};
|
||||
static_assert(std::is_same_v<decltype(one), int>);
|
||||
static_assert(std::is_same_v<decltype(two), float>);
|
||||
static_assert(std::is_same_v<decltype(three), pqxx::zview>);
|
||||
PQXX_CHECK_EQUAL(one, 1, "row::as() did not produce the right int.");
|
||||
PQXX_CHECK_GREATER(two, 1.9, "row::as() did not produce the right float.");
|
||||
PQXX_CHECK_LESS(two, 2.1, "row::as() did not produce the right float.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
three, "3"_zv, "row::as() did not produce the right zview.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::get<0>(tx.exec1("SELECT 999").as<int>()), 999,
|
||||
"Unary tuple did not extract right.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_row);
|
||||
PQXX_REGISTER_TEST(test_row_iterator);
|
||||
PQXX_REGISTER_TEST(test_row_as);
|
||||
} // namespace
|
||||
@@ -0,0 +1,33 @@
|
||||
#include <pqxx/separated_list>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
// Test program for separated_list.
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_separated_list()
|
||||
{
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::separated_list(",", std::vector<int>{}), "",
|
||||
"Empty list came out wrong.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::separated_list(",", std::vector<int>{5}), "5",
|
||||
"Single-element list came out wrong.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::separated_list(",", std::vector<int>{3, 6}), "3,6",
|
||||
"Things go wrong once separators come in.");
|
||||
|
||||
std::vector<int> const nums{1, 2, 3};
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::separated_list(
|
||||
"+", std::begin(nums), std::end(nums),
|
||||
[](auto elt) { return *elt * 2; }),
|
||||
"2+4+6", "Accessors don't seem to work.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_separated_list);
|
||||
} // namespace
|
||||
@@ -0,0 +1,20 @@
|
||||
#include <pqxx/nontransaction>
|
||||
#include <pqxx/subtransaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_simultaneous_transactions()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
|
||||
pqxx::nontransaction n1{conn};
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::nontransaction n2{conn}, std::logic_error,
|
||||
"Allowed to open simultaneous nontransactions.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_simultaneous_transactions);
|
||||
} // namespace
|
||||
@@ -0,0 +1,270 @@
|
||||
#include <pqxx/cursor>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_forward_sql_cursor()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
|
||||
// Plain owned, scoped, forward-only read-only cursor.
|
||||
pqxx::internal::sql_cursor forward(
|
||||
tx, "SELECT generate_series(1, 4)", "forward",
|
||||
pqxx::cursor_base::forward_only, pqxx::cursor_base::read_only,
|
||||
pqxx::cursor_base::owned, false);
|
||||
|
||||
PQXX_CHECK_EQUAL(forward.pos(), 0, "Wrong initial position");
|
||||
PQXX_CHECK_EQUAL(forward.endpos(), -1, "Wrong initial endpos()");
|
||||
|
||||
auto empty_result{forward.empty_result()};
|
||||
PQXX_CHECK_EQUAL(std::size(empty_result), 0, "Empty result not empty");
|
||||
|
||||
auto displacement{0};
|
||||
auto one{forward.fetch(1, displacement)};
|
||||
PQXX_CHECK_EQUAL(std::size(one), 1, "Fetched wrong number of rows");
|
||||
PQXX_CHECK_EQUAL(one[0][0].as<std::string>(), "1", "Unexpected result");
|
||||
PQXX_CHECK_EQUAL(displacement, 1, "Wrong displacement");
|
||||
PQXX_CHECK_EQUAL(forward.pos(), 1, "In wrong position");
|
||||
|
||||
auto offset{forward.move(1, displacement)};
|
||||
PQXX_CHECK_EQUAL(offset, 1, "Unexpected offset from move()");
|
||||
PQXX_CHECK_EQUAL(displacement, 1, "Unexpected displacement after move()");
|
||||
PQXX_CHECK_EQUAL(forward.pos(), 2, "Wrong position after move()");
|
||||
PQXX_CHECK_EQUAL(forward.endpos(), -1, "endpos() unexpectedly set");
|
||||
|
||||
auto row{forward.fetch(0, displacement)};
|
||||
PQXX_CHECK_EQUAL(std::size(row), 0, "fetch(0, displacement) returns rows");
|
||||
PQXX_CHECK_EQUAL(displacement, 0, "Unexpected displacement after fetch(0)");
|
||||
PQXX_CHECK_EQUAL(forward.pos(), 2, "fetch(0, displacement) affected pos()");
|
||||
|
||||
row = forward.fetch(0);
|
||||
PQXX_CHECK_EQUAL(std::size(row), 0, "fetch(0) fetched wrong number of rows");
|
||||
PQXX_CHECK_EQUAL(forward.pos(), 2, "fetch(0) moved cursor");
|
||||
PQXX_CHECK_EQUAL(forward.pos(), 2, "fetch(0) affected pos()");
|
||||
|
||||
offset = forward.move(1);
|
||||
PQXX_CHECK_EQUAL(offset, 1, "move(1) returned unexpected value");
|
||||
PQXX_CHECK_EQUAL(forward.pos(), 3, "move(1) after fetch(0) broke");
|
||||
|
||||
row = forward.fetch(1);
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(row), 1, "fetch(1) returned wrong number of rows");
|
||||
PQXX_CHECK_EQUAL(forward.pos(), 4, "fetch(1) results in bad pos()");
|
||||
PQXX_CHECK_EQUAL(row[0][0].as<std::string>(), "4", "pos() is lying");
|
||||
|
||||
empty_result = forward.fetch(1, displacement);
|
||||
PQXX_CHECK_EQUAL(std::size(empty_result), 0, "Got rows at end of cursor");
|
||||
PQXX_CHECK_EQUAL(forward.pos(), 5, "Not at one-past-end position");
|
||||
PQXX_CHECK_EQUAL(forward.endpos(), 5, "Failed to notice end position");
|
||||
PQXX_CHECK_EQUAL(displacement, 1, "Wrong displacement at end position");
|
||||
|
||||
offset = forward.move(5, displacement);
|
||||
PQXX_CHECK_EQUAL(offset, 0, "move() lied at end of result set");
|
||||
PQXX_CHECK_EQUAL(forward.pos(), 5, "pos() is beyond end");
|
||||
PQXX_CHECK_EQUAL(forward.endpos(), 5, "endpos() changed after end position");
|
||||
PQXX_CHECK_EQUAL(displacement, 0, "Wrong displacement after end position");
|
||||
|
||||
// Move through entire result set at once.
|
||||
pqxx::internal::sql_cursor forward2(
|
||||
tx, "SELECT generate_series(1, 4)", "forward",
|
||||
pqxx::cursor_base::forward_only, pqxx::cursor_base::read_only,
|
||||
pqxx::cursor_base::owned, false);
|
||||
|
||||
// Move through entire result set at once.
|
||||
offset = forward2.move(pqxx::cursor_base::all(), displacement);
|
||||
PQXX_CHECK_EQUAL(offset, 4, "Unexpected number of rows in result set");
|
||||
PQXX_CHECK_EQUAL(displacement, 5, "displacement != rows+1");
|
||||
PQXX_CHECK_EQUAL(forward2.pos(), 5, "Bad pos() after skipping all rows");
|
||||
PQXX_CHECK_EQUAL(forward2.endpos(), 5, "Bad endpos() after skipping");
|
||||
|
||||
pqxx::internal::sql_cursor forward3(
|
||||
tx, "SELECT generate_series(1, 4)", "forward",
|
||||
pqxx::cursor_base::forward_only, pqxx::cursor_base::read_only,
|
||||
pqxx::cursor_base::owned, false);
|
||||
|
||||
// Fetch entire result set at once.
|
||||
auto rows{forward3.fetch(pqxx::cursor_base::all(), displacement)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(rows), 4, "Unexpected number of rows in result set");
|
||||
PQXX_CHECK_EQUAL(displacement, 5, "displacement != rows+1");
|
||||
PQXX_CHECK_EQUAL(forward3.pos(), 5, "Bad pos() after fetching all rows");
|
||||
PQXX_CHECK_EQUAL(forward3.endpos(), 5, "Bad endpos() after fetching");
|
||||
|
||||
pqxx::internal::sql_cursor forward_empty(
|
||||
tx, "SELECT generate_series(0, -1)", "forward_empty",
|
||||
pqxx::cursor_base::forward_only, pqxx::cursor_base::read_only,
|
||||
pqxx::cursor_base::owned, false);
|
||||
|
||||
offset = forward_empty.move(3, displacement);
|
||||
PQXX_CHECK_EQUAL(forward_empty.pos(), 1, "Bad pos() at end of result");
|
||||
PQXX_CHECK_EQUAL(forward_empty.endpos(), 1, "Bad endpos() in empty result");
|
||||
PQXX_CHECK_EQUAL(displacement, 1, "Bad displacement in empty result");
|
||||
PQXX_CHECK_EQUAL(offset, 0, "move() in empty result counted rows");
|
||||
}
|
||||
|
||||
void test_scroll_sql_cursor()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
pqxx::internal::sql_cursor scroll(
|
||||
tx, "SELECT generate_series(1, 10)", "scroll",
|
||||
pqxx::cursor_base::random_access, pqxx::cursor_base::read_only,
|
||||
pqxx::cursor_base::owned, false);
|
||||
|
||||
PQXX_CHECK_EQUAL(scroll.pos(), 0, "Scroll cursor's initial pos() is wrong");
|
||||
PQXX_CHECK_EQUAL(scroll.endpos(), -1, "New scroll cursor has endpos() set");
|
||||
|
||||
auto rows{scroll.fetch(pqxx::cursor_base::next())};
|
||||
PQXX_CHECK_EQUAL(std::size(rows), 1, "Scroll cursor is broken");
|
||||
PQXX_CHECK_EQUAL(scroll.pos(), 1, "Scroll cursor's pos() is broken");
|
||||
PQXX_CHECK_EQUAL(scroll.endpos(), -1, "endpos() set prematurely");
|
||||
|
||||
// Turn cursor around. This is where we begin to feel SQL cursors'
|
||||
// semantics: we pre-decrement, ending up on the position in front of the
|
||||
// first row and returning no rows.
|
||||
rows = scroll.fetch(pqxx::cursor_base::prior());
|
||||
PQXX_CHECK_EQUAL(std::empty(rows), true, "Turning around on fetch() broke");
|
||||
PQXX_CHECK_EQUAL(scroll.pos(), 0, "pos() is not back at zero");
|
||||
PQXX_CHECK_EQUAL(
|
||||
scroll.endpos(), -1, "endpos() set on wrong side of result");
|
||||
|
||||
// Bounce off the left-hand side of the result set. Can't move before the
|
||||
// starting position.
|
||||
auto offset{0}, displacement{0};
|
||||
offset = scroll.move(-3, displacement);
|
||||
PQXX_CHECK_EQUAL(offset, 0, "Rows found before beginning");
|
||||
PQXX_CHECK_EQUAL(displacement, 0, "Failed to bounce off beginning");
|
||||
PQXX_CHECK_EQUAL(scroll.pos(), 0, "pos() moved back from zero");
|
||||
PQXX_CHECK_EQUAL(scroll.endpos(), -1, "endpos() set on left-side bounce");
|
||||
|
||||
// Try bouncing off the left-hand side a little harder. Take 4 paces away
|
||||
// from the boundary and run into it.
|
||||
offset = scroll.move(4, displacement);
|
||||
PQXX_CHECK_EQUAL(offset, 4, "Offset mismatch");
|
||||
PQXX_CHECK_EQUAL(displacement, 4, "Displacement mismatch");
|
||||
PQXX_CHECK_EQUAL(scroll.pos(), 4, "Position mismatch");
|
||||
PQXX_CHECK_EQUAL(scroll.endpos(), -1, "endpos() set at weird time");
|
||||
|
||||
offset = scroll.move(-10, displacement);
|
||||
PQXX_CHECK_EQUAL(offset, 3, "Offset mismatch");
|
||||
PQXX_CHECK_EQUAL(displacement, -4, "Displacement mismatch");
|
||||
PQXX_CHECK_EQUAL(scroll.pos(), 0, "Hard bounce failed");
|
||||
PQXX_CHECK_EQUAL(scroll.endpos(), -1, "endpos() set during hard bounce");
|
||||
|
||||
rows = scroll.fetch(3);
|
||||
PQXX_CHECK_EQUAL(scroll.pos(), 3, "Bad pos()");
|
||||
PQXX_CHECK_EQUAL(std::size(rows), 3, "Wrong number of rows");
|
||||
PQXX_CHECK_EQUAL(rows[2][0].as<int>(), 3, "pos() does not match data");
|
||||
rows = scroll.fetch(-1);
|
||||
PQXX_CHECK_EQUAL(scroll.pos(), 2, "Bad pos()");
|
||||
PQXX_CHECK_EQUAL(rows[0][0].as<int>(), 2, "pos() does not match data");
|
||||
|
||||
rows = scroll.fetch(1);
|
||||
PQXX_CHECK_EQUAL(scroll.pos(), 3, "Bad pos() after inverse turnaround");
|
||||
PQXX_CHECK_EQUAL(rows[0][0].as<int>(), 3, "Data position mismatch");
|
||||
}
|
||||
|
||||
|
||||
void test_adopted_sql_cursor()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
|
||||
tx.exec0(
|
||||
"DECLARE adopted SCROLL CURSOR FOR "
|
||||
"SELECT generate_series(1, 3)");
|
||||
pqxx::internal::sql_cursor adopted(tx, "adopted", pqxx::cursor_base::owned);
|
||||
PQXX_CHECK_EQUAL(adopted.pos(), -1, "Adopted cursor has known pos()");
|
||||
PQXX_CHECK_EQUAL(adopted.endpos(), -1, "Adopted cursor has known endpos()");
|
||||
|
||||
auto displacement{0};
|
||||
auto rows{adopted.fetch(pqxx::cursor_base::all(), displacement)};
|
||||
PQXX_CHECK_EQUAL(std::size(rows), 3, "Wrong number of rows in result");
|
||||
PQXX_CHECK_EQUAL(rows[0][0].as<int>(), 1, "Wrong result data");
|
||||
PQXX_CHECK_EQUAL(rows[2][0].as<int>(), 3, "Wrong result data");
|
||||
PQXX_CHECK_EQUAL(displacement, 4, "Wrong displacement");
|
||||
PQXX_CHECK_EQUAL(
|
||||
adopted.pos(), -1, "End-of-result set pos() on adopted cur");
|
||||
PQXX_CHECK_EQUAL(adopted.endpos(), -1, "endpos() set too early");
|
||||
|
||||
rows = adopted.fetch(pqxx::cursor_base::backward_all(), displacement);
|
||||
PQXX_CHECK_EQUAL(std::size(rows), 3, "Wrong number of rows in result");
|
||||
PQXX_CHECK_EQUAL(rows[0][0].as<int>(), 3, "Wrong result data");
|
||||
PQXX_CHECK_EQUAL(rows[2][0].as<int>(), 1, "Wrong result data");
|
||||
PQXX_CHECK_EQUAL(displacement, -4, "Wrong displacement");
|
||||
PQXX_CHECK_EQUAL(adopted.pos(), 0, "Failed to recognize starting position");
|
||||
PQXX_CHECK_EQUAL(adopted.endpos(), -1, "endpos() set too early");
|
||||
|
||||
auto offset{adopted.move(pqxx::cursor_base::all())};
|
||||
PQXX_CHECK_EQUAL(offset, 3, "Unexpected move() offset");
|
||||
PQXX_CHECK_EQUAL(adopted.pos(), 4, "Bad position on adopted cursor");
|
||||
PQXX_CHECK_EQUAL(adopted.endpos(), 4, "endpos() not set properly");
|
||||
|
||||
// Owned adopted cursors are cleaned up on destruction.
|
||||
pqxx::connection conn2;
|
||||
pqxx::work tx2(conn2, "tx2");
|
||||
tx2.exec0(
|
||||
"DECLARE adopted2 CURSOR FOR "
|
||||
"SELECT generate_series(1, 3)");
|
||||
{
|
||||
pqxx::internal::sql_cursor(tx2, "adopted2", pqxx::cursor_base::owned);
|
||||
}
|
||||
// Modern backends: accessing the cursor now is an error, as you'd expect.
|
||||
PQXX_CHECK_THROWS(
|
||||
tx2.exec("FETCH 1 IN adopted2"), pqxx::sql_error,
|
||||
"Owned adopted cursor not cleaned up");
|
||||
|
||||
tx2.abort();
|
||||
|
||||
pqxx::work tx3(conn2, "tx3");
|
||||
tx3.exec(
|
||||
"DECLARE adopted3 CURSOR FOR "
|
||||
"SELECT generate_series(1, 3)");
|
||||
{
|
||||
pqxx::internal::sql_cursor(tx3, "adopted3", pqxx::cursor_base::loose);
|
||||
}
|
||||
tx3.exec("MOVE 1 IN adopted3");
|
||||
}
|
||||
|
||||
void test_hold_cursor()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
|
||||
// "With hold" cursor is kept after commit.
|
||||
pqxx::internal::sql_cursor with_hold(
|
||||
tx, "SELECT generate_series(1, 3)", "hold_cursor",
|
||||
pqxx::cursor_base::forward_only, pqxx::cursor_base::read_only,
|
||||
pqxx::cursor_base::owned, true);
|
||||
tx.commit();
|
||||
pqxx::work tx2(conn, "tx2");
|
||||
auto rows{with_hold.fetch(1)};
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(rows), 1, "Did not get 1 row from with-hold cursor");
|
||||
|
||||
// Cursor without hold is closed on commit.
|
||||
pqxx::internal::sql_cursor no_hold(
|
||||
tx2, "SELECT generate_series(1, 3)", "no_hold_cursor",
|
||||
pqxx::cursor_base::forward_only, pqxx::cursor_base::read_only,
|
||||
pqxx::cursor_base::owned, false);
|
||||
tx2.commit();
|
||||
pqxx::work tx3(conn, "tx3");
|
||||
PQXX_CHECK_THROWS(
|
||||
no_hold.fetch(1), pqxx::sql_error, "Cursor not closed on commit");
|
||||
}
|
||||
|
||||
|
||||
void cursor_tests()
|
||||
{
|
||||
test_forward_sql_cursor();
|
||||
test_scroll_sql_cursor();
|
||||
test_adopted_sql_cursor();
|
||||
test_hold_cursor();
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(cursor_tests);
|
||||
} // namespace
|
||||
@@ -0,0 +1,85 @@
|
||||
#include <pqxx/cursor>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_stateless_cursor()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
|
||||
pqxx::stateless_cursor<
|
||||
pqxx::cursor_base::read_only, pqxx::cursor_base::owned>
|
||||
empty(tx, "SELECT generate_series(0, -1)", "empty", false);
|
||||
|
||||
auto rows{empty.retrieve(0, 0)};
|
||||
PQXX_CHECK_EQUAL(std::empty(rows), true, "Empty result not empty");
|
||||
rows = empty.retrieve(0, 1);
|
||||
PQXX_CHECK_EQUAL(std::size(rows), 0, "Empty result returned rows");
|
||||
|
||||
PQXX_CHECK_EQUAL(empty.size(), 0, "Empty cursor not empty");
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
empty.retrieve(1, 0), std::out_of_range, "Empty cursor tries to retrieve");
|
||||
|
||||
pqxx::stateless_cursor<
|
||||
pqxx::cursor_base::read_only, pqxx::cursor_base::owned>
|
||||
stateless(tx, "SELECT generate_series(0, 9)", "stateless", false);
|
||||
|
||||
PQXX_CHECK_EQUAL(stateless.size(), 10, "stateless_cursor::size() mismatch");
|
||||
|
||||
// Retrieve nothing.
|
||||
rows = stateless.retrieve(1, 1);
|
||||
PQXX_CHECK_EQUAL(std::size(rows), 0, "1-to-1 retrieval not empty");
|
||||
|
||||
// Retrieve two rows.
|
||||
rows = stateless.retrieve(1, 3);
|
||||
PQXX_CHECK_EQUAL(std::size(rows), 2, "Retrieved wrong number of rows");
|
||||
PQXX_CHECK_EQUAL(rows[0][0].as<int>(), 1, "Data/position mismatch");
|
||||
PQXX_CHECK_EQUAL(rows[1][0].as<int>(), 2, "Data/position mismatch");
|
||||
|
||||
// Retrieve same rows in reverse.
|
||||
rows = stateless.retrieve(2, 0);
|
||||
PQXX_CHECK_EQUAL(std::size(rows), 2, "Retrieved wrong number of rows");
|
||||
PQXX_CHECK_EQUAL(rows[0][0].as<int>(), 2, "Data/position mismatch");
|
||||
PQXX_CHECK_EQUAL(rows[1][0].as<int>(), 1, "Data/position mismatch");
|
||||
|
||||
// Retrieve beyond end.
|
||||
rows = stateless.retrieve(9, 13);
|
||||
PQXX_CHECK_EQUAL(std::size(rows), 1, "Row count wrong at end");
|
||||
PQXX_CHECK_EQUAL(rows[0][0].as<int>(), 9, "Data/pos mismatch at end");
|
||||
|
||||
// Retrieve beyond beginning.
|
||||
rows = stateless.retrieve(0, -4);
|
||||
PQXX_CHECK_EQUAL(std::size(rows), 1, "Row count wrong at beginning");
|
||||
PQXX_CHECK_EQUAL(rows[0][0].as<int>(), 0, "Data/pos mismatch at beginning");
|
||||
|
||||
// Retrieve entire result set backwards.
|
||||
rows = stateless.retrieve(10, -15);
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(rows), 10, "Reverse complete retrieval is broken");
|
||||
PQXX_CHECK_EQUAL(rows[0][0].as<int>(), 9, "Data mismatch");
|
||||
PQXX_CHECK_EQUAL(rows[9][0].as<int>(), 0, "Data mismatch");
|
||||
|
||||
// Normal usage pattern: step through result set, 4 rows at a time.
|
||||
rows = stateless.retrieve(0, 4);
|
||||
PQXX_CHECK_EQUAL(std::size(rows), 4, "Wrong batch size");
|
||||
PQXX_CHECK_EQUAL(rows[0][0].as<int>(), 0, "Batch in wrong place");
|
||||
PQXX_CHECK_EQUAL(rows[3][0].as<int>(), 3, "Batch in wrong place");
|
||||
|
||||
rows = stateless.retrieve(4, 8);
|
||||
PQXX_CHECK_EQUAL(std::size(rows), 4, "Wrong batch size");
|
||||
PQXX_CHECK_EQUAL(rows[0][0].as<int>(), 4, "Batch in wrong place");
|
||||
PQXX_CHECK_EQUAL(rows[3][0].as<int>(), 7, "Batch in wrong place");
|
||||
|
||||
rows = stateless.retrieve(8, 12);
|
||||
PQXX_CHECK_EQUAL(std::size(rows), 2, "Wrong batch size");
|
||||
PQXX_CHECK_EQUAL(rows[0][0].as<int>(), 8, "Batch in wrong place");
|
||||
PQXX_CHECK_EQUAL(rows[1][0].as<int>(), 9, "Batch in wrong place");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_stateless_cursor);
|
||||
} // namespace
|
||||
@@ -0,0 +1,143 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
enum colour
|
||||
{
|
||||
red,
|
||||
green,
|
||||
blue
|
||||
};
|
||||
enum class weather : short
|
||||
{
|
||||
hot,
|
||||
cold,
|
||||
wet
|
||||
};
|
||||
enum class many : unsigned long long
|
||||
{
|
||||
bottom = 0,
|
||||
top = std::numeric_limits<unsigned long long>::max(),
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
PQXX_DECLARE_ENUM_CONVERSION(colour);
|
||||
PQXX_DECLARE_ENUM_CONVERSION(weather);
|
||||
PQXX_DECLARE_ENUM_CONVERSION(many);
|
||||
} // namespace pqxx
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_strconv_bool()
|
||||
{
|
||||
PQXX_CHECK_EQUAL(pqxx::to_string(false), "false", "Wrong to_string(false).");
|
||||
PQXX_CHECK_EQUAL(pqxx::to_string(true), "true", "Wrong to_string(true).");
|
||||
|
||||
bool result;
|
||||
pqxx::from_string("false", result);
|
||||
PQXX_CHECK_EQUAL(result, false, "Wrong from_string('false').");
|
||||
pqxx::from_string("FALSE", result);
|
||||
PQXX_CHECK_EQUAL(result, false, "Wrong from_string('FALSE').");
|
||||
pqxx::from_string("f", result);
|
||||
PQXX_CHECK_EQUAL(result, false, "Wrong from_string('f').");
|
||||
pqxx::from_string("F", result);
|
||||
PQXX_CHECK_EQUAL(result, false, "Wrong from_string('F').");
|
||||
pqxx::from_string("0", result);
|
||||
PQXX_CHECK_EQUAL(result, false, "Wrong from_string('0').");
|
||||
pqxx::from_string("true", result);
|
||||
PQXX_CHECK_EQUAL(result, true, "Wrong from_string('true').");
|
||||
pqxx::from_string("TRUE", result);
|
||||
PQXX_CHECK_EQUAL(result, true, "Wrong from_string('TRUE').");
|
||||
pqxx::from_string("t", result);
|
||||
PQXX_CHECK_EQUAL(result, true, "Wrong from_string('t').");
|
||||
pqxx::from_string("T", result);
|
||||
PQXX_CHECK_EQUAL(result, true, "Wrong from_string('T').");
|
||||
pqxx::from_string("1", result);
|
||||
PQXX_CHECK_EQUAL(result, true, "Wrong from_string('1').");
|
||||
}
|
||||
|
||||
|
||||
void test_strconv_enum()
|
||||
{
|
||||
PQXX_CHECK_EQUAL(pqxx::to_string(red), "0", "Enum value did not convert.");
|
||||
PQXX_CHECK_EQUAL(pqxx::to_string(green), "1", "Enum value did not convert.");
|
||||
PQXX_CHECK_EQUAL(pqxx::to_string(blue), "2", "Enum value did not convert.");
|
||||
|
||||
colour col;
|
||||
pqxx::from_string("2", col);
|
||||
PQXX_CHECK_EQUAL(col, blue, "Could not recover enum value from string.");
|
||||
}
|
||||
|
||||
|
||||
void test_strconv_class_enum()
|
||||
{
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(weather::hot), "0", "Class enum value did not convert.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(weather::wet), "2", "Enum value did not convert.");
|
||||
|
||||
weather w;
|
||||
pqxx::from_string("2", w);
|
||||
PQXX_CHECK_EQUAL(
|
||||
w, weather::wet, "Could not recover class enum value from string.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(many::bottom), "0",
|
||||
"Small wide enum did not convert right.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(many::top),
|
||||
pqxx::to_string(std::numeric_limits<unsigned long long>::max()),
|
||||
"Large wide enum did not convert right.");
|
||||
}
|
||||
|
||||
|
||||
void test_strconv_optional()
|
||||
{
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::to_string(std::optional<int>{}), pqxx::conversion_error,
|
||||
"Converting an empty optional did not throw conversion error.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(std::optional<int>{std::in_place, 10}), "10",
|
||||
"std::optional<int> does not convert right.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(std::optional<int>{std::in_place, -10000}), "-10000",
|
||||
"std::optional<int> does not convert right.");
|
||||
}
|
||||
|
||||
|
||||
void test_strconv_smart_pointer()
|
||||
{
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::to_string(std::unique_ptr<int>{}), pqxx::conversion_error,
|
||||
"Converting an empty unique_ptr did not throw conversion error.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(std::make_unique<int>(10)), "10",
|
||||
"std::unique_ptr<int> does not convert right.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(std::make_unique<int>(-10000)), "-10000",
|
||||
"std::unique_ptr<int> does not convert right.");
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::to_string(std::shared_ptr<int>{}), pqxx::conversion_error,
|
||||
"Converting an empty shared_ptr did not throw conversion error.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(std::make_shared<int>(10)), "10",
|
||||
"std::shared_ptr<int> does not convert right.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(std::make_shared<int>(-10000)), "-10000",
|
||||
"std::shared_ptr<int> does not convert right.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_strconv_bool);
|
||||
PQXX_REGISTER_TEST(test_strconv_enum);
|
||||
PQXX_REGISTER_TEST(test_strconv_class_enum);
|
||||
PQXX_REGISTER_TEST(test_strconv_optional);
|
||||
PQXX_REGISTER_TEST(test_strconv_smart_pointer);
|
||||
} // namespace
|
||||
@@ -0,0 +1,344 @@
|
||||
#include <pqxx/stream_from>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
#include "../test_types.hxx"
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include <optional>
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_nonoptionals(pqxx::connection &connection)
|
||||
{
|
||||
pqxx::work tx{connection};
|
||||
auto extractor{pqxx::stream_from::query(
|
||||
tx, "SELECT * FROM stream_from_test ORDER BY number0")};
|
||||
PQXX_CHECK(extractor, "stream_from failed to initialize.");
|
||||
|
||||
std::tuple<int, std::string, int, ipv4, std::string, bytea> got_tuple;
|
||||
|
||||
try
|
||||
{
|
||||
// We can't read the "910" row -- it contains nulls, which our tuple does
|
||||
// not accept.
|
||||
extractor >> got_tuple;
|
||||
PQXX_CHECK_NOTREACHED(
|
||||
"Failed to fail to stream null values into null-less fields.");
|
||||
}
|
||||
catch (pqxx::conversion_error const &e)
|
||||
{
|
||||
std::string const what{e.what()};
|
||||
if (what.find("null") == std::string::npos)
|
||||
throw;
|
||||
pqxx::test::expected_exception(
|
||||
"Could not stream nulls into null-less fields: " + what);
|
||||
}
|
||||
|
||||
// The stream is still good though.
|
||||
// The second tuple is fine.
|
||||
extractor >> got_tuple;
|
||||
PQXX_CHECK(extractor, "Stream ended prematurely.");
|
||||
|
||||
PQXX_CHECK_EQUAL(std::get<0>(got_tuple), 1234, "Bad value.");
|
||||
// Don't know much about the timestamp, but let's assume it starts with a
|
||||
// year in the second millennium.
|
||||
PQXX_CHECK(
|
||||
std::get<1>(got_tuple).at(0) == '2', "Bad value. Expected timestamp.");
|
||||
PQXX_CHECK_LESS(
|
||||
std::size(std::get<1>(got_tuple)), 40u, "Unexpected length.");
|
||||
PQXX_CHECK_GREATER(
|
||||
std::size(std::get<1>(got_tuple)), 20u, "Unexpected length.");
|
||||
PQXX_CHECK_EQUAL(std::get<2>(got_tuple), 4321, "Bad value.");
|
||||
PQXX_CHECK_EQUAL(std::get<3>(got_tuple), (ipv4{8, 8, 8, 8}), "Bad value.");
|
||||
PQXX_CHECK_EQUAL(std::get<4>(got_tuple), "hello\n \tworld", "Bad value.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::get<5>(got_tuple), (bytea{'\x00', '\x01', '\x02'}), "Bad value.");
|
||||
|
||||
// The third tuple contains some nulls. For what it's worth, when we *know*
|
||||
// that we're getting nulls, we can stream them into nullptr_t fields.
|
||||
std::tuple<
|
||||
int, std::string, std::nullptr_t, std::nullptr_t, std::string, bytea>
|
||||
tup_w_nulls;
|
||||
|
||||
extractor >> tup_w_nulls;
|
||||
PQXX_CHECK(extractor, "Stream ended prematurely.");
|
||||
|
||||
PQXX_CHECK_EQUAL(std::get<0>(tup_w_nulls), 5678, "Bad value.");
|
||||
PQXX_CHECK(std::get<2>(tup_w_nulls) == nullptr, "Bad null.");
|
||||
PQXX_CHECK(std::get<3>(tup_w_nulls) == nullptr, "Bad null.");
|
||||
|
||||
// We're at the end of the stream.
|
||||
extractor >> tup_w_nulls;
|
||||
PQXX_CHECK(not extractor, "Stream did not end.");
|
||||
|
||||
// Of course we can't stream a non-null value into a nullptr field.
|
||||
auto ex2{pqxx::stream_from::query(tx, "SELECT 1")};
|
||||
std::tuple<std::nullptr_t> null_tup;
|
||||
try
|
||||
{
|
||||
ex2 >> null_tup;
|
||||
PQXX_CHECK_NOTREACHED(
|
||||
"stream_from should have refused to convert non-null value to "
|
||||
"nullptr_t.");
|
||||
}
|
||||
catch (pqxx::conversion_error const &e)
|
||||
{
|
||||
std::string const what{e.what()};
|
||||
if (what.find("null") == std::string::npos)
|
||||
throw;
|
||||
pqxx::test::expected_exception(
|
||||
std::string{"Could not extract row: "} + what);
|
||||
}
|
||||
ex2 >> null_tup;
|
||||
PQXX_CHECK(not ex2, "Stream did not end.");
|
||||
|
||||
PQXX_CHECK_SUCCEEDS(
|
||||
tx.exec1("SELECT 1"), "Could not use transaction after stream_from.");
|
||||
}
|
||||
|
||||
|
||||
void test_bad_tuples(pqxx::connection &conn)
|
||||
{
|
||||
pqxx::work tx{conn};
|
||||
auto extractor{pqxx::stream_from::table(tx, {"stream_from_test"})};
|
||||
PQXX_CHECK(extractor, "stream_from failed to initialize");
|
||||
|
||||
std::tuple<int> got_tuple_too_short;
|
||||
try
|
||||
{
|
||||
extractor >> got_tuple_too_short;
|
||||
PQXX_CHECK_NOTREACHED("stream_from improperly read first row");
|
||||
}
|
||||
catch (pqxx::usage_error const &e)
|
||||
{
|
||||
std::string what{e.what()};
|
||||
if (
|
||||
what.find("1") == std::string::npos or
|
||||
what.find("6") == std::string::npos)
|
||||
throw;
|
||||
pqxx::test::expected_exception("Tuple is wrong size: " + what);
|
||||
}
|
||||
|
||||
std::tuple<int, std::string, int, ipv4, std::string, bytea, std::string>
|
||||
got_tuple_too_long;
|
||||
try
|
||||
{
|
||||
extractor >> got_tuple_too_long;
|
||||
PQXX_CHECK_NOTREACHED("stream_from improperly read first row");
|
||||
}
|
||||
catch (pqxx::usage_error const &e)
|
||||
{
|
||||
std::string what{e.what()};
|
||||
if (
|
||||
what.find("6") == std::string::npos or
|
||||
what.find("7") == std::string::npos)
|
||||
throw;
|
||||
pqxx::test::expected_exception("Could not extract row: " + what);
|
||||
}
|
||||
|
||||
extractor.complete();
|
||||
}
|
||||
|
||||
|
||||
#define ASSERT_FIELD_EQUAL(OPT, VAL) \
|
||||
PQXX_CHECK(static_cast<bool>(OPT), "unexpected null field"); \
|
||||
PQXX_CHECK_EQUAL(*OPT, VAL, "field value mismatch")
|
||||
#define ASSERT_FIELD_NULL(OPT) \
|
||||
PQXX_CHECK(not static_cast<bool>(OPT), "expected null field")
|
||||
|
||||
|
||||
template<template<typename...> class O>
|
||||
void test_optional(pqxx::connection &connection)
|
||||
{
|
||||
pqxx::work tx{connection};
|
||||
auto extractor{pqxx::stream_from::query(
|
||||
tx, "SELECT * FROM stream_from_test ORDER BY number0")};
|
||||
PQXX_CHECK(extractor, "stream_from failed to initialize");
|
||||
|
||||
std::tuple<int, O<std::string>, O<int>, O<ipv4>, O<std::string>, O<bytea>>
|
||||
got_tuple;
|
||||
|
||||
extractor >> got_tuple;
|
||||
PQXX_CHECK(extractor, "stream_from failed to read third row");
|
||||
PQXX_CHECK_EQUAL(std::get<0>(got_tuple), 910, "field value mismatch");
|
||||
ASSERT_FIELD_NULL(std::get<1>(got_tuple));
|
||||
ASSERT_FIELD_NULL(std::get<2>(got_tuple));
|
||||
ASSERT_FIELD_NULL(std::get<3>(got_tuple));
|
||||
ASSERT_FIELD_EQUAL(std::get<4>(got_tuple), "\\N");
|
||||
ASSERT_FIELD_EQUAL(std::get<5>(got_tuple), bytea{});
|
||||
|
||||
extractor >> got_tuple;
|
||||
PQXX_CHECK(extractor, "stream_from failed to read first row.");
|
||||
PQXX_CHECK_EQUAL(std::get<0>(got_tuple), 1234, "Field value mismatch.");
|
||||
PQXX_CHECK(
|
||||
static_cast<bool>(std::get<1>(got_tuple)), "Unexpected null field.");
|
||||
// PQXX_CHECK_EQUAL(*std::get<1>(got_tuple), , "field value mismatch");
|
||||
ASSERT_FIELD_EQUAL(std::get<2>(got_tuple), 4321);
|
||||
ASSERT_FIELD_EQUAL(std::get<3>(got_tuple), (ipv4{8, 8, 8, 8}));
|
||||
ASSERT_FIELD_EQUAL(std::get<4>(got_tuple), "hello\n \tworld");
|
||||
ASSERT_FIELD_EQUAL(std::get<5>(got_tuple), (bytea{'\x00', '\x01', '\x02'}));
|
||||
|
||||
extractor >> got_tuple;
|
||||
PQXX_CHECK(extractor, "stream_from failed to read second row");
|
||||
PQXX_CHECK_EQUAL(std::get<0>(got_tuple), 5678, "field value mismatch");
|
||||
ASSERT_FIELD_EQUAL(std::get<1>(got_tuple), "2018-11-17 21:23:00");
|
||||
ASSERT_FIELD_NULL(std::get<2>(got_tuple));
|
||||
ASSERT_FIELD_NULL(std::get<3>(got_tuple));
|
||||
ASSERT_FIELD_EQUAL(std::get<4>(got_tuple), "\u3053\u3093\u306b\u3061\u308f");
|
||||
ASSERT_FIELD_EQUAL(
|
||||
std::get<5>(got_tuple), (bytea{'f', 'o', 'o', ' ', 'b', 'a', 'r', '\0'}));
|
||||
|
||||
extractor >> got_tuple;
|
||||
PQXX_CHECK(not extractor, "stream_from failed to detect end of stream");
|
||||
|
||||
extractor.complete();
|
||||
}
|
||||
|
||||
|
||||
void test_stream_from()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
tx.exec0(
|
||||
"CREATE TEMP TABLE stream_from_test ("
|
||||
"number0 INT NOT NULL,"
|
||||
"ts1 TIMESTAMP NULL,"
|
||||
"number2 INT NULL,"
|
||||
"addr3 INET NULL,"
|
||||
"txt4 TEXT NULL,"
|
||||
"bin5 BYTEA NOT NULL"
|
||||
")");
|
||||
tx.exec_params(
|
||||
"INSERT INTO stream_from_test VALUES ($1,$2,$3,$4,$5,$6)", 910, nullptr,
|
||||
nullptr, nullptr, "\\N", bytea{});
|
||||
tx.exec_params(
|
||||
"INSERT INTO stream_from_test VALUES ($1,$2,$3,$4,$5,$6)", 1234, "now",
|
||||
4321, ipv4{8, 8, 8, 8}, "hello\n \tworld", bytea{'\x00', '\x01', '\x02'});
|
||||
tx.exec_params(
|
||||
"INSERT INTO stream_from_test VALUES ($1,$2,$3,$4,$5,$6)", 5678,
|
||||
"2018-11-17 21:23:00", nullptr, nullptr, "\u3053\u3093\u306b\u3061\u308f",
|
||||
bytea{'f', 'o', 'o', ' ', 'b', 'a', 'r', '\0'});
|
||||
tx.commit();
|
||||
|
||||
test_nonoptionals(conn);
|
||||
test_bad_tuples(conn);
|
||||
std::cout << "testing `std::unique_ptr` as optional...\n";
|
||||
test_optional<std::unique_ptr>(conn);
|
||||
std::cout << "testing `std::optional` as optional...\n";
|
||||
test_optional<std::optional>(conn);
|
||||
}
|
||||
|
||||
|
||||
void test_stream_from_does_escaping()
|
||||
{
|
||||
std::string const input{"a\t\n\n\n \\b\nc"};
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
tx.exec0("CREATE TEMP TABLE badstr (str text)");
|
||||
tx.exec0("INSERT INTO badstr (str) VALUES (" + tx.quote(input) + ")");
|
||||
auto reader{pqxx::stream_from::table(tx, {"badstr"})};
|
||||
std::tuple<std::string> out;
|
||||
reader >> out;
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::get<0>(out), input, "stream_from got weird characters wrong.");
|
||||
}
|
||||
|
||||
|
||||
void test_stream_from_does_iteration()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
tx.exec0("CREATE TEMP TABLE str (s text)");
|
||||
tx.exec0("INSERT INTO str (s) VALUES ('foo')");
|
||||
auto reader{pqxx::stream_from::table(tx, {"str"})};
|
||||
|
||||
int i{0};
|
||||
std::string out;
|
||||
for (std::tuple<std::string> t : reader.iter<std::string>())
|
||||
{
|
||||
i++;
|
||||
out = std::get<0>(t);
|
||||
}
|
||||
PQXX_CHECK_EQUAL(i, 1, "Wrong number of iterations.");
|
||||
PQXX_CHECK_EQUAL(out, "foo", "Got wrong string.");
|
||||
|
||||
tx.exec0("INSERT INTO str (s) VALUES ('bar')");
|
||||
i = 0;
|
||||
std::set<std::string> strings;
|
||||
auto reader2{pqxx::stream_from::table(tx, {"str"})};
|
||||
for (std::tuple<std::string> t : reader2.iter<std::string>())
|
||||
{
|
||||
i++;
|
||||
strings.insert(std::get<0>(t));
|
||||
}
|
||||
PQXX_CHECK_EQUAL(i, 2, "Wrong number of iterations.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::size(strings), 2u, "Wrong number of strings retrieved.");
|
||||
PQXX_CHECK(strings.find("foo") != std::end(strings), "Missing key.");
|
||||
PQXX_CHECK(strings.find("bar") != std::end(strings), "Missing key.");
|
||||
}
|
||||
|
||||
|
||||
void test_transaction_stream_from()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
tx.exec0("CREATE TEMP TABLE sample (id integer, name varchar)");
|
||||
tx.exec0("INSERT INTO sample (id, name) VALUES (321, 'something')");
|
||||
|
||||
int items{0};
|
||||
int id{0};
|
||||
std::string name;
|
||||
|
||||
for (auto [iid, iname] :
|
||||
tx.stream<int, std::string_view>("SELECT id, name FROM sample"))
|
||||
{
|
||||
items++;
|
||||
id = iid;
|
||||
name = iname;
|
||||
}
|
||||
PQXX_CHECK_EQUAL(items, 1, "Wrong number of iterations.");
|
||||
PQXX_CHECK_EQUAL(id, 321, "Got wrong int.");
|
||||
PQXX_CHECK_EQUAL(name, std::string{"something"}, "Got wrong string.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
tx.query_value<int>("SELECT 4"), 4,
|
||||
"Loop did not relinquish transaction.");
|
||||
}
|
||||
|
||||
|
||||
void test_stream_from_read_row()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
tx.exec0("CREATE TEMP TABLE sample (id integer, name varchar, opt integer)");
|
||||
tx.exec0("INSERT INTO sample (id, name) VALUES (321, 'something')");
|
||||
|
||||
auto stream{pqxx::stream_from::table(tx, {"sample"})};
|
||||
auto fields{stream.read_row()};
|
||||
PQXX_CHECK_EQUAL(fields->size(), 3ul, "Wrong number of fields.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::string((*fields)[0]), "321", "Integer field came out wrong.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
std::string((*fields)[1]), "something", "Text field came out wrong.");
|
||||
PQXX_CHECK(std::data((*fields)[2]) == nullptr, "Null field came out wrong.");
|
||||
|
||||
auto last{stream.read_row()};
|
||||
PQXX_CHECK(last == nullptr, "No null pointer at end of stream.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_stream_from);
|
||||
PQXX_REGISTER_TEST(test_stream_from_does_escaping);
|
||||
PQXX_REGISTER_TEST(test_stream_from_does_iteration);
|
||||
PQXX_REGISTER_TEST(test_transaction_stream_from);
|
||||
PQXX_REGISTER_TEST(test_stream_from_read_row);
|
||||
} // namespace
|
||||
@@ -0,0 +1,445 @@
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
|
||||
#include <pqxx/stream_to>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
#include "../test_types.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string truncate_sql_error(std::string const &what)
|
||||
{
|
||||
auto trunc{what.substr(0, what.find('\n'))};
|
||||
if (std::size(trunc) > 64)
|
||||
trunc = trunc.substr(0, 61) + "...";
|
||||
return trunc;
|
||||
}
|
||||
|
||||
|
||||
void test_nonoptionals(pqxx::connection &connection)
|
||||
{
|
||||
pqxx::work tx{connection};
|
||||
auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
|
||||
PQXX_CHECK(inserter, "stream_to failed to initialize");
|
||||
|
||||
auto const nonascii{"\u3053\u3093\u306b\u3061\u308f"};
|
||||
bytea const binary{'\x00', '\x01', '\x02'},
|
||||
text{'f', 'o', 'o', ' ', 'b', 'a', 'r', '\0'};
|
||||
|
||||
inserter << std::make_tuple(
|
||||
1234, "now", 4321, ipv4{8, 8, 4, 4}, "hello nonoptional world", binary);
|
||||
inserter << std::make_tuple(
|
||||
5678, "2018-11-17 21:23:00", nullptr, nullptr, nonascii, text);
|
||||
inserter << std::make_tuple(910, nullptr, nullptr, nullptr, "\\N", bytea{});
|
||||
|
||||
inserter.complete();
|
||||
|
||||
auto r1{tx.exec1("SELECT * FROM stream_to_test WHERE number0 = 1234")};
|
||||
PQXX_CHECK_EQUAL(r1[0].as<int>(), 1234, "Read back wrong first int.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
r1[4].as<std::string>(), "hello nonoptional world",
|
||||
"Read back wrong string.");
|
||||
PQXX_CHECK_EQUAL(r1[3].as<ipv4>(), ipv4(8, 8, 4, 4), "Read back wrong ip.");
|
||||
PQXX_CHECK_EQUAL(r1[5].as<bytea>(), binary, "Read back wrong bytea.");
|
||||
|
||||
auto r2{tx.exec1("SELECT * FROM stream_to_test WHERE number0 = 5678")};
|
||||
PQXX_CHECK_EQUAL(r2[0].as<int>(), 5678, "Wrong int on second row.");
|
||||
PQXX_CHECK(r2[2].is_null(), "Field 2 was meant to be null.");
|
||||
PQXX_CHECK(r2[3].is_null(), "Field 3 was meant to be null.");
|
||||
PQXX_CHECK_EQUAL(r2[4].as<std::string>(), nonascii, "Wrong non-ascii text.");
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
void test_nonoptionals_fold(pqxx::connection &connection)
|
||||
{
|
||||
pqxx::work tx{connection};
|
||||
auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
|
||||
PQXX_CHECK(inserter, "stream_to failed to initialize");
|
||||
|
||||
auto const nonascii{"\u3053\u3093\u306b\u3061\u308f"};
|
||||
bytea const binary{'\x00', '\x01', '\x02'},
|
||||
text{'f', 'o', 'o', ' ', 'b', 'a', 'r', '\0'};
|
||||
|
||||
inserter.write_values(
|
||||
1234, "now", 4321, ipv4{8, 8, 4, 4}, "hello nonoptional world", binary);
|
||||
inserter.write_values(
|
||||
5678, "2018-11-17 21:23:00", nullptr, nullptr, nonascii, text);
|
||||
inserter.write_values(910, nullptr, nullptr, nullptr, "\\N", bytea{});
|
||||
|
||||
inserter.complete();
|
||||
|
||||
auto r1{tx.exec1("SELECT * FROM stream_to_test WHERE number0 = 1234")};
|
||||
PQXX_CHECK_EQUAL(r1[0].as<int>(), 1234, "Read back wrong first int.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
r1[4].as<std::string>(), "hello nonoptional world",
|
||||
"Read back wrong string.");
|
||||
PQXX_CHECK_EQUAL(r1[3].as<ipv4>(), ipv4(8, 8, 4, 4), "Read back wrong ip.");
|
||||
PQXX_CHECK_EQUAL(r1[5].as<bytea>(), binary, "Read back wrong bytera.");
|
||||
|
||||
auto r2{tx.exec1("SELECT * FROM stream_to_test WHERE number0 = 5678")};
|
||||
PQXX_CHECK_EQUAL(r2[0].as<int>(), 5678, "Wrong int on second row.");
|
||||
PQXX_CHECK(r2[2].is_null(), "Field 2 was meant to be null.");
|
||||
PQXX_CHECK(r2[3].is_null(), "Field 3 was meant to be null.");
|
||||
PQXX_CHECK_EQUAL(r2[4].as<std::string>(), nonascii, "Wrong non-ascii text.");
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
|
||||
/// Try to violate stream_to_test's not-null constraint using a stream_to.
|
||||
void insert_bad_null_tuple(pqxx::stream_to &inserter)
|
||||
{
|
||||
inserter << std::make_tuple(
|
||||
nullptr, "now", 4321, ipv4{8, 8, 8, 8}, "hello world",
|
||||
bytea{'\x00', '\x01', '\x02'});
|
||||
inserter.complete();
|
||||
}
|
||||
|
||||
|
||||
void test_bad_null(pqxx::connection &connection)
|
||||
{
|
||||
pqxx::work tx{connection};
|
||||
auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
|
||||
PQXX_CHECK(inserter, "stream_to failed to initialize");
|
||||
PQXX_CHECK_THROWS(
|
||||
insert_bad_null_tuple(inserter), pqxx::not_null_violation,
|
||||
"Did not expected not_null_violation when stream_to inserts a bad null.");
|
||||
}
|
||||
|
||||
|
||||
/// Try to violate stream_to_test's not-null construct using a stream_to.
|
||||
void insert_bad_null_write(pqxx::stream_to &inserter)
|
||||
{
|
||||
inserter.write_values(
|
||||
nullptr, "now", 4321, ipv4{8, 8, 8, 8}, "hello world",
|
||||
bytea{'\x00', '\x01', '\x02'});
|
||||
inserter.complete();
|
||||
}
|
||||
|
||||
|
||||
void test_bad_null_fold(pqxx::connection &connection)
|
||||
{
|
||||
pqxx::work tx{connection};
|
||||
auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
|
||||
PQXX_CHECK(inserter, "stream_to failed to initialize");
|
||||
PQXX_CHECK_THROWS(
|
||||
insert_bad_null_write(inserter), pqxx::not_null_violation,
|
||||
"Did not expected not_null_violation when stream_to inserts a bad null.");
|
||||
}
|
||||
|
||||
|
||||
void test_too_few_fields(pqxx::connection &connection)
|
||||
{
|
||||
pqxx::work tx{connection};
|
||||
auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
|
||||
PQXX_CHECK(inserter, "stream_to failed to initialize");
|
||||
|
||||
try
|
||||
{
|
||||
inserter << std::make_tuple(1234, "now", 4321, ipv4{8, 8, 8, 8});
|
||||
inserter.complete();
|
||||
tx.commit();
|
||||
PQXX_CHECK_NOTREACHED("stream_from improperly inserted row");
|
||||
}
|
||||
catch (pqxx::sql_error const &e)
|
||||
{
|
||||
std::string what{e.what()};
|
||||
if (what.find("missing data for column") == std::string::npos)
|
||||
throw;
|
||||
pqxx::test::expected_exception(
|
||||
"Could not insert row: " + truncate_sql_error(what));
|
||||
}
|
||||
}
|
||||
|
||||
void test_too_few_fields_fold(pqxx::connection &connection)
|
||||
{
|
||||
pqxx::work tx{connection};
|
||||
auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
|
||||
PQXX_CHECK(inserter, "stream_to failed to initialize");
|
||||
|
||||
try
|
||||
{
|
||||
inserter.write_values(1234, "now", 4321, ipv4{8, 8, 8, 8});
|
||||
inserter.complete();
|
||||
tx.commit();
|
||||
PQXX_CHECK_NOTREACHED("stream_from_fold improperly inserted row");
|
||||
}
|
||||
catch (pqxx::sql_error const &e)
|
||||
{
|
||||
std::string what{e.what()};
|
||||
if (what.find("missing data for column") == std::string::npos)
|
||||
throw;
|
||||
pqxx::test::expected_exception(
|
||||
"Fold - Could not insert row: " + truncate_sql_error(what));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void test_too_many_fields(pqxx::connection &connection)
|
||||
{
|
||||
pqxx::work tx{connection};
|
||||
auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
|
||||
PQXX_CHECK(inserter, "stream_to failed to initialize");
|
||||
|
||||
try
|
||||
{
|
||||
inserter << std::make_tuple(
|
||||
1234, "now", 4321, ipv4{8, 8, 8, 8}, "hello world",
|
||||
bytea{'\x00', '\x01', '\x02'}, 5678);
|
||||
inserter.complete();
|
||||
tx.commit();
|
||||
PQXX_CHECK_NOTREACHED("stream_from improperly inserted row");
|
||||
}
|
||||
catch (pqxx::sql_error const &e)
|
||||
{
|
||||
std::string what{e.what()};
|
||||
if (what.find("extra data") == std::string::npos)
|
||||
throw;
|
||||
pqxx::test::expected_exception(
|
||||
"Could not insert row: " + truncate_sql_error(what));
|
||||
}
|
||||
}
|
||||
|
||||
void test_too_many_fields_fold(pqxx::connection &connection)
|
||||
{
|
||||
pqxx::work tx{connection};
|
||||
auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
|
||||
PQXX_CHECK(inserter, "stream_to failed to initialize");
|
||||
|
||||
try
|
||||
{
|
||||
inserter.write_values(
|
||||
1234, "now", 4321, ipv4{8, 8, 8, 8}, "hello world",
|
||||
bytea{'\x00', '\x01', '\x02'}, 5678);
|
||||
inserter.complete();
|
||||
tx.commit();
|
||||
PQXX_CHECK_NOTREACHED("stream_from_fold improperly inserted row");
|
||||
}
|
||||
catch (pqxx::sql_error const &e)
|
||||
{
|
||||
std::string what{e.what()};
|
||||
if (what.find("extra data") == std::string::npos)
|
||||
throw;
|
||||
pqxx::test::expected_exception(
|
||||
"Fold - Could not insert row: " + truncate_sql_error(what));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void test_stream_to_does_nonnull_optional()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
tx.exec0("CREATE TEMP TABLE foo(x integer, y text)");
|
||||
auto inserter{pqxx::stream_to::table(tx, {"foo"})};
|
||||
inserter.write_values(
|
||||
std::optional<int>{368}, std::optional<std::string>{"Text"});
|
||||
inserter.complete();
|
||||
auto const row{tx.exec1("SELECT x, y FROM foo")};
|
||||
PQXX_CHECK_EQUAL(
|
||||
row[0].as<std::string>(), "368", "Non-null int optional came out wrong.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
row[1].as<std::string>(), "Text",
|
||||
"Non-null string optional came out wrong.");
|
||||
}
|
||||
|
||||
|
||||
template<template<typename...> class O>
|
||||
void test_optional(pqxx::connection &connection)
|
||||
{
|
||||
pqxx::work tx{connection};
|
||||
auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
|
||||
PQXX_CHECK(inserter, "stream_to failed to initialize");
|
||||
|
||||
inserter << std::make_tuple(
|
||||
910, O<std::string>{pqxx::nullness<O<std::string>>::null()},
|
||||
O<int>{pqxx::nullness<O<int>>::null()},
|
||||
O<ipv4>{pqxx::nullness<O<ipv4>>::null()}, "\\N", bytea{});
|
||||
|
||||
inserter.complete();
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
template<template<typename...> class O>
|
||||
void test_optional_fold(pqxx::connection &connection)
|
||||
{
|
||||
pqxx::work tx{connection};
|
||||
auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
|
||||
PQXX_CHECK(inserter, "stream_to failed to initialize");
|
||||
|
||||
inserter.write_values(
|
||||
910, O<std::string>{pqxx::nullness<O<std::string>>::null()},
|
||||
O<int>{pqxx::nullness<O<int>>::null()},
|
||||
O<ipv4>{pqxx::nullness<O<ipv4>>::null()}, "\\N", bytea{});
|
||||
|
||||
inserter.complete();
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
|
||||
// As an alternative to a tuple, you can also insert a container.
|
||||
void test_container_stream_to()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
tx.exec0("CREATE TEMP TABLE test_container(a integer, b integer)");
|
||||
|
||||
auto inserter{pqxx::stream_to::table(tx, {"test_container"})};
|
||||
|
||||
inserter << std::vector{112, 244};
|
||||
inserter.complete();
|
||||
|
||||
auto read{tx.exec1("SELECT * FROM test_container")};
|
||||
PQXX_CHECK_EQUAL(
|
||||
read[0].as<int>(), 112, "stream_to on container went wrong.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
read[1].as<int>(), 244, "Second container field went wrong.");
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
void test_variant_fold(pqxx::connection_base &connection)
|
||||
{
|
||||
pqxx::work tx{connection};
|
||||
auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
|
||||
PQXX_CHECK(inserter, "stream_to failed to initialize");
|
||||
|
||||
inserter.write_values(
|
||||
std::variant<std::string, int>{1234},
|
||||
std::variant<float, std::string>{"now"}, 4321, ipv4{8, 8, 8, 8},
|
||||
"hello world", bytea{'\x00', '\x01', '\x02'});
|
||||
inserter.write_values(
|
||||
5678, "2018-11-17 21:23:00", nullptr, nullptr,
|
||||
"\u3053\u3093\u306b\u3061\u308f",
|
||||
bytea{'f', 'o', 'o', ' ', 'b', 'a', 'r', '\0'});
|
||||
inserter.write_values(910, nullptr, nullptr, nullptr, "\\N", bytea{});
|
||||
|
||||
inserter.complete();
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
void clear_table(pqxx::connection &conn)
|
||||
{
|
||||
pqxx::work tx{conn};
|
||||
tx.exec0("DELETE FROM stream_to_test");
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
|
||||
void test_stream_to()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
|
||||
tx.exec0(
|
||||
"CREATE TEMP TABLE stream_to_test ("
|
||||
"number0 INT NOT NULL,"
|
||||
"ts1 TIMESTAMP NULL,"
|
||||
"number2 INT NULL,"
|
||||
"addr3 INET NULL,"
|
||||
"txt4 TEXT NULL,"
|
||||
"bin5 BYTEA NOT NULL"
|
||||
")");
|
||||
tx.commit();
|
||||
|
||||
test_nonoptionals(conn);
|
||||
clear_table(conn);
|
||||
test_nonoptionals_fold(conn);
|
||||
clear_table(conn);
|
||||
test_bad_null(conn);
|
||||
clear_table(conn);
|
||||
test_bad_null_fold(conn);
|
||||
clear_table(conn);
|
||||
test_too_few_fields(conn);
|
||||
clear_table(conn);
|
||||
test_too_few_fields_fold(conn);
|
||||
clear_table(conn);
|
||||
test_too_many_fields(conn);
|
||||
clear_table(conn);
|
||||
test_too_many_fields_fold(conn);
|
||||
clear_table(conn);
|
||||
test_optional<std::unique_ptr>(conn);
|
||||
clear_table(conn);
|
||||
test_optional_fold<std::unique_ptr>(conn);
|
||||
clear_table(conn);
|
||||
test_optional<std::optional>(conn);
|
||||
clear_table(conn);
|
||||
test_optional_fold<std::optional>(conn);
|
||||
clear_table(conn);
|
||||
test_variant_fold(conn);
|
||||
}
|
||||
|
||||
|
||||
void test_stream_to_factory_with_static_columns()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
|
||||
tx.exec0("CREATE TEMP TABLE pqxx_stream_to(a integer, b varchar)");
|
||||
|
||||
auto stream{pqxx::stream_to::table(tx, {"pqxx_stream_to"}, {"a", "b"})};
|
||||
stream.write_values(3, "three");
|
||||
stream.complete();
|
||||
|
||||
auto r{tx.exec1("SELECT a, b FROM pqxx_stream_to")};
|
||||
PQXX_CHECK_EQUAL(r[0].as<int>(), 3, "Failed to stream_to a table.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
r[1].as<std::string>(), "three",
|
||||
"Failed to stream_to a string to a table.");
|
||||
}
|
||||
|
||||
|
||||
void test_stream_to_factory_with_dynamic_columns()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
|
||||
tx.exec0("CREATE TEMP TABLE pqxx_stream_to(a integer, b varchar)");
|
||||
|
||||
std::vector<std::string_view> columns{"a", "b"};
|
||||
#if defined(PQXX_HAVE_CONCEPTS)
|
||||
auto stream{pqxx::stream_to::table(tx, {"pqxx_stream_to"}, columns)};
|
||||
#else
|
||||
auto stream{pqxx::stream_to::raw_table(
|
||||
tx, conn.quote_table({"pqxx_stream_to"}), conn.quote_columns(columns))};
|
||||
#endif
|
||||
stream.write_values(4, "four");
|
||||
stream.complete();
|
||||
|
||||
auto r{tx.exec1("SELECT a, b FROM pqxx_stream_to")};
|
||||
PQXX_CHECK_EQUAL(
|
||||
r[0].as<int>(), 4, "Failed to stream_to a table with dynamic columns.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
r[1].as<std::string>(), "four",
|
||||
"Failed to stream_to a string to a table with dynamic columns.");
|
||||
}
|
||||
|
||||
|
||||
void test_stream_to_quotes_arguments()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
|
||||
std::string const table{R"--(pqxx_Stream"'x)--"}, column{R"--(a'"b)--"};
|
||||
|
||||
tx.exec0(
|
||||
"CREATE TEMP TABLE " + tx.quote_name(table) + "(" + tx.quote_name(column) +
|
||||
" integer)");
|
||||
auto write{pqxx::stream_to::table(tx, {table}, {column})};
|
||||
write.write_values<int>(12);
|
||||
write.complete();
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
tx.query_value<int>(
|
||||
"SELECT " + tx.quote_name(column) + " FROM " + tx.quote_name(table)),
|
||||
12, "Stream wrote wrong value.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_stream_to);
|
||||
PQXX_REGISTER_TEST(test_container_stream_to);
|
||||
PQXX_REGISTER_TEST(test_stream_to_does_nonnull_optional);
|
||||
PQXX_REGISTER_TEST(test_stream_to_factory_with_static_columns);
|
||||
PQXX_REGISTER_TEST(test_stream_to_factory_with_dynamic_columns);
|
||||
PQXX_REGISTER_TEST(test_stream_to_quotes_arguments);
|
||||
} // namespace
|
||||
@@ -0,0 +1,178 @@
|
||||
#include <cstdint>
|
||||
#include <variant>
|
||||
|
||||
#include <pqxx/connection>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
// Some enums with string conversions.
|
||||
enum EnumA
|
||||
{
|
||||
ea0,
|
||||
ea1,
|
||||
ea2
|
||||
};
|
||||
enum EnumB
|
||||
{
|
||||
eb0,
|
||||
eb1,
|
||||
eb2
|
||||
};
|
||||
namespace pqxx
|
||||
{
|
||||
PQXX_DECLARE_ENUM_CONVERSION(EnumA);
|
||||
PQXX_DECLARE_ENUM_CONVERSION(EnumB);
|
||||
} // namespace pqxx
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
// "A minimal difference."
|
||||
constexpr double thres{0.00001};
|
||||
|
||||
|
||||
void test_string_conversion()
|
||||
{
|
||||
PQXX_CHECK_EQUAL(
|
||||
"C string array", pqxx::to_string("C string array"),
|
||||
"C-style string constant does not convert to string properly.");
|
||||
|
||||
char text_array[]{"C char array"};
|
||||
PQXX_CHECK_EQUAL(
|
||||
"C char array", pqxx::to_string(text_array),
|
||||
"C-style non-const char array does not convert to string properly.");
|
||||
|
||||
char const *text_ptr{"C string pointer"};
|
||||
PQXX_CHECK_EQUAL(
|
||||
"C string pointer", pqxx::to_string(text_ptr),
|
||||
"C-style string pointer does not convert to string properly.");
|
||||
|
||||
std::string const cxx_string{"C++ string"};
|
||||
PQXX_CHECK_EQUAL(
|
||||
"C++ string", pqxx::to_string(cxx_string),
|
||||
"C++-style string object does not convert to string properly.");
|
||||
|
||||
PQXX_CHECK_EQUAL("0", pqxx::to_string(0), "Zero does not convert right.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
"1", pqxx::to_string(1), "Basic integer does not convert right.");
|
||||
PQXX_CHECK_EQUAL("-1", pqxx::to_string(-1), "Negative numbers don't work.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
"9999", pqxx::to_string(9999), "Larger numbers don't work.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
"-9999", pqxx::to_string(-9999), "Larger negative numbers don't work.");
|
||||
|
||||
int x;
|
||||
pqxx::from_string("0", x);
|
||||
PQXX_CHECK_EQUAL(0, x, "Zero does not parse right.");
|
||||
pqxx::from_string("1", x);
|
||||
PQXX_CHECK_EQUAL(1, x, "Basic integer does not parse right.");
|
||||
pqxx::from_string("-1", x);
|
||||
PQXX_CHECK_EQUAL(-1, x, "Negative numbers don't work.");
|
||||
pqxx::from_string("9999", x);
|
||||
PQXX_CHECK_EQUAL(9999, x, "Larger numbers don't work.");
|
||||
pqxx::from_string("-9999", x);
|
||||
PQXX_CHECK_EQUAL(-9999, x, "Larger negative numbers don't work.");
|
||||
|
||||
// Bug #263 describes a case where this kind of overflow went undetected.
|
||||
if (sizeof(unsigned int) == 4)
|
||||
{
|
||||
std::uint32_t u;
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::from_string("4772185884", u), pqxx::conversion_error,
|
||||
"Overflow not detected.");
|
||||
}
|
||||
|
||||
// We can convert to and from long double. The implementation may fall
|
||||
// back on a thread-local std::stringstream. Each call does its own
|
||||
// cleanup, so the conversion works multiple times.
|
||||
constexpr long double ld1{123456789.25}, ld2{9876543210.5};
|
||||
constexpr char lds1[]{"123456789.25"}, lds2[]{"9876543210.5"};
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(ld1).substr(0, 12), lds1,
|
||||
"Wrong conversion from long double.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(ld2).substr(0, 12), lds2,
|
||||
"Wrong value on repeated conversion from long double.");
|
||||
long double ldi1, ldi2;
|
||||
pqxx::from_string(lds1, ldi1);
|
||||
PQXX_CHECK_BOUNDS(
|
||||
ldi1, ld1 - thres, ld1 + thres, "Wrong conversion to long double.");
|
||||
pqxx::from_string(lds2, ldi2);
|
||||
PQXX_CHECK_BOUNDS(
|
||||
ldi2, ld2 - thres, ld2 + thres,
|
||||
"Wrong repeated conversion to long double.");
|
||||
|
||||
// We can define string conversions for enums.
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(ea0), "0", "Enum-to-string conversion is broken.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(eb0), "0",
|
||||
"Enum-to-string conversion is inconsistent between enum types.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(ea1), "1",
|
||||
"Enum-to-string conversion breaks for nonzero value.");
|
||||
|
||||
EnumA ea;
|
||||
pqxx::from_string("2", ea);
|
||||
PQXX_CHECK_EQUAL(ea, ea2, "String-to-enum conversion is broken.");
|
||||
}
|
||||
|
||||
|
||||
void test_convert_variant_to_string()
|
||||
{
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(std::variant<int, std::string>{99}), "99",
|
||||
"First variant field did not convert right.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(std::variant<int, std::string>{"Text"}), "Text",
|
||||
"Second variant field did not convert right.");
|
||||
}
|
||||
|
||||
|
||||
void test_integer_conversion()
|
||||
{
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::from_string<int>("12"), 12, "Basic integer conversion failed.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::from_string<int>(" 12"), 12,
|
||||
"Leading whitespace confused integer conversion.");
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::ignore_unused(pqxx::from_string<int>("")), pqxx::conversion_error,
|
||||
"Converting empty string to integer did not throw conversion error.");
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::ignore_unused(pqxx::from_string<int>(" ")), pqxx::conversion_error,
|
||||
"Converting whitespace to integer did not throw conversion error.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::from_string<int>("-6"), -6,
|
||||
"Leading whitespace did not work with negative number.");
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::ignore_unused(pqxx::from_string<int>("- 3")), pqxx::conversion_error,
|
||||
"A space between negation and number was not properly flagged.");
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::ignore_unused(pqxx::from_string<int>("-")), pqxx::conversion_error,
|
||||
"Just a minus sign should not parse as an integer.");
|
||||
}
|
||||
|
||||
|
||||
void test_convert_null()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
PQXX_CHECK_EQUAL(
|
||||
tx.quote(nullptr), "NULL", "Null pointer did not come out as SQL 'null'.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
tx.quote(std::nullopt), "NULL",
|
||||
"std::nullopt did not come out as SQL 'null'.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
tx.quote(std::monostate{}), "NULL",
|
||||
"std::monostate did not come out as SQL 'null'.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_string_conversion);
|
||||
PQXX_REGISTER_TEST(test_convert_variant_to_string);
|
||||
PQXX_REGISTER_TEST(test_integer_conversion);
|
||||
PQXX_REGISTER_TEST(test_convert_null);
|
||||
} // namespace
|
||||
@@ -0,0 +1,78 @@
|
||||
#include <pqxx/subtransaction>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
void make_table(pqxx::transaction_base &trans)
|
||||
{
|
||||
trans.exec0("CREATE TEMP TABLE foo (x INTEGER)");
|
||||
}
|
||||
|
||||
|
||||
void insert_row(pqxx::transaction_base &trans)
|
||||
{
|
||||
trans.exec0("INSERT INTO foo(x) VALUES (1)");
|
||||
}
|
||||
|
||||
|
||||
int count_rows(pqxx::transaction_base &trans)
|
||||
{
|
||||
return trans.query_value<int>("SELECT count(*) FROM foo");
|
||||
}
|
||||
|
||||
|
||||
void test_subtransaction_commits_if_commit_called(pqxx::connection &conn)
|
||||
{
|
||||
pqxx::work trans(conn);
|
||||
make_table(trans);
|
||||
{
|
||||
pqxx::subtransaction sub(trans);
|
||||
insert_row(sub);
|
||||
sub.commit();
|
||||
}
|
||||
PQXX_CHECK_EQUAL(
|
||||
count_rows(trans), 1, "Work done in committed subtransaction was lost.");
|
||||
}
|
||||
|
||||
|
||||
void test_subtransaction_aborts_if_abort_called(pqxx::connection &conn)
|
||||
{
|
||||
pqxx::work trans(conn);
|
||||
make_table(trans);
|
||||
{
|
||||
pqxx::subtransaction sub(trans);
|
||||
insert_row(sub);
|
||||
sub.abort();
|
||||
}
|
||||
PQXX_CHECK_EQUAL(
|
||||
count_rows(trans), 0, "Aborted subtransaction was not rolled back.");
|
||||
}
|
||||
|
||||
|
||||
void test_subtransaction_aborts_implicitly(pqxx::connection &conn)
|
||||
{
|
||||
pqxx::work trans(conn);
|
||||
make_table(trans);
|
||||
{
|
||||
pqxx::subtransaction sub(trans);
|
||||
insert_row(sub);
|
||||
}
|
||||
PQXX_CHECK_EQUAL(
|
||||
count_rows(trans), 0,
|
||||
"Uncommitted subtransaction was not rolled back uring destruction.");
|
||||
}
|
||||
|
||||
|
||||
void test_subtransaction()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
test_subtransaction_commits_if_commit_called(conn);
|
||||
test_subtransaction_aborts_if_abort_called(conn);
|
||||
test_subtransaction_aborts_implicitly(conn);
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_subtransaction);
|
||||
} // namespace
|
||||
@@ -0,0 +1,214 @@
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
void empty() {}
|
||||
|
||||
|
||||
void test_check_notreached()
|
||||
{
|
||||
// At a minimum, PQXX_CHECK_NOTREACHED must work.
|
||||
bool failed{true};
|
||||
try
|
||||
{
|
||||
PQXX_CHECK_NOTREACHED("(expected)");
|
||||
failed = false;
|
||||
}
|
||||
catch (pqxx::test::test_failure const &)
|
||||
{
|
||||
// This is what we expect.
|
||||
}
|
||||
if (not failed)
|
||||
throw pqxx::test::test_failure(
|
||||
__FILE__, __LINE__, "PQXX_CHECK_NOTREACHED is broken.");
|
||||
}
|
||||
|
||||
|
||||
// Test PQXX_CHECK.
|
||||
void test_check()
|
||||
{
|
||||
PQXX_CHECK(true, "PQXX_CHECK is broken.");
|
||||
|
||||
bool failed{true};
|
||||
try
|
||||
{
|
||||
PQXX_CHECK(false, "(expected)");
|
||||
failed = false;
|
||||
}
|
||||
catch (pqxx::test::test_failure const &)
|
||||
{}
|
||||
if (not failed)
|
||||
PQXX_CHECK_NOTREACHED("PQXX_CHECK failed to notice failure.");
|
||||
}
|
||||
|
||||
|
||||
// Test PQXX_CHECK_THROWS_EXCEPTION.
|
||||
void test_check_throws_exception()
|
||||
{
|
||||
// PQXX_CHECK_THROWS_EXCEPTION expects std::exception...
|
||||
PQXX_CHECK_THROWS_EXCEPTION(
|
||||
throw std::exception(),
|
||||
"PQXX_CHECK_THROWS_EXCEPTION did not catch std::exception.");
|
||||
|
||||
// ...or any exception type derived from it.
|
||||
PQXX_CHECK_THROWS_EXCEPTION(
|
||||
throw pqxx::test::test_failure(__FILE__, __LINE__, "(expected)"),
|
||||
"PQXX_CHECK_THROWS_EXCEPTION() failed to catch expected exception.");
|
||||
|
||||
// Any other type is an error.
|
||||
bool failed{true};
|
||||
try
|
||||
{
|
||||
PQXX_CHECK_THROWS_EXCEPTION(throw 1, "(expected)");
|
||||
failed = false;
|
||||
}
|
||||
catch (pqxx::test::test_failure const &)
|
||||
{}
|
||||
PQXX_CHECK(
|
||||
failed,
|
||||
"PQXX_CHECK_THROWS_EXCEPTION did not complain about non-exception.");
|
||||
|
||||
// But there _must_ be an exception.
|
||||
failed = true;
|
||||
try
|
||||
{
|
||||
// If the test fails to throw, this throws a failure.
|
||||
PQXX_CHECK_THROWS_EXCEPTION(empty(), "(expected)");
|
||||
// So we shouldn't get to this point.
|
||||
failed = false;
|
||||
}
|
||||
catch (pqxx::test::test_failure const &)
|
||||
{
|
||||
// Instead, we go straight here.
|
||||
}
|
||||
PQXX_CHECK(
|
||||
failed, "PQXX_CHECK_THROWS_EXCEPTION did not notice missing exception.");
|
||||
|
||||
// PQXX_CHECK_THROWS_EXCEPTION can test itself...
|
||||
PQXX_CHECK_THROWS_EXCEPTION(
|
||||
PQXX_CHECK_THROWS_EXCEPTION(empty(), "(expected)"),
|
||||
"PQXX_CHECK_THROWS_EXCEPTION failed to throw for missing exception.");
|
||||
|
||||
PQXX_CHECK_THROWS_EXCEPTION(
|
||||
PQXX_CHECK_THROWS_EXCEPTION(throw 1, "(expected)"),
|
||||
"PQXX_CHECK_THROWS_EXCEPTION ignored wrong exception type.");
|
||||
}
|
||||
|
||||
|
||||
// Test PQXX_CHECK_THROWS.
|
||||
void test_check_throws()
|
||||
{
|
||||
PQXX_CHECK_THROWS(
|
||||
throw pqxx::test::test_failure(__FILE__, __LINE__, "(expected)"),
|
||||
pqxx::test::test_failure,
|
||||
"PQXX_CHECK_THROWS() failed to catch expected exception.");
|
||||
|
||||
// Even if it's not std::exception-derived.
|
||||
PQXX_CHECK_THROWS(throw 1, int, "(expected)");
|
||||
|
||||
// PQXX_CHECK_THROWS means there _must_ be an exception.
|
||||
bool failed{true};
|
||||
try
|
||||
{
|
||||
// If the test fails to throw, PQXX_CHECK_THROWS throws a failure.
|
||||
PQXX_CHECK_THROWS(empty(), std::runtime_error, "(expected)");
|
||||
// So we shouldn't get to this point.
|
||||
failed = false;
|
||||
}
|
||||
catch (pqxx::test::test_failure const &)
|
||||
{
|
||||
// Instead, we go straight here.
|
||||
}
|
||||
PQXX_CHECK(failed, "PQXX_CHECK_THROWS did not notice missing exception.");
|
||||
|
||||
// The exception must be of the right type (or a subclass of the right type).
|
||||
failed = true;
|
||||
try
|
||||
{
|
||||
// If the test throws the wrong type, PQXX_CHECK_THROWS throws a failure.
|
||||
PQXX_CHECK_THROWS(
|
||||
throw std::exception(), pqxx::test::test_failure, "(expected)");
|
||||
failed = false;
|
||||
}
|
||||
catch (pqxx::test::test_failure const &)
|
||||
{
|
||||
// Instead, we go straight here.
|
||||
}
|
||||
PQXX_CHECK(failed, "PQXX_CHECK_THROWS did not notice wrong exception type.");
|
||||
|
||||
// PQXX_CHECK_THROWS can test itself...
|
||||
PQXX_CHECK_THROWS(
|
||||
PQXX_CHECK_THROWS(empty(), pqxx::test::test_failure, "(expected)"),
|
||||
pqxx::test::test_failure,
|
||||
"PQXX_CHECK_THROWS failed to throw for missing exception.");
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
PQXX_CHECK_THROWS(throw 1, std::runtime_error, "(expected)"),
|
||||
pqxx::test::test_failure,
|
||||
"PQXX_CHECK_THROWS failed to throw for wrong exception type.");
|
||||
}
|
||||
|
||||
|
||||
void test_test_helpers()
|
||||
{
|
||||
test_check_notreached();
|
||||
test_check();
|
||||
test_check_throws_exception();
|
||||
test_check_throws();
|
||||
|
||||
// Test other helpers against PQXX_CHECK_THROWS.
|
||||
PQXX_CHECK_THROWS(
|
||||
PQXX_CHECK_NOTREACHED("(expected)"), pqxx::test::test_failure,
|
||||
"PQXX_CHECK_THROWS did not catch PQXX_CHECK_NOTREACHED.");
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
PQXX_CHECK(false, "(expected)"), pqxx::test::test_failure,
|
||||
"PQXX_CHECK_THROWS did not catch failing PQXX_CHECK.");
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
PQXX_CHECK_THROWS(
|
||||
PQXX_CHECK(true, "(shouldn't happen)"), pqxx::test::test_failure,
|
||||
"(expected)"),
|
||||
pqxx::test::test_failure,
|
||||
"PQXX_CHECK_THROWS on successful PQXX_CHECK failed to throw.");
|
||||
|
||||
// PQXX_CHECK_EQUAL tests for equality. Its arguments need not be of the
|
||||
// same type, as long as equality between them is defined.
|
||||
PQXX_CHECK_EQUAL(1, 1, "PQXX_CHECK_EQUAL is broken.");
|
||||
PQXX_CHECK_EQUAL(1, 1L, "PQXX_CHECK_EQUAL breaks on type mismatch.");
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
PQXX_CHECK_EQUAL(1, 2, "(expected)"), pqxx::test::test_failure,
|
||||
"PQXX_CHECK_EQUAL fails to spot inequality.");
|
||||
|
||||
// PQXX_CHECK_NOT_EQUAL is like PQXX_CHECK_EQUAL, but tests for inequality.
|
||||
PQXX_CHECK_NOT_EQUAL(1, 2, "PQXX_CHECK_NOT_EQUAL is broken.");
|
||||
PQXX_CHECK_THROWS(
|
||||
PQXX_CHECK_NOT_EQUAL(1, 1, "(expected)"), pqxx::test::test_failure,
|
||||
"PQXX_CHECK_NOT_EQUAL fails to fail when arguments are equal.");
|
||||
PQXX_CHECK_THROWS(
|
||||
PQXX_CHECK_NOT_EQUAL(1, 1L, "(expected)"), pqxx::test::test_failure,
|
||||
"PQXX_CHECK_NOT_EQUAL breaks on type mismatch.");
|
||||
|
||||
// PQXX_CHECK_BOUNDS checks a value against a range.
|
||||
PQXX_CHECK_BOUNDS(2, 1, 3, "PQXX_CHECK_BOUNDS wrongly finds fault.");
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
PQXX_CHECK_BOUNDS(1, 2, 3, "(Expected)"), pqxx::test::test_failure,
|
||||
"PQXX_CHECK_BOUNDS did not detect value below permitted range.");
|
||||
|
||||
// PQXX_CHECK_BOUNDS tests against a half-open interval.
|
||||
PQXX_CHECK_BOUNDS(1, 1, 3, "PQXX_CHECK_BOUNDS goes wrong on lower bound.");
|
||||
PQXX_CHECK_THROWS(
|
||||
PQXX_CHECK_BOUNDS(3, 1, 3, "(Expected)"), pqxx::test::test_failure,
|
||||
"PQXX_CHECK_BOUNDS interval is not half-open.");
|
||||
|
||||
// PQXX_CHECK_BOUNDS deals well with empty intervals.
|
||||
PQXX_CHECK_THROWS(
|
||||
PQXX_CHECK_BOUNDS(1, 2, 1, "(Expected)"), pqxx::test::test_failure,
|
||||
"PQXX_CHECK_BOUNDS did not detect empty interval.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_test_helpers);
|
||||
} // namespace
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
#include <pqxx/util>
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_thread_safety_model()
|
||||
{
|
||||
auto const model{pqxx::describe_thread_safety()};
|
||||
|
||||
if (model.safe_libpq and model.safe_kerberos)
|
||||
PQXX_CHECK_EQUAL(
|
||||
model.description, "",
|
||||
"Thread-safety looks okay but model description is nonempty.");
|
||||
else
|
||||
PQXX_CHECK_NOT_EQUAL(
|
||||
model.description, "",
|
||||
"Thread-safety model is imperfect but lacks description.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_thread_safety_model);
|
||||
} // namespace
|
||||
@@ -0,0 +1,86 @@
|
||||
#include <pqxx/time>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
#if defined(PQXX_HAVE_YEAR_MONTH_DAY)
|
||||
using namespace std::literals;
|
||||
|
||||
|
||||
void test_date_string_conversion()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
std::tuple<int, unsigned, unsigned, std::string_view> const conversions[]{
|
||||
{-542, 1, 1, "0543-01-01 BC"sv},
|
||||
{-1, 2, 3, "0002-02-03 BC"sv},
|
||||
{0, 9, 14, "0001-09-14 BC"sv},
|
||||
{1, 12, 8, "0001-12-08"sv},
|
||||
{2021, 10, 24, "2021-10-24"sv},
|
||||
{10191, 8, 30, "10191-08-30"sv},
|
||||
{-4712, 1, 1, "4713-01-01 BC"sv},
|
||||
{32767, 12, 31, "32767-12-31"sv},
|
||||
{2000, 2, 29, "2000-02-29"sv},
|
||||
{2004, 2, 29, "2004-02-29"sv},
|
||||
// This one won't work in postgres, but we can test the conversions.
|
||||
{-32767, 11, 3, "32768-11-03 BC"sv},
|
||||
};
|
||||
for (auto const &[y, m, d, text] : conversions)
|
||||
{
|
||||
std::chrono::year_month_day const date{
|
||||
std::chrono::year{y}, std::chrono::month{m}, std::chrono::day{d}};
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::to_string(date), text, "Date did not convert right.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
pqxx::from_string<std::chrono::year_month_day>(text), date,
|
||||
"Date did not parse right.");
|
||||
if (int{date.year()} > -4712)
|
||||
{
|
||||
// We can't test this for years before 4713 BC (4712 BCE), because
|
||||
// postgres doesn't handle earlier years.
|
||||
PQXX_CHECK_EQUAL(
|
||||
tx.query_value<std::string>(
|
||||
"SELECT '" + pqxx::to_string(date) + "'::date"),
|
||||
text, "Backend interpreted date differently.");
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view const invalid[]{
|
||||
""sv,
|
||||
"yesterday"sv,
|
||||
"1981-01"sv,
|
||||
"2010"sv,
|
||||
"2010-8-9"sv,
|
||||
"1900-02-29"sv,
|
||||
"2021-02-29"sv,
|
||||
"2000-11-29-3"sv,
|
||||
"1900-02-29"sv,
|
||||
"2003-02-29"sv,
|
||||
"12-12-12"sv,
|
||||
"0000-09-16"sv,
|
||||
"-01-01"sv,
|
||||
"-1000-01-01"sv,
|
||||
"1000-00-01"sv,
|
||||
"1000-01-00"sv,
|
||||
"2001y-01-01"sv,
|
||||
"10-09-08"sv,
|
||||
"0-01-01"sv,
|
||||
"0000-01-01"sv,
|
||||
"2021-13-01"sv,
|
||||
"2021-+02-01"sv,
|
||||
"2021-12-32"sv,
|
||||
};
|
||||
for (auto const text : invalid)
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::ignore_unused(
|
||||
pqxx::from_string<std::chrono::year_month_day>(text)),
|
||||
pqxx::conversion_error,
|
||||
pqxx::internal::concat("Invalid date '", text, "' parsed as if valid."));
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_date_string_conversion);
|
||||
#endif // PQXX_HAVE_YEAR_MONTH_DAY
|
||||
} // namespace
|
||||
@@ -0,0 +1,113 @@
|
||||
#include <pqxx/nontransaction>
|
||||
#include <pqxx/robusttransaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_nontransaction_continues_after_error()
|
||||
{
|
||||
pqxx::connection c;
|
||||
pqxx::nontransaction tx{c};
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
tx.query_value<int>("SELECT 9"), 9, "Simple query went wrong.");
|
||||
PQXX_CHECK_THROWS(
|
||||
tx.exec("SELECT 1/0"), pqxx::sql_error, "Expected error did not happen.");
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
tx.query_value<int>("SELECT 5"), 5, "Wrong result after error.");
|
||||
}
|
||||
|
||||
|
||||
std::string const table{"pqxx_test_transaction"};
|
||||
|
||||
|
||||
void delete_temp_table(pqxx::transaction_base &tx)
|
||||
{
|
||||
tx.exec0(std::string{"DROP TABLE IF EXISTS "} + table);
|
||||
}
|
||||
|
||||
|
||||
void create_temp_table(pqxx::transaction_base &tx)
|
||||
{
|
||||
tx.exec0("CREATE TEMP TABLE " + table + " (x integer)");
|
||||
}
|
||||
|
||||
|
||||
void insert_temp_table(pqxx::transaction_base &tx, int value)
|
||||
{
|
||||
tx.exec0(
|
||||
"INSERT INTO " + table + " (x) VALUES (" + pqxx::to_string(value) + ")");
|
||||
}
|
||||
|
||||
int count_temp_table(pqxx::transaction_base &tx)
|
||||
{
|
||||
return tx.query_value<int>("SELECT count(*) FROM " + table);
|
||||
}
|
||||
|
||||
|
||||
void test_nontransaction_autocommits()
|
||||
{
|
||||
pqxx::connection c;
|
||||
|
||||
pqxx::nontransaction tx1{c};
|
||||
delete_temp_table(tx1);
|
||||
create_temp_table(tx1);
|
||||
tx1.commit();
|
||||
|
||||
pqxx::nontransaction tx2{c};
|
||||
insert_temp_table(tx2, 4);
|
||||
tx2.abort();
|
||||
|
||||
pqxx::nontransaction tx3{c};
|
||||
PQXX_CHECK_EQUAL(
|
||||
count_temp_table(tx3), 1,
|
||||
"Did not keep effect of aborted nontransaction.");
|
||||
delete_temp_table(tx3);
|
||||
}
|
||||
|
||||
|
||||
template<typename TX> void test_double_close()
|
||||
{
|
||||
pqxx::connection c;
|
||||
|
||||
TX tx1{c};
|
||||
tx1.exec1("SELECT 1");
|
||||
tx1.commit();
|
||||
tx1.commit();
|
||||
|
||||
TX tx2{c};
|
||||
tx2.exec1("SELECT 2");
|
||||
tx2.abort();
|
||||
tx2.abort();
|
||||
|
||||
TX tx3{c};
|
||||
tx3.exec1("SELECT 3");
|
||||
tx3.commit();
|
||||
PQXX_CHECK_THROWS(
|
||||
tx3.abort(), pqxx::usage_error, "Abort after commit not caught.");
|
||||
;
|
||||
|
||||
TX tx4{c};
|
||||
tx4.exec1("SELECT 4");
|
||||
tx4.abort();
|
||||
PQXX_CHECK_THROWS(
|
||||
tx4.commit(), pqxx::usage_error, "Commit after abort not caught.");
|
||||
}
|
||||
|
||||
|
||||
void test_transaction()
|
||||
{
|
||||
test_nontransaction_continues_after_error();
|
||||
test_nontransaction_autocommits();
|
||||
test_double_close<pqxx::transaction<>>();
|
||||
test_double_close<pqxx::read_transaction>();
|
||||
test_double_close<pqxx::nontransaction>();
|
||||
test_double_close<pqxx::robusttransaction<>>();
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_transaction);
|
||||
} // namespace
|
||||
@@ -0,0 +1,106 @@
|
||||
#include <pqxx/stream_to>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_exec0(pqxx::transaction_base &trans)
|
||||
{
|
||||
pqxx::result E{trans.exec0("SELECT * FROM pg_tables WHERE 0 = 1")};
|
||||
PQXX_CHECK(std::empty(E), "Nonempty result from exec0.");
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
trans.exec0("SELECT 99"), pqxx::unexpected_rows,
|
||||
"Nonempty exec0 result did not throw unexpected_rows.");
|
||||
}
|
||||
|
||||
|
||||
void test_exec1(pqxx::transaction_base &trans)
|
||||
{
|
||||
pqxx::row R{trans.exec1("SELECT 99")};
|
||||
PQXX_CHECK_EQUAL(std::size(R), 1, "Wrong size result from exec1.");
|
||||
PQXX_CHECK_EQUAL(R.front().as<int>(), 99, "Wrong result from exec1.");
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
trans.exec1("SELECT * FROM pg_tables WHERE 0 = 1"), pqxx::unexpected_rows,
|
||||
"Empty exec1 result did not throw unexpected_rows.");
|
||||
PQXX_CHECK_THROWS(
|
||||
trans.exec1("SELECT * FROM generate_series(1, 2)"), pqxx::unexpected_rows,
|
||||
"Two-row exec1 result did not throw unexpected_rows.");
|
||||
}
|
||||
|
||||
|
||||
void test_exec_n(pqxx::transaction_base &trans)
|
||||
{
|
||||
pqxx::result R{trans.exec_n(3, "SELECT * FROM generate_series(1, 3)")};
|
||||
PQXX_CHECK_EQUAL(std::size(R), 3, "Wrong result size from exec_n.");
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
trans.exec_n(2, "SELECT * FROM generate_series(1, 3)"),
|
||||
pqxx::unexpected_rows,
|
||||
"exec_n did not throw unexpected_rows for an undersized result.");
|
||||
PQXX_CHECK_THROWS(
|
||||
trans.exec_n(4, "SELECT * FROM generate_series(1, 3)"),
|
||||
pqxx::unexpected_rows,
|
||||
"exec_n did not throw unexpected_rows for an oversized result.");
|
||||
}
|
||||
|
||||
|
||||
void test_query_value(pqxx::connection &conn)
|
||||
{
|
||||
pqxx::work tx{conn};
|
||||
|
||||
PQXX_CHECK_EQUAL(
|
||||
tx.query_value<int>("SELECT 84 / 2"), 42,
|
||||
"Got wrong value from query_value.");
|
||||
PQXX_CHECK_THROWS(
|
||||
tx.query_value<int>("SAVEPOINT dummy"), pqxx::unexpected_rows,
|
||||
"Got field when none expected.");
|
||||
PQXX_CHECK_THROWS(
|
||||
tx.query_value<int>("SELECT generate_series(1, 2)"), pqxx::unexpected_rows,
|
||||
"Failed to fail for multiple rows.");
|
||||
PQXX_CHECK_THROWS(
|
||||
tx.query_value<int>("SELECT 1, 2"), pqxx::usage_error,
|
||||
"No error for too many fields.");
|
||||
PQXX_CHECK_THROWS(
|
||||
tx.query_value<int>("SELECT 3.141"), pqxx::conversion_error,
|
||||
"Got int field from float string.");
|
||||
}
|
||||
|
||||
|
||||
void test_transaction_base()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
{
|
||||
pqxx::work tx{conn};
|
||||
test_exec_n(tx);
|
||||
test_exec0(tx);
|
||||
test_exec1(tx);
|
||||
}
|
||||
test_query_value(conn);
|
||||
}
|
||||
|
||||
|
||||
void test_transaction_for_each()
|
||||
{
|
||||
constexpr auto query{
|
||||
"SELECT i, concat('x', (2*i)::text) "
|
||||
"FROM generate_series(1, 3) AS i "
|
||||
"ORDER BY i"};
|
||||
pqxx::connection conn;
|
||||
pqxx::work tx{conn};
|
||||
std::string ints;
|
||||
std::string strings;
|
||||
tx.for_each(query, [&ints, &strings](int i, std::string const &s) {
|
||||
ints += pqxx::to_string(i) + " ";
|
||||
strings += s + " ";
|
||||
});
|
||||
PQXX_CHECK_EQUAL(ints, "1 2 3 ", "Unexpected int sequence.");
|
||||
PQXX_CHECK_EQUAL(strings, "x2 x4 x6 ", "Unexpected string sequence.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_transaction_base);
|
||||
PQXX_REGISTER_TEST(test_transaction_for_each);
|
||||
} // namespace
|
||||
@@ -0,0 +1,53 @@
|
||||
#include <pqxx/stream_from>
|
||||
#include <pqxx/transaction>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
auto make_focus(pqxx::dbtransaction &tx)
|
||||
{
|
||||
return pqxx::stream_from::query(tx, "SELECT * from generate_series(1, 10)");
|
||||
}
|
||||
|
||||
|
||||
void test_cannot_run_statement_during_focus()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::transaction tx{conn};
|
||||
tx.exec("SELECT 1");
|
||||
auto focus{make_focus(tx)};
|
||||
PQXX_CHECK_THROWS(
|
||||
tx.exec("SELECT 1"), pqxx::usage_error,
|
||||
"Command during focus did not throw expected error.");
|
||||
}
|
||||
|
||||
|
||||
void test_cannot_run_prepared_statement_during_focus()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
conn.prepare("foo", "SELECT 1");
|
||||
pqxx::transaction tx{conn};
|
||||
tx.exec_prepared("foo");
|
||||
auto focus{make_focus(tx)};
|
||||
PQXX_CHECK_THROWS(
|
||||
tx.exec_prepared("foo"), pqxx::usage_error,
|
||||
"Prepared statement during focus did not throw expected error.");
|
||||
}
|
||||
|
||||
void test_cannot_run_params_statement_during_focus()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
pqxx::transaction tx{conn};
|
||||
tx.exec_params("select $1", 10);
|
||||
auto focus{make_focus(tx)};
|
||||
PQXX_CHECK_THROWS(
|
||||
tx.exec_params("select $1", 10), pqxx::usage_error,
|
||||
"Parameterized statement during focus did not throw expected error.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_cannot_run_statement_during_focus);
|
||||
PQXX_REGISTER_TEST(test_cannot_run_prepared_statement_during_focus);
|
||||
PQXX_REGISTER_TEST(test_cannot_run_params_statement_during_focus);
|
||||
} // namespace
|
||||
@@ -0,0 +1,130 @@
|
||||
#include <pqxx/transaction>
|
||||
#include <pqxx/transactor>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_transactor_newstyle_executes_simple_query()
|
||||
{
|
||||
pqxx::connection conn;
|
||||
auto const r{pqxx::perform([&conn] {
|
||||
return pqxx::work{conn}.exec("SELECT generate_series(1, 4)");
|
||||
})};
|
||||
|
||||
PQXX_CHECK_EQUAL(std::size(r), 4, "Unexpected result size.");
|
||||
PQXX_CHECK_EQUAL(r.columns(), 1, "Unexpected number of columns.");
|
||||
PQXX_CHECK_EQUAL(r[0][0].as<int>(), 1, "Unexpected first row.");
|
||||
PQXX_CHECK_EQUAL(r[3][0].as<int>(), 4, "Unexpected last row.");
|
||||
}
|
||||
|
||||
|
||||
void test_transactor_newstyle_can_return_void()
|
||||
{
|
||||
bool done{false};
|
||||
pqxx::perform([&done]() noexcept { done = true; });
|
||||
PQXX_CHECK(done, "Callback was not executed.");
|
||||
}
|
||||
|
||||
|
||||
void test_transactor_newstyle_completes_upon_success()
|
||||
{
|
||||
int attempts{0};
|
||||
pqxx::perform([&attempts]() noexcept { attempts++; });
|
||||
PQXX_CHECK_EQUAL(attempts, 1, "Successful transactor didn't run 1 time.");
|
||||
}
|
||||
|
||||
|
||||
void test_transactor_newstyle_retries_broken_connection()
|
||||
{
|
||||
int counter{0};
|
||||
auto const &callback{[&counter] {
|
||||
++counter;
|
||||
if (counter == 1)
|
||||
throw pqxx::broken_connection();
|
||||
return counter;
|
||||
}};
|
||||
|
||||
int const result{pqxx::perform(callback)};
|
||||
PQXX_CHECK_EQUAL(result, 2, "Transactor run returned wrong result.");
|
||||
PQXX_CHECK_EQUAL(counter, result, "Number of retries does not match.");
|
||||
}
|
||||
|
||||
|
||||
void test_transactor_newstyle_retries_rollback()
|
||||
{
|
||||
int counter{0};
|
||||
auto const &callback{[&counter] {
|
||||
++counter;
|
||||
if (counter == 1)
|
||||
throw pqxx::transaction_rollback("Simulated error");
|
||||
return counter;
|
||||
}};
|
||||
|
||||
int const result{pqxx::perform(callback)};
|
||||
PQXX_CHECK_EQUAL(result, 2, "Transactor run returned wrong result.");
|
||||
PQXX_CHECK_EQUAL(counter, result, "Number of retries does not match.");
|
||||
}
|
||||
|
||||
|
||||
void test_transactor_newstyle_does_not_retry_in_doubt_error()
|
||||
{
|
||||
int counter{0};
|
||||
auto const &callback{[&counter] {
|
||||
++counter;
|
||||
throw pqxx::in_doubt_error("Simulated error");
|
||||
}};
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::perform(callback), pqxx::in_doubt_error,
|
||||
"Transactor did not propagate in_doubt_error.");
|
||||
PQXX_CHECK_EQUAL(counter, 1, "Transactor retried after in_doubt_error.");
|
||||
}
|
||||
|
||||
|
||||
void test_transactor_newstyle_does_not_retry_other_error()
|
||||
{
|
||||
int counter{0};
|
||||
auto const &callback{[&counter] {
|
||||
++counter;
|
||||
throw std::runtime_error("Simulated error");
|
||||
}};
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::perform(callback), std::runtime_error,
|
||||
"Transactor did not propagate std exception.");
|
||||
PQXX_CHECK_EQUAL(counter, 1, "Transactor retried after std exception.");
|
||||
}
|
||||
|
||||
|
||||
void test_transactor_newstyle_repeats_up_to_given_number_of_attempts()
|
||||
{
|
||||
int const attempts{5};
|
||||
int counter{0};
|
||||
auto const &callback{[&counter] {
|
||||
++counter;
|
||||
throw pqxx::transaction_rollback("Simulated error");
|
||||
}};
|
||||
|
||||
PQXX_CHECK_THROWS(
|
||||
pqxx::perform(callback, attempts), pqxx::transaction_rollback,
|
||||
"Not propagating original exception.");
|
||||
PQXX_CHECK_EQUAL(counter, attempts, "Number of retries does not match.");
|
||||
}
|
||||
|
||||
|
||||
void test_transactor()
|
||||
{
|
||||
test_transactor_newstyle_executes_simple_query();
|
||||
test_transactor_newstyle_can_return_void();
|
||||
test_transactor_newstyle_completes_upon_success();
|
||||
test_transactor_newstyle_retries_broken_connection();
|
||||
test_transactor_newstyle_retries_rollback();
|
||||
test_transactor_newstyle_does_not_retry_in_doubt_error();
|
||||
test_transactor_newstyle_does_not_retry_other_error();
|
||||
test_transactor_newstyle_repeats_up_to_given_number_of_attempts();
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_transactor);
|
||||
} // namespace
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_type_name()
|
||||
{
|
||||
// It's hard to test in more detail, because spellings may differ.
|
||||
// For instance, one compiler might call "const unsigned int*" what another
|
||||
// might call "unsigned const *". And Visual Studio prefixes "class" to
|
||||
// class types.
|
||||
std::string const i{pqxx::type_name<int>};
|
||||
PQXX_CHECK_LESS(std::size(i), 5u, "type_name<int> is suspiciously long.");
|
||||
PQXX_CHECK_EQUAL(
|
||||
i.substr(0, 1), "i", "type_name<int> does not start with 'i'.");
|
||||
}
|
||||
|
||||
|
||||
PQXX_REGISTER_TEST(test_type_name);
|
||||
} // namespace
|
||||
@@ -0,0 +1,16 @@
|
||||
#include <pqxx/zview>
|
||||
|
||||
#include "../test_helpers.hxx"
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
void test_zview_literal()
|
||||
{
|
||||
using pqxx::operator"" _zv;
|
||||
|
||||
PQXX_CHECK_EQUAL(("foo"_zv), pqxx::zview{"foo"}, "zview literal is broken.");
|
||||
}
|
||||
|
||||
PQXX_REGISTER_TEST(test_zview_literal);
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user