Skip to content

Commit ae807b0

Browse files
authored
Merge pull request #1941 from joto/pgsql-binary
Add functionality to send data to PostgreSQL in binary
2 parents 83b9fbe + 2d8f654 commit ae807b0

2 files changed

Lines changed: 60 additions & 16 deletions

File tree

src/pgsql.hpp

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,20 @@ class pg_result_t
126126
std::unique_ptr<PGresult, pg_result_deleter_t> m_result;
127127
};
128128

129+
/**
130+
* Wrapper class for query parameters that should be sent to the database
131+
* as binary parameter.
132+
*/
133+
class binary_param : public std::string_view
134+
{
135+
public:
136+
using std::string_view::string_view;
137+
138+
binary_param(std::string const &str)
139+
: std::string_view(str.data(), str.size())
140+
{}
141+
};
142+
129143
/**
130144
* PostgreSQL connection.
131145
*
@@ -229,6 +243,8 @@ class pg_conn_t
229243
return 0;
230244
} else if constexpr (std::is_same_v<T, std::string>) {
231245
return 0;
246+
} else if constexpr (std::is_same_v<T, binary_param>) {
247+
return 0;
232248
}
233249
return 1;
234250
}
@@ -240,12 +256,18 @@ class pg_conn_t
240256
* strings.
241257
*/
242258
template <typename T>
243-
static char const *to_str(std::vector<std::string> *data, T const &param)
259+
static char const *to_str(std::vector<std::string> *data, int *length,
260+
int *bin, T const &param)
244261
{
245262
if constexpr (std::is_same_v<T, char const *>) {
246263
return param;
247264
} else if constexpr (std::is_same_v<T, std::string>) {
265+
*length = param.size();
248266
return param.c_str();
267+
} else if constexpr (std::is_same_v<T, binary_param>) {
268+
*length = param.size();
269+
*bin = 1;
270+
return param.data();
249271
}
250272
return data->emplace_back(fmt::to_string(param)).c_str();
251273
}
@@ -275,16 +297,21 @@ class pg_conn_t
275297
std::vector<std::string> exec_params;
276298
exec_params.reserve(total_buffers_needed);
277299

300+
std::array<int, sizeof...(params)> lengths = {0};
301+
std::array<int, sizeof...(params)> bins = {0};
302+
278303
// This array holds the pointers to all parameter strings, either
279304
// to the original string parameters or to the recently converted
280305
// in the exec_params vector.
306+
std::size_t n = 0;
307+
std::size_t m = 0;
281308
std::array<char const *, sizeof...(params)> param_ptrs = {
282-
to_str<std::decay_t<TArgs>>(&exec_params,
309+
to_str<std::decay_t<TArgs>>(&exec_params, &lengths[n++], &bins[m++],
283310
std::forward<TArgs>(params))...};
284311

285312
return exec_prepared_internal(stmt, sizeof...(params),
286-
param_ptrs.data(), nullptr, nullptr,
287-
result_as_binary ? 1 : 0);
313+
param_ptrs.data(), lengths.data(),
314+
bins.data(), result_as_binary ? 1 : 0);
288315
}
289316

290317
struct pg_conn_deleter_t

tests/test-pgsql.cpp

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,6 @@ TEST_CASE("exec with invalid SQL should fail")
5656
REQUIRE_THROWS(conn.exec("XYZ"));
5757
}
5858

59-
TEST_CASE("exec_prepared without parameters should work")
60-
{
61-
auto const conn = db.db().connect();
62-
conn.exec("PREPARE test AS SELECT 42");
63-
64-
auto const result = conn.exec_prepared("test");
65-
REQUIRE(result.status() == PGRES_TUPLES_OK);
66-
REQUIRE(result.num_fields() == 1);
67-
REQUIRE(result.num_tuples() == 1);
68-
REQUIRE(result.get(0, 0) == "42");
69-
}
70-
7159
TEST_CASE("exec_prepared with single string parameters should work")
7260
{
7361
auto const conn = db.db().connect();
@@ -108,6 +96,35 @@ TEST_CASE("exec_prepared with non-string parameters should work")
10896
REQUIRE(result.get(0, 0) == "6");
10997
}
11098

99+
TEST_CASE("exec_prepared with binary parameter should work")
100+
{
101+
auto const conn = db.db().connect();
102+
conn.exec("PREPARE test(bytea) AS SELECT length($1)");
103+
104+
binary_param const p{"foo \x01 bar"};
105+
auto const result = conn.exec_prepared("test", p);
106+
REQUIRE(result.status() == PGRES_TUPLES_OK);
107+
REQUIRE(result.num_fields() == 1);
108+
REQUIRE(result.num_tuples() == 1);
109+
REQUIRE(result.get(0, 0) == "9");
110+
}
111+
112+
TEST_CASE("exec_prepared with mixed parameter types should work")
113+
{
114+
auto const conn = db.db().connect();
115+
conn.exec("PREPARE test(text, bytea, int) AS"
116+
" SELECT length($1) + length($2) + $3");
117+
118+
std::string const p1{"foo bar"};
119+
binary_param const p2{"foo \x01 bar"};
120+
int const p3 = 17;
121+
auto const result = conn.exec_prepared("test", p1, p2, p3);
122+
REQUIRE(result.status() == PGRES_TUPLES_OK);
123+
REQUIRE(result.num_fields() == 1);
124+
REQUIRE(result.num_tuples() == 1);
125+
REQUIRE(result.get(0, 0) == "33"); // 7 + 9 + 17
126+
}
127+
111128
TEST_CASE("create table and insert something")
112129
{
113130
auto const conn = db.db().connect();

0 commit comments

Comments
 (0)