Skip to content

Commit 1db68cd

Browse files
Made sure the INSERT statement works as expected
1 parent a56edd6 commit 1db68cd

File tree

6 files changed

+202
-58
lines changed

6 files changed

+202
-58
lines changed

include/sqlgen/duckdb/Connection.hpp

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,22 @@ class Connection {
5959
template <class ItBegin, class ItEnd>
6060
Result<Nothing> insert(const dynamic::Insert &_insert_stmt, ItBegin _begin,
6161
ItEnd _end) noexcept {
62+
using namespace std::ranges::views;
63+
6264
const auto sql = to_sql(_insert_stmt);
6365

66+
auto columns = internal::collect::vector(
67+
_insert_stmt.columns |
68+
transform([](const auto &_str) { return _str.c_str(); }));
69+
6470
return get_duckdb_logical_types(_insert_stmt.table, _insert_stmt.columns)
65-
.and_then([&](const auto &_types) -> Result<Nothing> {
71+
.and_then([&](auto _types) -> Result<Nothing> {
6672
duckdb_appender appender{};
6773
if (duckdb_appender_create_query(
6874
conn_->conn(), sql.c_str(),
6975
static_cast<idx_t>(_insert_stmt.columns.size()),
70-
_types.data(), nullptr, nullptr, &appender) == DuckDBError) {
76+
_types.data(), "sqlgen_appended_data", columns.data(),
77+
&appender) == DuckDBError) {
7178
return error("Could not create appender.");
7279
}
7380
const auto res = write_to_appender(_begin, _end, appender);
@@ -81,8 +88,12 @@ class Connection {
8188
using ValueType = transpilation::value_t<ContainerType>;
8289
auto res = Ref<duckdb_result>();
8390
duckdb_query(conn_->conn(), to_sql(_query).c_str(), res.get());
84-
return internal::to_container<ContainerType, Iterator<ValueType>>(
85-
Iterator<ValueType>(res, conn_));
91+
const auto result =
92+
internal::to_container<ContainerType, Iterator<ValueType>>(
93+
Iterator<ValueType>(res, conn_));
94+
// TODO: Destroy result inside of iterator.
95+
duckdb_destroy_result(res.get());
96+
return result;
8697
}
8798

8899
Result<Nothing> rollback() noexcept;
@@ -96,16 +107,27 @@ class Connection {
96107
return error(
97108
"Write operation already in progress - you cannot start another.");
98109
}
99-
appender_ = std::make_unique<duckdb_appender>();
100-
if (duckdb_appender_create(
101-
conn_->conn(),
102-
_write_stmt.table.schema ? _write_stmt.table.schema->c_str()
103-
: nullptr,
104-
_write_stmt.table.name.c_str(), appender_.get()) == DuckDBError) {
105-
appender_ = nullptr;
106-
return error("Could not create appender.");
107-
}
108-
return Nothing{};
110+
111+
using namespace std::ranges::views;
112+
113+
auto columns = internal::collect::vector(
114+
_write_stmt.columns |
115+
transform([](const auto &_str) { return _str.c_str(); }));
116+
117+
const auto sql = to_sql(_write_stmt);
118+
119+
return get_duckdb_logical_types(_write_stmt.table, _write_stmt.columns)
120+
.and_then([&](auto _types) -> Result<Nothing> {
121+
appender_ = std::make_unique<duckdb_appender>();
122+
if (duckdb_appender_create_query(
123+
conn_->conn(), sql.c_str(),
124+
static_cast<idx_t>(_write_stmt.columns.size()), _types.data(),
125+
"sqlgen_appended_data", columns.data(),
126+
appender_.get()) == DuckDBError) {
127+
return error("Could not create appender.");
128+
}
129+
return Nothing{};
130+
});
109131
}
110132

111133
Result<Nothing> end_write() {
@@ -141,13 +163,24 @@ class Connection {
141163
const auto select_from = dynamic::SelectFrom{
142164
.table_or_query = _table, .fields = fields, .limit = dynamic::Limit{0}};
143165

144-
auto res = Ref<duckdb_result>();
166+
duckdb_result res{};
167+
168+
const auto state =
169+
duckdb_query(conn_->conn(), to_sql(select_from).c_str(), &res);
170+
171+
if (state == DuckDBError) {
172+
const auto err = error(duckdb_result_error(&res));
173+
duckdb_destroy_result(&res);
174+
return err;
175+
}
176+
177+
const auto types = internal::collect::vector(
178+
iota(static_cast<idx_t>(0), static_cast<idx_t>(fields.size())) |
179+
transform(std::bind_front(duckdb_column_logical_type, &res)));
145180

146-
duckdb_query(conn_->conn(), to_sql(select_from).c_str(), res.get());
181+
duckdb_destroy_result(&res);
147182

148-
return internal::collect::vector(
149-
iota(static_cast<idx_t>(fields.size())) |
150-
transform(std::bind_front(duckdb_column_logical_type, res.get())));
183+
return types;
151184
}
152185

153186
template <class ItBegin, class ItEnd>

src/sqlgen/duckdb/Connection.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Result<Nothing> Connection::begin_transaction() noexcept {
1818
Result<Nothing> Connection::commit() noexcept { return execute("COMMIT;"); }
1919

2020
Result<Nothing> Connection::execute(const std::string& _sql) noexcept {
21-
duckdb_result res;
21+
duckdb_result res{};
2222
const auto state = duckdb_query(conn_->conn(), _sql.c_str(), &res);
2323
if (state == DuckDBError) {
2424
const auto err = error(duckdb_result_error(&res));

src/sqlgen/duckdb/to_sql.cpp

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -461,47 +461,32 @@ std::vector<std::pair<std::string, std::vector<std::string>>> get_enum_types(
461461
std::string insert_to_sql(const dynamic::Insert& _stmt) noexcept {
462462
using namespace std::ranges::views;
463463

464-
const auto to_placeholder = [](const size_t _i) -> std::string {
465-
return "$" + std::to_string(_i + 1);
466-
};
464+
std::stringstream stream;
467465

468-
const auto as_excluded = [](const std::string& _str) -> std::string {
469-
return _str + "=excluded." + _str;
470-
};
466+
stream << "INSERT ";
467+
468+
if (_stmt.or_replace) {
469+
stream << "OR REPLACE ";
470+
}
471+
472+
stream << "INTO ";
471473

472-
std::stringstream stream;
473-
stream << "INSERT INTO ";
474474
if (_stmt.table.schema) {
475475
stream << wrap_in_quotes(*_stmt.table.schema) << ".";
476476
}
477-
stream << wrap_in_quotes(_stmt.table.name);
478477

479-
stream << " (";
480-
stream << internal::strings::join(
481-
", ",
482-
internal::collect::vector(_stmt.columns | transform(wrap_in_quotes)));
483-
stream << ")";
478+
stream << wrap_in_quotes(_stmt.table.name);
484479

485-
stream << " VALUES (";
480+
stream << " BY NAME ( SELECT ";
486481
stream << internal::strings::join(
487482
", ", internal::collect::vector(
488-
iota(static_cast<size_t>(0), _stmt.columns.size()) |
489-
transform(to_placeholder)));
490-
stream << ")";
491-
492-
if (_stmt.or_replace) {
493-
stream << " ON CONFLICT (";
494-
stream << internal::strings::join(
495-
", ", internal::collect::vector(_stmt.constraints));
496-
stream << ")";
497-
498-
stream << " DO UPDATE SET ";
499-
stream << internal::strings::join(
500-
", ",
501-
internal::collect::vector(_stmt.columns | transform(as_excluded)));
502-
}
483+
_stmt.columns | transform([&](const auto _name) {
484+
return wrap_in_quotes(_name) + " AS " + wrap_in_quotes(_name);
485+
})));
486+
stream << " FROM sqlgen_appended_data)";
503487

504488
stream << ";";
489+
505490
return stream.str();
506491
}
507492

@@ -903,13 +888,25 @@ std::string update_to_sql(const dynamic::Update& _stmt) noexcept {
903888

904889
std::string write_to_sql(const dynamic::Write& _stmt) noexcept {
905890
using namespace std::ranges::views;
906-
const auto schema = wrap_in_quotes(_stmt.table.schema.value_or("public"));
907-
const auto table = wrap_in_quotes(_stmt.table.name);
908-
const auto colnames = internal::strings::join(
909-
", ",
910-
internal::collect::vector(_stmt.columns | transform(wrap_in_quotes)));
911-
return "COPY " + schema + "." + table + "(" + colnames +
912-
") FROM STDIN WITH DELIMITER '\t' NULL '\e' CSV QUOTE '\a';";
891+
892+
std::stringstream stream;
893+
stream << "INSERT INTO ";
894+
if (_stmt.table.schema) {
895+
stream << wrap_in_quotes(*_stmt.table.schema) << ".";
896+
}
897+
stream << wrap_in_quotes(_stmt.table.name);
898+
899+
stream << " BY NAME ( SELECT ";
900+
stream << internal::strings::join(
901+
", ", internal::collect::vector(
902+
_stmt.columns | transform([&](const auto _name) {
903+
return wrap_in_quotes(_name) + " AS " + wrap_in_quotes(_name);
904+
})));
905+
stream << " FROM sqlgen_appended_data)";
906+
907+
stream << ";";
908+
909+
return stream.str();
913910
}
914911

915912
} // namespace sqlgen::duckdb
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#include <gtest/gtest.h>
2+
3+
#include <rfl.hpp>
4+
#include <rfl/json.hpp>
5+
#include <sqlgen.hpp>
6+
#include <sqlgen/duckdb.hpp>
7+
#include <vector>
8+
9+
namespace test_insert_and_read {
10+
11+
struct Person {
12+
sqlgen::PrimaryKey<uint32_t> id;
13+
std::string first_name;
14+
std::string last_name;
15+
int age;
16+
};
17+
18+
TEST(duckdb, test_insert_and_read) {
19+
const auto people1 = std::vector<Person>(
20+
{Person{
21+
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
22+
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
23+
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
24+
Person{
25+
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
26+
27+
using namespace sqlgen;
28+
using namespace sqlgen::literals;
29+
30+
const auto people2 = duckdb::connect()
31+
.and_then(begin_transaction)
32+
.and_then(create_table<Person> | if_not_exists)
33+
.and_then(insert(people1))
34+
.and_then(commit)
35+
.and_then(sqlgen::read<std::vector<Person>>)
36+
.value();
37+
38+
const auto json1 = rfl::json::write(people1);
39+
const auto json2 = rfl::json::write(people2);
40+
41+
EXPECT_EQ(json1, json2);
42+
}
43+
44+
} // namespace test_insert_and_read
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#include <gtest/gtest.h>
2+
3+
#include <rfl.hpp>
4+
#include <rfl/json.hpp>
5+
#include <sqlgen.hpp>
6+
#include <sqlgen/duckdb.hpp>
7+
#include <vector>
8+
9+
namespace test_insert_or_replace {
10+
11+
struct Person {
12+
sqlgen::PrimaryKey<uint32_t> id;
13+
std::string first_name;
14+
std::string last_name;
15+
int age;
16+
};
17+
18+
TEST(duckdb, test_insert_or_replace) {
19+
const auto people1 = std::vector<Person>(
20+
{Person{
21+
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
22+
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
23+
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
24+
Person{
25+
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
26+
27+
const auto people2 = std::vector<Person>({Person{.id = 1,
28+
.first_name = "Bartholomew",
29+
.last_name = "Simpson",
30+
.age = 10},
31+
Person{.id = 3,
32+
.first_name = "Margaret",
33+
.last_name = "Simpson",
34+
.age = 1}});
35+
36+
const auto people3 = std::vector<Person>(
37+
{Person{
38+
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
39+
Person{.id = 1,
40+
.first_name = "Bartholomew",
41+
.last_name = "Simpson",
42+
.age = 10},
43+
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
44+
Person{.id = 3,
45+
.first_name = "Margaret",
46+
.last_name = "Simpson",
47+
.age = 1}});
48+
49+
using namespace sqlgen;
50+
using namespace sqlgen::literals;
51+
52+
const auto people4 =
53+
duckdb::connect()
54+
.and_then(begin_transaction)
55+
.and_then(create_table<Person> | if_not_exists)
56+
.and_then(insert(std::ref(people1)))
57+
.and_then(commit)
58+
.and_then(begin_transaction)
59+
.and_then(insert_or_replace(std::ref(people2)))
60+
.and_then(commit)
61+
.and_then(sqlgen::read<std::vector<Person>> | order_by("id"_c))
62+
.value();
63+
64+
const auto json3 = rfl::json::write(people3);
65+
const auto json4 = rfl::json::write(people4);
66+
67+
EXPECT_EQ(json3, json4);
68+
}
69+
70+
} // namespace test_insert_or_replace

vcpkg.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"dependencies": [
1212
{
1313
"name": "duckdb",
14-
"version>=": "1.3.2"
14+
"version>=": "1.4.1"
1515
}
1616
]
1717
},

0 commit comments

Comments
 (0)