Implement insert_or_replace (#45)

Co-authored-by: Dr. Patrick Urbanke <patrick@getml.com>
This commit is contained in:
Demian Nave
2025-09-07 05:14:02 -04:00
committed by GitHub
parent 82e1c193d3
commit 8b6b6c5a59
24 changed files with 675 additions and 39 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
name: linux-cxx20-conan
on: [push]
on: [push, pull_request]
jobs:
linux:
+1 -1
View File
@@ -1,6 +1,6 @@
name: linux-cxx20-vcpkg
on: [push]
on: [push, pull_request]
env:
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
+1 -1
View File
@@ -1,6 +1,6 @@
name: macos-cxx20-conan
on: [push]
on: [push, pull_request]
jobs:
macos-clang:
+2 -1
View File
@@ -1,6 +1,6 @@
name: macos-cxx20-vcpkg
on: [push]
on: [push, pull_request]
env:
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
@@ -57,6 +57,7 @@ jobs:
if [[ "${{ matrix.os == 'macos-latest' }}" == "true" ]]; then
export VCPKG_FORCE_SYSTEM_BINARIES=arm
export CMAKE_GENERATOR=Ninja
export MACOSX_DEPLOYMENT_TARGET="$(sw_vers -productVersion)"
fi
$CXX --version
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_SQLITE3=OFF -DSQLGEN_BUILD_DRY_TESTS_ONLY=ON
+1 -1
View File
@@ -1,6 +1,6 @@
name: windows-cxx20-vcpkg
on: [push]
on: [push, pull_request]
env:
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
+53 -2
View File
@@ -68,6 +68,58 @@ sqlgen::sqlite::connect("database.db")
.value();
```
### With Replacement
Replace existing rows:
```cpp
const auto people1 = std::vector<Person>({
Person{.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}
});
const auto people2 = std::vector<Person>({
Person{.id = 1, .first_name = "Bartholomew", .last_name = "Simpson", .age = 10}
});
using namespace sqlgen;
const auto result = sqlite::connect()
.and_then(create_table<Person> | if_not_exists)
.and_then(insert(std::ref(people1)))
.and_then(insert_or_replace(std::ref(people2)))
.value();
```
This generates the following SQL:
```sql
CREATE TABLE IF NOT EXISTS "Person" (
"id" INTEGER PRIMARY KEY,
"first_name" TEXT NOT NULL,
"last_name" TEXT NOT NULL,
"age" INTEGER NOT NULL
);
INSERT INTO "Person" ("id", "first_name", "last_name", "age") VALUES (?, ?, ?, ?);
INSERT INTO "Person" ("id", "first_name", "last_name", "age") VALUES (?, ?, ?, ?)
ON CONFLICT (id) DO UPDATE SET
id=excluded.id,
first_name=excluded.first_name,
last_name=excluded.last_name,
age=excluded.age;
```
The SQL generated by the PostgreSQL backend for `insert_or_replace(...)` is similar. The MySQL backend generates `ON DUPLICATE KEY UPDATE` instead:
```sql
...
INSERT INTO `Person` (`id`, `first_name`, `last_name`, `age`) VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
id=VALUES(id),
first_name=VALUES(first_name),
last_name=VALUES(last_name),
age=VALUES(age);
```
## Example: Full Transaction Usage
Here's a complete example showing how to use `insert` within a transaction:
@@ -195,5 +247,4 @@ While both `insert` and `write` can be used to add data to a database, they serv
4. Takes a connection and a reference wrapper to a container
- Unlike `write`, `insert` does not create tables automatically - you must create tables separately using `create_table`
- The insert operation is atomic within a transaction
- When using reference wrappers (`std::ref`), the data is not copied, which can be more efficient for large datasets
- When using reference wrappers (`std::ref`), the data is not copied, which can be more efficient for large datasets
+5
View File
@@ -11,6 +11,11 @@ namespace sqlgen::dynamic {
struct Insert {
Table table;
std::vector<std::string> columns;
bool or_replace;
std::vector<std::string> non_primary_keys;
/// Holds primary keys and unique columns when or_replace is true.
std::vector<std::string> constraints;
};
} // namespace sqlgen::dynamic
+62 -17
View File
@@ -7,9 +7,11 @@
#include <rfl.hpp>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "internal/batch_size.hpp"
#include "internal/has_constraint.hpp"
#include "internal/to_str_vec.hpp"
#include "is_connection.hpp"
#include "transpilation/to_insert_or_write.hpp"
@@ -18,13 +20,14 @@ namespace sqlgen {
template <class ItBegin, class ItEnd, class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> insert(const Ref<Connection>& _conn, ItBegin _begin,
ItEnd _end) {
Result<Ref<Connection>> insert_impl(const Ref<Connection>& _conn,
ItBegin _begin, ItEnd _end,
bool _or_replace) {
using T =
std::remove_cvref_t<typename std::iterator_traits<ItBegin>::value_type>;
const auto insert_stmt =
transpilation::to_insert_or_write<T, dynamic::Insert>();
transpilation::to_insert_or_write<T, dynamic::Insert>(_or_replace);
std::vector<std::vector<std::optional<std::string>>> data;
@@ -51,40 +54,82 @@ Result<Ref<Connection>> insert(const Ref<Connection>& _conn, ItBegin _begin,
template <class ItBegin, class ItEnd, class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> insert(const Result<Ref<Connection>>& _res,
ItBegin _begin, ItEnd _end) {
return _res.and_then(
[&](const auto& _conn) { return insert(_conn, _begin, _end); });
Result<Ref<Connection>> insert_impl(const Result<Ref<Connection>>& _res,
ItBegin _begin, ItEnd _end,
bool _or_replace) {
return _res.and_then([&](const auto& _conn) {
return insert_impl(_conn, _begin, _end, _or_replace);
});
}
template <class ContainerType>
auto insert(const auto& _conn, const ContainerType& _data) {
auto insert_impl(const auto& _conn, const ContainerType& _data,
bool _or_replace) {
if constexpr (std::ranges::input_range<std::remove_cvref_t<ContainerType>>) {
return insert(_conn, _data.begin(), _data.end());
return insert_impl(_conn, _data.begin(), _data.end(), _or_replace);
} else {
return insert(_conn, &_data, &_data + 1);
return insert_impl(_conn, &_data, &_data + 1, _or_replace);
}
}
template <class ContainerType>
auto insert(const auto& _conn,
const std::reference_wrapper<ContainerType>& _data) {
return insert(_conn, _data.get());
auto insert_impl(const auto& _conn,
const std::reference_wrapper<ContainerType>& _data,
bool _or_replace) {
return insert_impl(_conn, _data.get(), _or_replace);
}
template <class ContainerType>
struct Insert {
auto operator()(const auto& _conn) const { return insert(_conn, data_); }
auto operator()(const auto& _conn) const {
return insert_impl(_conn, data_, or_replace_);
}
ContainerType data_;
bool or_replace_;
};
template <class ContainerType>
Insert<ContainerType> insert(const ContainerType& _data) {
return Insert<ContainerType>{.data_ = _data};
Insert<ContainerType> insert_impl(const ContainerType& _data,
bool _or_replace) {
return Insert<ContainerType>{.data_ = _data, .or_replace_ = _or_replace};
}
template <class... Args>
auto insert(const Args&... args) {
return insert_impl(args..., false);
}
template <class ContainerType>
auto insert_or_replace(const auto& _conn, const ContainerType& _data) {
if constexpr (std::ranges::input_range<std::remove_cvref_t<ContainerType>>) {
static_assert(
internal::has_constraint_v<typename ContainerType::value_type>,
"The table must have a primary key or unique column for "
"insert_or_replace(...) to work.");
} else {
static_assert(internal::has_constraint_v<ContainerType>,
"The table must have a primary key or unique column for "
"insert_or_replace(...) to work.");
}
return insert_impl(_conn, _data, true);
}
template <class ContainerType>
auto insert_or_replace(const ContainerType& _data) {
if constexpr (std::ranges::input_range<std::remove_cvref_t<ContainerType>>) {
static_assert(
internal::has_constraint_v<typename ContainerType::value_type>,
"The table must have a primary key or unique column for "
"insert_or_replace(...) to work.");
} else {
static_assert(internal::has_constraint_v<ContainerType>,
"The table must have a primary key or unique column for "
"insert_or_replace(...) to work.");
}
return insert_impl(_data, true);
}
}; // namespace sqlgen
#endif
@@ -0,0 +1,24 @@
#ifndef SQLGEN_INTERNAL_HAS_CONSTRAINT_HPP_
#define SQLGEN_INTERNAL_HAS_CONSTRAINT_HPP_
#include <type_traits>
#include "is_constraint.hpp"
namespace sqlgen::internal {
template <class T>
struct has_constraint;
template <class... FieldTypes>
struct has_constraint<rfl::NamedTuple<FieldTypes...>> {
constexpr static bool value =
(false || ... || is_constraint_v<typename FieldTypes::Type>);
};
template <class T>
constexpr bool has_constraint_v = has_constraint<rfl::named_tuple_t<T>>::value;
} // namespace sqlgen::internal
#endif
@@ -0,0 +1,25 @@
#ifndef SQLGEN_INTERNAL_HAS_PRIMARY_KEY_HPP_
#define SQLGEN_INTERNAL_HAS_PRIMARY_KEY_HPP_
#include <rfl.hpp>
#include "is_primary_key.hpp"
namespace sqlgen::internal {
template <class T>
struct has_primary_key;
template <class... FieldTypes>
struct has_primary_key<rfl::NamedTuple<FieldTypes...>> {
constexpr static bool value =
(false || ... || is_primary_key_v<typename FieldTypes::Type>);
};
template <class T>
constexpr bool has_primary_key_v =
has_primary_key<rfl::named_tuple_t<T>>::value;
} // namespace sqlgen::internal
#endif
@@ -0,0 +1,25 @@
#ifndef SQLGEN_INTERNAL_HAS_UNIQUE_COLUMN_HPP_
#define SQLGEN_INTERNAL_HAS_UNIQUE_COLUMN_HPP_
#include <rfl.hpp>
#include "is_unique_column.hpp"
namespace sqlgen::internal {
template <class T>
struct has_unique_column;
template <class... FieldTypes>
struct has_unique_column<rfl::NamedTuple<FieldTypes...>> {
constexpr static bool value =
(false || ... || is_unique_column_v<typename FieldTypes::Type>);
};
template <class T>
constexpr bool has_unique_column_v =
has_unique_column<rfl::named_tuple_t<T>>::value;
} // namespace sqlgen::internal
#endif
+19
View File
@@ -0,0 +1,19 @@
#ifndef SQLGEN_INTERNAL_IS_CONSTRAINT_HPP_
#define SQLGEN_INTERNAL_IS_CONSTRAINT_HPP_
#include <type_traits>
#include "is_primary_key.hpp"
#include "is_unique_column.hpp"
namespace sqlgen::internal {
template <class T>
using is_constraint = std::disjunction<is_primary_key<T>, is_unique_column<T>>;
template <class T>
constexpr bool is_constraint_v = is_constraint<std::remove_cvref_t<T>>();
} // namespace sqlgen::internal
#endif
@@ -0,0 +1,24 @@
#ifndef SQLGEN_INTERNAL_IS_PRIMARY_KEY_HPP_
#define SQLGEN_INTERNAL_IS_PRIMARY_KEY_HPP_
#include <type_traits>
#include "../PrimaryKey.hpp"
namespace sqlgen::internal {
template <class T>
class is_primary_key;
template <class T>
class is_primary_key : public std::false_type {};
template <class T, bool _auto_incr>
class is_primary_key<PrimaryKey<T, _auto_incr>> : public std::true_type {};
template <class T>
constexpr bool is_primary_key_v = is_primary_key<std::remove_cvref_t<T>>();
} // namespace sqlgen::internal
#endif
@@ -0,0 +1,24 @@
#ifndef SQLGEN_INTERNAL_IS_UNIQUE_COLUMN_HPP_
#define SQLGEN_INTERNAL_IS_UNIQUE_COLUMN_HPP_
#include <type_traits>
#include "../Unique.hpp"
namespace sqlgen::internal {
template <class T>
class is_unique_column;
template <class T>
class is_unique_column : public std::false_type {};
template <class T>
class is_unique_column<Unique<T>> : public std::true_type {};
template <class T>
constexpr bool is_unique_column_v = is_unique_column<std::remove_cvref_t<T>>();
} // namespace sqlgen::internal
#endif
@@ -20,7 +20,7 @@ namespace sqlgen::transpilation {
template <class T, class InsertOrWrite>
requires std::is_class_v<std::remove_cvref_t<T>> &&
std::is_aggregate_v<std::remove_cvref_t<T>>
InsertOrWrite to_insert_or_write() {
InsertOrWrite to_insert_or_write(bool or_replace) {
using namespace std::ranges::views;
using NamedTupleType = sqlgen::internal::remove_auto_incr_primary_t<
@@ -32,10 +32,41 @@ InsertOrWrite to_insert_or_write() {
const auto get_name = [](const auto& _col) { return _col.name; };
return InsertOrWrite{.table = dynamic::Table{.name = get_tablename<T>(),
.schema = get_schema<T>()},
.columns = sqlgen::internal::collect::vector(
columns | transform(get_name))};
auto result = InsertOrWrite{
.table =
dynamic::Table{.name = get_tablename<T>(), .schema = get_schema<T>()},
.columns =
sqlgen::internal::collect::vector(columns | transform(get_name))};
if constexpr (std::is_same_v<InsertOrWrite, dynamic::Insert>) {
const auto is_non_primary = [](const auto& _c) {
return _c.type.visit(
[](const auto& _t) { return !_t.properties.primary; });
};
const auto is_constraint = [](const auto& _c) {
return _c.type.visit([](const auto& _t) {
return _t.properties.primary || _t.properties.unique;
});
};
result.or_replace = or_replace;
result.non_primary_keys = sqlgen::internal::collect::vector(
columns | filter(is_non_primary) | transform(get_name));
if (or_replace) {
result.constraints = sqlgen::internal::collect::vector(
columns | filter(is_constraint) | transform(get_name));
}
}
return result;
}
template <class T, class InsertOrWrite>
requires std::is_class_v<std::remove_cvref_t<T>> &&
std::is_aggregate_v<std::remove_cvref_t<T>>
InsertOrWrite to_insert_or_write() {
return to_insert_or_write<T, InsertOrWrite>(false);
}
} // namespace sqlgen::transpilation
+18 -4
View File
@@ -517,6 +517,14 @@ template <class InsertOrWrite>
std::string insert_or_write_to_sql(const InsertOrWrite& _stmt) noexcept {
using namespace std::ranges::views;
const auto to_questionmark = [](const std::string&) -> std::string {
return "?";
};
const auto as_values = [](const std::string& _str) -> std::string {
return _str + "=VALUES(" + _str + ")";
};
std::stringstream stream;
stream << "INSERT INTO ";
@@ -533,11 +541,17 @@ std::string insert_or_write_to_sql(const InsertOrWrite& _stmt) noexcept {
stream << " VALUES (";
stream << internal::strings::join(
", ", internal::collect::vector(
_stmt.columns |
transform([](const auto&) -> std::string { return "?"; })));
stream << ");";
", ", internal::collect::vector(_stmt.columns | transform(to_questionmark)));
stream << ")";
if constexpr (std::is_same_v<InsertOrWrite, dynamic::Insert>) {
if (_stmt.or_replace) {
stream << " ON DUPLICATE KEY UPDATE ";
stream << internal::strings::join(
", ", internal::collect::vector(_stmt.columns | transform(as_values)));
}
}
stream << ';';
return stream.str();
}
+19 -3
View File
@@ -392,12 +392,16 @@ std::string insert_to_sql(const dynamic::Insert& _stmt) noexcept {
return "$" + std::to_string(_i + 1);
};
const auto as_excluded = [](const std::string& _str) -> std::string {
return _str + "=excluded." + _str;
};
std::stringstream stream;
stream << "INSERT INTO ";
if (_stmt.table.schema) {
stream << "\"" << *_stmt.table.schema << "\".";
stream << wrap_in_quotes(*_stmt.table.schema) << ".";
}
stream << "\"" << _stmt.table.name << "\"";
stream << wrap_in_quotes(_stmt.table.name);
stream << " (";
stream << internal::strings::join(
@@ -410,8 +414,20 @@ std::string insert_to_sql(const dynamic::Insert& _stmt) noexcept {
", ", internal::collect::vector(
iota(static_cast<size_t>(0), _stmt.columns.size()) |
transform(to_placeholder)));
stream << ");";
stream << ")";
if (_stmt.or_replace) {
stream << " ON CONFLICT (";
stream << internal::strings::join(
", ", internal::collect::vector(_stmt.constraints));
stream << ")";
stream << " DO UPDATE SET ";
stream << internal::strings::join(
", ", internal::collect::vector(_stmt.columns | transform(as_excluded)));
}
stream << ";";
return stream.str();
}
+22 -3
View File
@@ -388,12 +388,17 @@ std::string insert_or_write_to_sql(const InsertOrWrite& _stmt) noexcept {
return "?";
};
const auto as_excluded = [](const std::string& _str) -> std::string {
return _str + "=excluded." + _str;
};
std::stringstream stream;
stream << "INSERT INTO ";
if (_stmt.table.schema) {
stream << "\"" << *_stmt.table.schema << "\".";
stream << wrap_in_quotes(*_stmt.table.schema) << ".";
}
stream << "\"" << _stmt.table.name << "\"";
stream << wrap_in_quotes(_stmt.table.name);
stream << " (";
stream << internal::strings::join(
@@ -404,8 +409,22 @@ std::string insert_or_write_to_sql(const InsertOrWrite& _stmt) noexcept {
stream << internal::strings::join(
", ",
internal::collect::vector(_stmt.columns | transform(to_questionmark)));
stream << ");";
stream << ")";
if constexpr (std::is_same_v<InsertOrWrite, dynamic::Insert>) {
if (_stmt.or_replace) {
stream << " ON CONFLICT (";
stream << internal::strings::join(
", ", internal::collect::vector(_stmt.constraints));
stream << ")";
stream << " DO UPDATE SET ";
stream << internal::strings::join(
", ", internal::collect::vector(_stmt.columns | transform(as_excluded)));
}
}
stream << ';';
return stream.str();
}
+79
View File
@@ -0,0 +1,79 @@
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/mysql.hpp>
#include <vector>
namespace test_insert_or_replace {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(mysql, test_insert_or_replace) {
const auto people1 = std::vector<Person>(
{Person{
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
Person{
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
const auto people2 = std::vector<Person>({Person{.id = 1,
.first_name = "Bartholomew",
.last_name = "Simpson",
.age = 10},
Person{.id = 3,
.first_name = "Margaret",
.last_name = "Simpson",
.age = 1}});
const auto people3 = std::vector<Person>(
{Person{
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
Person{.id = 1,
.first_name = "Bartholomew",
.last_name = "Simpson",
.age = 10},
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
Person{.id = 3,
.first_name = "Margaret",
.last_name = "Simpson",
.age = 1}});
using namespace sqlgen;
using namespace sqlgen::literals;
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
.user = "sqlgen",
.password = "password",
.dbname = "mysql"};
const auto people4 =
sqlgen::mysql::connect(credentials)
.and_then(drop<Person> | if_exists)
.and_then(begin_transaction)
.and_then(create_table<Person> | if_not_exists)
.and_then(insert(people1))
.and_then(commit)
.and_then(begin_transaction)
.and_then(insert_or_replace(people2))
.and_then(commit)
.and_then(sqlgen::read<std::vector<Person>> | order_by("id"_c))
.value();
const auto json3 = rfl::json::write(people3);
const auto json4 = rfl::json::write(people4);
EXPECT_EQ(json3, json4);
}
} // namespace test_insert_or_replace
#endif
@@ -0,0 +1,31 @@
#include <gtest/gtest.h>
#include <sqlgen.hpp>
#include <sqlgen/dynamic/Insert.hpp>
#include <sqlgen/mysql.hpp>
#include <sqlgen/transpilation/to_insert_or_write.hpp>
namespace test_to_insert_or_replace_dry {
struct TestTable {
std::string field1;
int32_t field2;
sqlgen::Unique<std::string> field3;
sqlgen::PrimaryKey<uint32_t> id;
std::optional<std::string> nullable;
};
TEST(mysql, test_to_insert_or_replace_dry) {
static_assert(sqlgen::internal::has_constraint_v<TestTable>,
"The table must have a primary key or unique column for "
"insert_or_replace(...) to work.");
const auto insert_stmt =
sqlgen::dynamic::Statement(sqlgen::transpilation::to_insert_or_write<TestTable,
sqlgen::dynamic::Insert>(true));
const auto expected =
R"(INSERT INTO `TestTable` (`field1`, `field2`, `field3`, `id`, `nullable`) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE field1=VALUES(field1), field2=VALUES(field2), field3=VALUES(field3), id=VALUES(id), nullable=VALUES(nullable);)";
EXPECT_EQ(sqlgen::mysql::to_sql(insert_stmt), expected);
}
} // namespace test_to_insert_or_replace_dry
+80
View File
@@ -0,0 +1,80 @@
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/postgres.hpp>
#include <vector>
namespace test_insert_or_replace {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(postgres, test_insert_or_replace) {
const auto people1 = std::vector<Person>(
{Person{
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
Person{
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
const auto people2 = std::vector<Person>({Person{.id = 1,
.first_name = "Bartholomew",
.last_name = "Simpson",
.age = 10},
Person{.id = 3,
.first_name = "Margaret",
.last_name = "Simpson",
.age = 1}});
const auto people3 = std::vector<Person>(
{Person{
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
Person{.id = 1,
.first_name = "Bartholomew",
.last_name = "Simpson",
.age = 10},
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
Person{.id = 3,
.first_name = "Margaret",
.last_name = "Simpson",
.age = 1}});
using namespace sqlgen;
using namespace sqlgen::literals;
const auto credentials = sqlgen::postgres::Credentials{.user = "postgres",
.password = "password",
.host = "localhost",
.dbname = "postgres"};
const auto people4 =
sqlgen::postgres::connect(credentials)
.and_then(drop<Person> | if_exists)
.and_then(begin_transaction)
.and_then(create_table<Person> | if_not_exists)
.and_then(insert(people1))
.and_then(commit)
.and_then(begin_transaction)
.and_then(insert_or_replace(people2))
.and_then(commit)
.and_then(sqlgen::read<std::vector<Person>> | order_by("id"_c))
.value();
const auto json3 = rfl::json::write(people3);
const auto json4 = rfl::json::write(people4);
EXPECT_EQ(json3, json4);
}
} // namespace test_insert_or_replace
#endif
@@ -0,0 +1,31 @@
#include <gtest/gtest.h>
#include <sqlgen.hpp>
#include <sqlgen/dynamic/Insert.hpp>
#include <sqlgen/postgres.hpp>
#include <sqlgen/transpilation/to_insert_or_write.hpp>
namespace test_to_insert_or_replace_dry {
struct TestTable {
std::string field1;
int32_t field2;
sqlgen::Unique<std::string> field3;
sqlgen::PrimaryKey<uint32_t> id;
std::optional<std::string> nullable;
};
TEST(postgres, test_to_insert_or_replace_dry) {
static_assert(sqlgen::internal::has_constraint_v<TestTable>,
"The table must have a primary key or unique column for "
"insert_or_replace(...) to work.");
const auto insert_stmt = sqlgen::dynamic::Statement(
sqlgen::transpilation::to_insert_or_write<TestTable,
sqlgen::dynamic::Insert>(true));
const auto expected =
R"(INSERT INTO "TestTable" ("field1", "field2", "field3", "id", "nullable") VALUES ($1, $2, $3, $4, $5) ON CONFLICT (field3, id) DO UPDATE SET field1=excluded.field1, field2=excluded.field2, field3=excluded.field3, id=excluded.id, nullable=excluded.nullable;)";
EXPECT_EQ(sqlgen::postgres::to_sql(insert_stmt), expected);
}
} // namespace test_to_insert_or_replace_dry
+60
View File
@@ -0,0 +1,60 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/sqlite.hpp>
#include <vector>
namespace test_insert_or_replace {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(sqlite, test_insert_or_replace) {
const auto people1 = std::vector<Person>(
{Person{
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
Person{
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
const auto people2 = std::vector<Person>(
{Person{.id = 1, .first_name = "Bartholomew", .last_name = "Simpson", .age = 10},
Person{
.id = 3, .first_name = "Margaret", .last_name = "Simpson", .age = 1}});
const auto people3 = std::vector<Person>(
{Person{
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
Person{.id = 1, .first_name = "Bartholomew", .last_name = "Simpson", .age = 10},
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
Person{
.id = 3, .first_name = "Margaret", .last_name = "Simpson", .age = 1}});
using namespace sqlgen;
using namespace sqlgen::literals;
const auto people4 = sqlite::connect()
.and_then(begin_transaction)
.and_then(create_table<Person> | if_not_exists)
.and_then(insert(people1))
.and_then(commit)
.and_then(begin_transaction)
.and_then(insert_or_replace(people2))
.and_then(commit)
.and_then(sqlgen::read<std::vector<Person>> | order_by("id"_c))
.value();
const auto json3 = rfl::json::write(people3);
const auto json4 = rfl::json::write(people4);
EXPECT_EQ(json3, json4);
}
} // namespace test_insert_or_replace
@@ -0,0 +1,32 @@
#include <gtest/gtest.h>
#include <sqlgen.hpp>
#include <sqlgen/dynamic/Insert.hpp>
#include <sqlgen/sqlite.hpp>
#include <sqlgen/transpilation/to_insert_or_write.hpp>
namespace test_to_insert_or_replace {
struct TestTable {
std::string field1;
int32_t field2;
sqlgen::Unique<std::string> field3;
sqlgen::PrimaryKey<uint32_t> id;
std::optional<std::string> nullable;
};
TEST(sqlite, test_to_insert_or_replace) {
static_assert(sqlgen::internal::has_constraint_v<TestTable>,
"The table must have a primary key or unique column for "
"insert_or_replace(...) to work.");
const auto insert_stmt =
sqlgen::transpilation::to_insert_or_write<TestTable,
sqlgen::dynamic::Insert>(true);
const auto conn = sqlgen::sqlite::connect().value();
const auto expected =
R"(INSERT INTO "TestTable" ("field1", "field2", "field3", "id", "nullable") VALUES (?, ?, ?, ?, ?) ON CONFLICT (field3, id) DO UPDATE SET field1=excluded.field1, field2=excluded.field2, field3=excluded.field3, id=excluded.id, nullable=excluded.nullable;)";
EXPECT_EQ(conn->to_sql(insert_stmt), expected);
}
} // namespace test_to_insert_or_replace