First Commit
This commit is contained in:
25
ext/libpqxx-7.7.3/test/unit/CMakeLists.txt
Normal file
25
ext/libpqxx-7.7.3/test/unit/CMakeLists.txt
Normal file
@@ -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
|
||||
)
|
||||
548
ext/libpqxx-7.7.3/test/unit/test_array.cxx
Normal file
548
ext/libpqxx-7.7.3/test/unit/test_array.cxx
Normal file
@@ -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
|
||||
211
ext/libpqxx-7.7.3/test/unit/test_binarystring.cxx
Normal file
211
ext/libpqxx-7.7.3/test/unit/test_binarystring.cxx
Normal file
@@ -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
|
||||
644
ext/libpqxx-7.7.3/test/unit/test_blob.cxx
Normal file
644
ext/libpqxx-7.7.3/test/unit/test_blob.cxx
Normal file
@@ -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
|
||||
25
ext/libpqxx-7.7.3/test/unit/test_cancel_query.cxx
Normal file
25
ext/libpqxx-7.7.3/test/unit/test_cancel_query.cxx
Normal file
@@ -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
|
||||
61
ext/libpqxx-7.7.3/test/unit/test_column.cxx
Normal file
61
ext/libpqxx-7.7.3/test/unit/test_column.cxx
Normal file
@@ -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);
|
||||
98
ext/libpqxx-7.7.3/test/unit/test_composite.cxx
Normal file
98
ext/libpqxx-7.7.3/test/unit/test_composite.cxx
Normal file
@@ -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
|
||||
212
ext/libpqxx-7.7.3/test/unit/test_connection.cxx
Normal file
212
ext/libpqxx-7.7.3/test/unit/test_connection.cxx
Normal file
@@ -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
|
||||
50
ext/libpqxx-7.7.3/test/unit/test_cursor.cxx
Normal file
50
ext/libpqxx-7.7.3/test/unit/test_cursor.cxx
Normal file
@@ -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
|
||||
114
ext/libpqxx-7.7.3/test/unit/test_encodings.cxx
Normal file
114
ext/libpqxx-7.7.3/test/unit/test_encodings.cxx
Normal file
@@ -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
|
||||
37
ext/libpqxx-7.7.3/test/unit/test_error_verbosity.cxx
Normal file
37
ext/libpqxx-7.7.3/test/unit/test_error_verbosity.cxx
Normal file
@@ -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
|
||||
223
ext/libpqxx-7.7.3/test/unit/test_errorhandler.cxx
Normal file
223
ext/libpqxx-7.7.3/test/unit/test_errorhandler.cxx
Normal file
@@ -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
|
||||
228
ext/libpqxx-7.7.3/test/unit/test_escape.cxx
Normal file
228
ext/libpqxx-7.7.3/test/unit/test_escape.cxx
Normal file
@@ -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
|
||||
45
ext/libpqxx-7.7.3/test/unit/test_exceptions.cxx
Normal file
45
ext/libpqxx-7.7.3/test/unit/test_exceptions.cxx
Normal file
@@ -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
|
||||
57
ext/libpqxx-7.7.3/test/unit/test_field.cxx
Normal file
57
ext/libpqxx-7.7.3/test/unit/test_field.cxx
Normal file
@@ -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
|
||||
175
ext/libpqxx-7.7.3/test/unit/test_float.cxx
Normal file
175
ext/libpqxx-7.7.3/test/unit/test_float.cxx
Normal file
@@ -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
|
||||
58
ext/libpqxx-7.7.3/test/unit/test_largeobject.cxx
Normal file
58
ext/libpqxx-7.7.3/test/unit/test_largeobject.cxx
Normal file
@@ -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
|
||||
27
ext/libpqxx-7.7.3/test/unit/test_nonblocking_connect.cxx
Normal file
27
ext/libpqxx-7.7.3/test/unit/test_nonblocking_connect.cxx
Normal file
@@ -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
|
||||
86
ext/libpqxx-7.7.3/test/unit/test_notification.cxx
Normal file
86
ext/libpqxx-7.7.3/test/unit/test_notification.cxx
Normal file
@@ -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
|
||||
64
ext/libpqxx-7.7.3/test/unit/test_pipeline.cxx
Normal file
64
ext/libpqxx-7.7.3/test/unit/test_pipeline.cxx
Normal file
@@ -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);
|
||||
334
ext/libpqxx-7.7.3/test/unit/test_prepared_statement.cxx
Normal file
334
ext/libpqxx-7.7.3/test/unit/test_prepared_statement.cxx
Normal file
@@ -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
|
||||
555
ext/libpqxx-7.7.3/test/unit/test_range.cxx
Normal file
555
ext/libpqxx-7.7.3/test/unit/test_range.cxx
Normal file
@@ -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
|
||||
22
ext/libpqxx-7.7.3/test/unit/test_read_transaction.cxx
Normal file
22
ext/libpqxx-7.7.3/test/unit/test_read_transaction.cxx
Normal file
@@ -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
|
||||
137
ext/libpqxx-7.7.3/test/unit/test_result_iteration.cxx
Normal file
137
ext/libpqxx-7.7.3/test/unit/test_result_iteration.cxx
Normal file
@@ -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
|
||||
157
ext/libpqxx-7.7.3/test/unit/test_result_slicing.cxx
Normal file
157
ext/libpqxx-7.7.3/test/unit/test_result_slicing.cxx
Normal file
@@ -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"
|
||||
84
ext/libpqxx-7.7.3/test/unit/test_row.cxx
Normal file
84
ext/libpqxx-7.7.3/test/unit/test_row.cxx
Normal file
@@ -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
|
||||
33
ext/libpqxx-7.7.3/test/unit/test_separated_list.cxx
Normal file
33
ext/libpqxx-7.7.3/test/unit/test_separated_list.cxx
Normal file
@@ -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
|
||||
270
ext/libpqxx-7.7.3/test/unit/test_sql_cursor.cxx
Normal file
270
ext/libpqxx-7.7.3/test/unit/test_sql_cursor.cxx
Normal file
@@ -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
|
||||
85
ext/libpqxx-7.7.3/test/unit/test_stateless_cursor.cxx
Normal file
85
ext/libpqxx-7.7.3/test/unit/test_stateless_cursor.cxx
Normal file
@@ -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
|
||||
143
ext/libpqxx-7.7.3/test/unit/test_strconv.cxx
Normal file
143
ext/libpqxx-7.7.3/test/unit/test_strconv.cxx
Normal file
@@ -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
|
||||
344
ext/libpqxx-7.7.3/test/unit/test_stream_from.cxx
Normal file
344
ext/libpqxx-7.7.3/test/unit/test_stream_from.cxx
Normal file
@@ -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
|
||||
445
ext/libpqxx-7.7.3/test/unit/test_stream_to.cxx
Normal file
445
ext/libpqxx-7.7.3/test/unit/test_stream_to.cxx
Normal file
@@ -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
|
||||
178
ext/libpqxx-7.7.3/test/unit/test_string_conversion.cxx
Normal file
178
ext/libpqxx-7.7.3/test/unit/test_string_conversion.cxx
Normal file
@@ -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
|
||||
78
ext/libpqxx-7.7.3/test/unit/test_subtransaction.cxx
Normal file
78
ext/libpqxx-7.7.3/test/unit/test_subtransaction.cxx
Normal file
@@ -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
|
||||
214
ext/libpqxx-7.7.3/test/unit/test_test_helpers.cxx
Normal file
214
ext/libpqxx-7.7.3/test/unit/test_test_helpers.cxx
Normal file
@@ -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
|
||||
23
ext/libpqxx-7.7.3/test/unit/test_thread_safety_model.cxx
Normal file
23
ext/libpqxx-7.7.3/test/unit/test_thread_safety_model.cxx
Normal file
@@ -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
|
||||
86
ext/libpqxx-7.7.3/test/unit/test_time.cxx
Normal file
86
ext/libpqxx-7.7.3/test/unit/test_time.cxx
Normal file
@@ -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
|
||||
113
ext/libpqxx-7.7.3/test/unit/test_transaction.cxx
Normal file
113
ext/libpqxx-7.7.3/test/unit/test_transaction.cxx
Normal file
@@ -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
|
||||
106
ext/libpqxx-7.7.3/test/unit/test_transaction_base.cxx
Normal file
106
ext/libpqxx-7.7.3/test/unit/test_transaction_base.cxx
Normal file
@@ -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
|
||||
53
ext/libpqxx-7.7.3/test/unit/test_transaction_focus.cxx
Normal file
53
ext/libpqxx-7.7.3/test/unit/test_transaction_focus.cxx
Normal file
@@ -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
|
||||
130
ext/libpqxx-7.7.3/test/unit/test_transactor.cxx
Normal file
130
ext/libpqxx-7.7.3/test/unit/test_transactor.cxx
Normal file
@@ -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
|
||||
19
ext/libpqxx-7.7.3/test/unit/test_type_name.cxx
Normal file
19
ext/libpqxx-7.7.3/test/unit/test_type_name.cxx
Normal file
@@ -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
|
||||
16
ext/libpqxx-7.7.3/test/unit/test_zview.cxx
Normal file
16
ext/libpqxx-7.7.3/test/unit/test_zview.cxx
Normal file
@@ -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