mirror of
https://github.com/getml/sqlgen.git
synced 2026-04-28 03:01:02 -05:00
Implement insert_or_replace (#45)
Co-authored-by: Dr. Patrick Urbanke <patrick@getml.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
name: linux-cxx20-conan
|
||||
|
||||
on: [push]
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: linux-cxx20-vcpkg
|
||||
|
||||
on: [push]
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: macos-cxx20-conan
|
||||
|
||||
on: [push]
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
macos-clang:
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
name: windows-cxx20-vcpkg
|
||||
|
||||
on: [push]
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
|
||||
+53
-2
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user