-
Notifications
You must be signed in to change notification settings - Fork 248
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Encoding problems with bytea #827
Comments
When you send prepared statement parameters with libpq, all parameters are converted once into strings. Try replacing |
Thank you for your suggestion, but I tried adding ::bytea earlier and it didn't work. But thanks to your explanation I ve got a soluction that does work. By passing input into pqxx::internal::esc_bin it will produce escaped string and will work correctly afterwards. I would guess, that it also should not be too expensive by calling it this way: Anyways, i will also include my solution there in case somebody will find it useful: #include <pqxx/pqxx>
#include <iostream>
#include <format>
inline constexpr std::size_t DataSize = 8;
using Data = std::array<std::byte, DataSize>;
namespace pqxx
{
template <>
struct string_traits<Data>
{
// static constexpr bool converts_to_string{true};
static constexpr bool converts_from_string{true};
static constexpr auto DataSize = internal::size_esc_bin(std::tuple_size_v<Data>);
static Data from_string(std::string_view text)
{
if (text.size() + 1 != DataSize) //size_esc_bin adds 1 byte for \0 but std::string_view::size doesn't include it
{
throw pqxx::conversion_error(std::format("invalid data size: {}. Expected: {}", text.size(), DataSize));
}
Data buf;
pqxx::internal::unesc_bin(text, reinterpret_cast<std::byte *>(buf.data()));
return buf;
}
static zview to_buf(char *begin, char *end, Data const &value)
{
if (end - begin < DataSize)
{
throw pqxx::conversion_overrun(std::format("to_buf: unable to fit data into buffer with size {}", end - begin));
}
return generic_to_buf(begin, end, value);
}
static char *into_buf(char *begin, char *end, Data const &value)
{
if (end - begin < DataSize)
{
throw pqxx::conversion_overrun(std::format("into_buf: unable to fit data into buffer with size {}", end - begin));
}
for (auto i = 0; i < value.size(); i++)
{
begin[i] = static_cast<char>(value.at(i));
}
begin[value.size()] = '\0';
return begin + DataSize;
}
static size_t size_buffer(Data const &value) noexcept
{
return DataSize;
}
};
}
namespace std
{
template <>
struct formatter<Data> : formatter<string>
{
auto format(const Data &data, format_context &ctx) const
{
int tmpData[DataSize];
std::transform(data.begin(), data.end(), tmpData, [](auto elem)
{ return static_cast<int>(elem); });
return formatter<string>::format(
std::format("{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
tmpData[0], tmpData[1], tmpData[2], tmpData[3],
tmpData[4], tmpData[5], tmpData[6], tmpData[7]),
ctx);
}
};
}
inline constexpr std::byte operator""_bt(unsigned long long c)
{
return static_cast<std::byte>(c);
}
auto main(int argc, const char *argv[]) -> int
{
static constexpr std::string_view ConnectionQuerySchema = "host={} port={} dbname={} user={} password={}";
static constexpr std::string_view InsertionSchema = "INSERT INTO test_table VALUES ('\\x{}')";
static constexpr Data okData{0x01_bt, 0x01_bt, 0x01_bt, 0x01_bt, 0x01_bt, 0x01_bt, 0x01_bt, 0x01_bt};
static constexpr Data badData{0xd6_bt, 0x5f_bt, 0xd6_bt, 0x5f_bt, 0xd6_bt, 0x5f_bt, 0xd6_bt, 0x5f_bt};
try
{
pqxx::connection con(std::format(ConnectionQuerySchema, argv[1], argv[2], argv[3], argv[4], argv[5]));
con.prepare("insert", "INSERT INTO test_table VALUES ($1)");
con.prepare("view", "SELECT * FROM test_table");
pqxx::nontransaction nt(con);
nt.exec0("CREATE TABLE IF NOT EXISTS test_table(data bytea NOT NULL)");
std::cout << "Ok: " << std::boolalpha << nt.exec_prepared0("insert", okData).empty() << std::endl;
const auto query = std::format(InsertionSchema, badData);
std::cout << "Query: " << query << " Also ok: " << nt.exec0(query).empty() << std::endl;
std::cout << "Bad: " << nt.exec_prepared0("insert", pqxx::internal::esc_bin({badData.begin(), badData.end()})).empty() << std::endl;
const auto result = nt.exec_prepared("view");
for(auto row : result)
{
auto [data] = std::move(row.as<Data>());
std::cout << std::format("{}", data) << std::endl;
}
}
catch (const std::exception &e)
{
std::cerr << e.what() << '\n';
}
} |
Glad It's solved. |
Oops, and I'm only just trying to catch up! Thanks once again @tt4g for jumping in. @Broollie I did notice this one little thing: static Data from_string(std::string_view text)
{
if (text.size() != DataSize)
{
throw pqxx::conversion_error(std::format("invalid data size: {}. Expected: {}", text.size(), DataSize));
}
// ...
} It looks to me here as if you're using |
(I took the liberty to edit the code snippets you posted above: when you add "cxx" after the three backticks at the beginning, you get C++ syntax highlighting. :-) |
And I must admit that the mechanism by which libpq figures out the types of the parameters is mysterious to me as well. By the way if you're working in C++20 @Broollie, do you actually need your own Generally it's not a good idea to rely on items from the |
Hey, it s ok, i am myself always late :D Regarding DataSize, ye, that was my mistake on naming constants, didn't notice that I already have DataSize. It should be more like this // ...
inline constexpr std::size_t DataSize = 8;
struct string_traits<Data>
{
static constexpr auto EscDataSize = internal::size_esc_bin(std::tuple_size_v<Data>);
// ...
That s actually what made me attempt to do it in the first place, because std::array satisfies std::ranges::contiguous_range
And it s not just pqxx::row::as(), but pqxx::row::reference::get<>() as well.
Ye, looking through docs once again, i noticed convenient pqxx::binary_cast that not only works in my case, but also produces a view, so no copies (unlike pqxx::internal::esc_bin, which produces std::string) |
Upon further investigation, i found out that PQXX_HAVE_CONCEPTS was not defined, so now I wonder if there are other defines, that may be missing and, if defined, should enable direct use of std::array<std::byte, N> |
The I'm writing this on my phone where it's hard to look up past conversation... Did you select C++20 (or newer) when configuring the build? Because if so, I would definitely have expected it to detect concepts support. By the way beware that |
@Broollie when building libpqxx, did you use |
@jtv Ok, i ve figured out what s going on. Mostly. So, basically in my main project i build libpqxx with cmake and all defines are present there. But for this test I use libpqxx installed with brew package manager. And defines are missing there because by default libpqxx v7.9 uses |
But it also seems like I m missing something, because direct call to |
Oh, and to clarify, I saw, that |
Ah yes, the forbidden conversion - I guess that's two concepts clashing. A span of contiguous But more generically, a So we'll need a way to tell the compiler that we want the former in this case, not the latter. |
Or, given that the most specific specialisation should normally win out, perhaps it's a matter of removing a |
That is indeed a clash of 2 while not completely different, but still contradicting concepts. There are enough ways of resolving this conflict while keeping backward compatibility (or you can ignore it in favor of implementing changes for 8.x version and some changes in traits API, if there are any). I would guess it may be solved either by creating more specialisations (like having different implementations for |
@Broollie for now I'm just utterly stuck on getting readthedocs working again. I'm rebuilding that from scratch, because I see no other way at this point. Hopefully after that I can focus on actual progress again. |
@jtv sure, we can just keep the issue open so that you can return to it once you r done |
The good news is that I got the documentation build working again. (With lots of scary warning messages, so I'll be chipping away at that pile still.) I've got one other small feature to build, and then hopefully I can swap this ticket back into my personal memory and see what I can do about it. |
Reading this back again, I think what I need to do about this ticket is write a test that does conversions of a Of course that's a whole different ballgame in C++20 than it is in C++17, because I'll be counting on C++20 soon. It's not as easy as "make sure it works in C++20, we don't care about C++17." So I'm planning to keep this ticket on the shelf until then. Right now I've got a really nice idea of where to take the exec functions, and I think that would be a good cutoff point to start 8.0 development. |
Seems to be a reasonable test case and perhaps there should be a test for a |
Thanks! |
There has been no activity on this ticket. Consider closing it. |
Update: I've got the new exec functions in place. My next step is something fairly urgent that will be ABI-incompatible and deprecate the existing |
Been moving really slowly. But, I've released libpqxx 7.10 and branched off an 8.0 development branch. Reminder to self: write tests (and then get them to work) for string conversions of...
|
The |
Fixes: #694 Fixes: #827 Making much broader use of concepts. String conversions now accept any contiguous range of `std::byte` as binary data. Traits specialisations for integer and floating-point types are simpler now. And you can now just convert from string to `std::string_view` (or `char const *`), so long as you don't access it after the original string's lifetime ends.
Fixes: #694 Fixes: #827 Making much broader use of concepts. String conversions now accept any contiguous range of `std::byte` as binary data. Traits specialisations for integer and floating-point types are simpler now. And you can now just convert from string to `std::string_view` (or `char const *`), so long as you don't access it after the original string's lifetime ends.
Fixes: #694 Fixes: #827 Making much broader use of concepts. String conversions now accept any contiguous range of `std::byte` as binary data. Traits specialisations for integer and floating-point types are simpler now. And you can now just convert from string to `std::string_view` (or `char const *`), so long as you don't access it after the original string's lifetime ends.
* Retire `binarystring`. This type has been deprecated since 7.2.0, more than 4 years ago. * String conversion to `string_view`. More binary types. Fixes: #694 Fixes: #827 Making much broader use of concepts. String conversions now accept any contiguous range of `std::byte` as binary data. Traits specialisations for integer and floating-point types are simpler now. And you can now just convert from string to `std::string_view` (or `char const *`), so long as you don't access it after the original string's lifetime ends. * Work around Visual Studio 2022 concepts problem. This compiler was having trouble with the syntax I used to specialise the generic `string_traits<T>` template to a _concept_ `T` (as opposed to run-of-the-mill specialisation to a _type_ `T`). So just for the floating-point string traits, I went back to the old setup where I had a separate implementation type template (`string_float_traits`) and derived the `string_traits` implementations for those types from instantiations of that template. * Forbid string conversion from `char const *`. It was stupid of me to allow this. I hope nobody ever used it.
That test works now. This feature has been merged into the 8.0 working branch. |
* Retire `binarystring`. This type has been deprecated since 7.2.0, more than 4 years ago. * String conversion to `string_view`. More binary types. Fixes: #694 Fixes: #827 Making much broader use of concepts. String conversions now accept any contiguous range of `std::byte` as binary data. Traits specialisations for integer and floating-point types are simpler now. And you can now just convert from string to `std::string_view` (or `char const *`), so long as you don't access it after the original string's lifetime ends. * Work around Visual Studio 2022 concepts problem. This compiler was having trouble with the syntax I used to specialise the generic `string_traits<T>` template to a _concept_ `T` (as opposed to run-of-the-mill specialisation to a _type_ `T`). So just for the floating-point string traits, I went back to the old setup where I had a separate implementation type template (`string_float_traits`) and derived the `string_traits` implementations for those types from instantiations of that template. * Forbid string conversion from `char const *`. It was stupid of me to allow this. I hope nobody ever used it.
Hello, I encountered an interesting issue. I have a table with column of type bytea. Data in this column is of fixed size so in order to ommit manual runtime size checks I implemented string_traits for std::array<std::byte, some size> to use this type directly in prepared statements. And it does work.. until it doesn't. Depending on contents, query may result in this error: "ERROR: invalid byte sequence for encoding "UTF8": 0xd6 0x5f CONTEXT: unnamed portal parameter $1" (0xd6, 0x5f just for an instance). And i m not sure what causes it, but apparently this error comes from libpq call, but I cannot be sure about this. Here is code that represents my case:
Compiled as follows: g++-13 -std=c++20 test.cpp -o test -lpqxx -lpq
The text was updated successfully, but these errors were encountered: