Added update

This commit is contained in:
Dr. Patrick Urbanke
2025-05-11 05:15:04 +02:00
parent dcbda14301
commit 9d6cac0502
14 changed files with 384 additions and 2 deletions

View File

@@ -22,6 +22,7 @@
#include "sqlgen/order_by.hpp"
#include "sqlgen/patterns.hpp"
#include "sqlgen/read.hpp"
#include "sqlgen/update.hpp"
#include "sqlgen/where.hpp"
#include "sqlgen/write.hpp"

View File

@@ -6,6 +6,7 @@
#include "transpilation/Condition.hpp"
#include "transpilation/Desc.hpp"
#include "transpilation/Set.hpp"
#include "transpilation/Value.hpp"
#include "transpilation/conditions.hpp"
@@ -20,6 +21,17 @@ struct Col {
/// Returns the column name.
std::string name() const noexcept { return Name().str(); }
/// Defines a SET clause in an UPDATE statement.
template <class T>
auto set(const T& _to) const noexcept {
return transpilation::Set<Col<_name>, std::remove_cvref_t<T>>{.to = _to};
}
/// Defines a SET clause in an UPDATE statement.
auto set(const char* _to) const noexcept {
return transpilation::Set<Col<_name>, std::string>{.to = _to};
}
};
template <rfl::internal::StringLiteral _name>

View File

@@ -8,11 +8,12 @@
#include "Drop.hpp"
#include "Insert.hpp"
#include "SelectFrom.hpp"
#include "Update.hpp"
namespace sqlgen::dynamic {
using Statement =
rfl::TaggedUnion<"stmt", CreateTable, DeleteFrom, Drop, Insert, SelectFrom>;
using Statement = rfl::TaggedUnion<"stmt", CreateTable, DeleteFrom, Drop,
Insert, SelectFrom, Update>;
} // namespace sqlgen::dynamic

View File

@@ -0,0 +1,27 @@
#ifndef SQLGEN_DYNAMIC_UPDATE_HPP_
#define SQLGEN_DYNAMIC_UPDATE_HPP_
#include <optional>
#include <vector>
#include "Column.hpp"
#include "ColumnOrValue.hpp"
#include "Condition.hpp"
#include "Table.hpp"
namespace sqlgen::dynamic {
struct Update {
struct Set {
Column col;
ColumnOrValue to;
};
Table table;
std::vector<Set> sets;
std::optional<Condition> where;
};
} // namespace sqlgen::dynamic
#endif

View File

@@ -0,0 +1,15 @@
#ifndef SQLGEN_TRANSPILATION_SET_HPP_
#define SQLGEN_TRANSPILATION_SET_HPP_
namespace sqlgen::transpilation {
/// Defines the SET clause in an UPDATE statement.
template <class _ColType, class T>
struct Set {
using ColType = _ColType;
T to;
};
} // namespace sqlgen::transpilation
#endif

View File

@@ -0,0 +1,69 @@
#ifndef SQLGEN_TRANSPILATION_TO_SETS_HPP_
#define SQLGEN_TRANSPILATION_TO_SETS_HPP_
#include <rfl.hpp>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "../Result.hpp"
#include "../col.hpp"
#include "../dynamic/Table.hpp"
#include "../dynamic/Update.hpp"
#include "Set.hpp"
#include "all_columns_exist.hpp"
#include "get_schema.hpp"
#include "get_tablename.hpp"
#include "to_condition.hpp"
#include "to_sets.hpp"
#include "to_value.hpp"
namespace sqlgen::transpilation {
template <class T, class SetType>
struct ToSet;
template <class T, rfl::internal::StringLiteral _name, class ToType>
struct ToSet<T, Set<Col<_name>, ToType>> {
static_assert(all_columns_exist<T, Col<_name>>(), "All columns must exist.");
dynamic::Update::Set operator()(const auto& _set) const {
return dynamic::Update::Set{
.col = dynamic::Column{.name = _name.str()},
.to = to_value(_set.to),
};
}
};
template <class T, rfl::internal::StringLiteral _name1,
rfl::internal::StringLiteral _name2>
struct ToSet<T, Set<Col<_name1>, Col<_name2>>> {
static_assert(all_columns_exist<T, Col<_name1>>(), "All columns must exist.");
static_assert(all_columns_exist<T, Col<_name2>>(), "All columns must exist.");
dynamic::Update::Set operator()(const auto& _set) const {
return dynamic::Update::Set{
.col = dynamic::Column{.name = _name1.str()},
.to = dynamic::Column{.name = _name2.str()},
};
}
};
template <class T, class SetType>
dynamic::Update::Set to_set(const SetType& _set) {
return ToSet<std::remove_cvref_t<T>, std::remove_cvref_t<SetType>>{}(_set);
}
template <class T, class SetsType>
std::vector<dynamic::Update::Set> to_sets(const SetsType& _sets) {
return rfl::apply(
[](const auto&... _s) {
return std::vector<dynamic::Update::Set>({to_set<T>(_s)...});
},
_sets);
}
} // namespace sqlgen::transpilation
#endif

View File

@@ -9,11 +9,13 @@
#include "../drop.hpp"
#include "../dynamic/Statement.hpp"
#include "../read.hpp"
#include "../update.hpp"
#include "to_create_table.hpp"
#include "to_delete_from.hpp"
#include "to_drop.hpp"
#include "to_insert.hpp"
#include "to_select_from.hpp"
#include "to_update.hpp"
#include "value_t.hpp"
namespace sqlgen::transpilation {
@@ -56,6 +58,13 @@ struct ToSQL<Read<ContainerType, WhereType, OrderByType, LimitType>> {
}
};
template <class T, class SetsType, class WhereType>
struct ToSQL<Update<T, SetsType, WhereType>> {
dynamic::Statement operator()(const auto& _update) const {
return to_update<T, SetsType, WhereType>(_update.sets_, _update.where_);
}
};
template <class T>
dynamic::Statement to_sql(const T& _t) {
return ToSQL<std::remove_cvref_t<T>>{}(_t);

View File

@@ -0,0 +1,31 @@
#ifndef SQLGEN_TRANSPILATION_TO_UPDATE_HPP_
#define SQLGEN_TRANSPILATION_TO_UPDATE_HPP_
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "../Result.hpp"
#include "../dynamic/Table.hpp"
#include "../dynamic/Update.hpp"
#include "get_schema.hpp"
#include "get_tablename.hpp"
#include "to_condition.hpp"
#include "to_sets.hpp"
namespace sqlgen::transpilation {
template <class T, class SetsType, class WhereType>
requires std::is_class_v<std::remove_cvref_t<T>> &&
std::is_aggregate_v<std::remove_cvref_t<T>>
dynamic::Update to_update(const SetsType& _sets, const WhereType& _where) {
return dynamic::Update{.table = dynamic::Table{.name = get_tablename<T>(),
.schema = get_schema<T>()},
.sets = to_sets<T>(_sets),
.where = to_condition<std::remove_cvref_t<T>>(_where)};
}
} // namespace sqlgen::transpilation
#endif

54
include/sqlgen/update.hpp Normal file
View File

@@ -0,0 +1,54 @@
#ifndef SQLGEN_UPDATE_HPP_
#define SQLGEN_UPDATE_HPP_
#include <rfl.hpp>
#include <type_traits>
#include "Connection.hpp"
#include "Ref.hpp"
#include "Result.hpp"
#include "transpilation/to_update.hpp"
namespace sqlgen {
template <class ValueType, class SetsType, class WhereType>
Result<Nothing> update_impl(const Ref<Connection>& _conn, const SetsType& _sets,
const WhereType& _where) {
const auto query =
transpilation::to_update<ValueType, SetsType, WhereType>(_sets, _where);
return _conn->execute(_conn->to_sql(query));
}
template <class ValueType, class SetsType, class WhereType>
Result<Nothing> update_impl(const Result<Ref<Connection>>& _res,
const SetsType& _sets, const WhereType& _where) {
return _res.and_then([&](const auto& _conn) {
return update_impl<ValueType, SetsType, WhereType>(_conn, _sets, _where);
});
}
template <class ValueType, class SetsType, class WhereType = Nothing>
struct Update {
Result<Nothing> operator()(const auto& _conn) const noexcept {
try {
return update_impl<ValueType, SetsType, WhereType>(_conn, sets_, where_);
} catch (std::exception& e) {
return error(e.what());
}
}
SetsType sets_;
WhereType where_;
};
template <class ValueType, class... SetsType>
inline auto update(const SetsType&... _sets) {
static_assert(sizeof...(_sets) > 0, "You must update at least one column.");
using TupleType = rfl::Tuple<std::remove_cvref_t<SetsType>...>;
return Update<ValueType, TupleType>{.sets_ = TupleType(_sets...)};
};
} // namespace sqlgen
#endif

View File

@@ -8,6 +8,7 @@
#include "read.hpp"
#include "transpilation/Limit.hpp"
#include "transpilation/value_t.hpp"
#include "update.hpp"
namespace sqlgen {
@@ -40,6 +41,16 @@ auto operator|(const Read<ContainerType, WhereType, OrderByType, LimitType>& _r,
.where_ = _where.condition};
}
template <class ValueType, class SetsType, class WhereType, class ConditionType>
auto operator|(const Update<ValueType, SetsType, WhereType>& _u,
const Where<ConditionType>& _where) {
static_assert(std::is_same_v<WhereType, Nothing>,
"You cannot call where(...) twice (but you can apply more "
"than one condition by combining them with && or ||).");
return Update<ValueType, SetsType, ConditionType>{.sets_ = _u.sets_,
.where_ = _where.condition};
}
template <class ConditionType>
inline auto where(const ConditionType& _cond) {
return Where<std::remove_cvref_t<ConditionType>>{.condition = _cond};

View File

@@ -37,6 +37,8 @@ std::string select_from_to_sql(const dynamic::SelectFrom& _stmt) noexcept;
std::string type_to_sql(const dynamic::Type& _type) noexcept;
std::string update_to_sql(const dynamic::Update& _stmt) noexcept;
// ----------------------------------------------------------------------------
inline std::string get_name(const dynamic::Column& _col) { return _col.name; }
@@ -268,6 +270,7 @@ std::string select_from_to_sql(const dynamic::SelectFrom& _stmt) noexcept {
std::string to_sql_impl(const dynamic::Statement& _stmt) noexcept {
return _stmt.visit([&](const auto& _s) -> std::string {
using S = std::remove_cvref_t<decltype(_s)>;
if constexpr (std::is_same_v<S, dynamic::CreateTable>) {
return create_table_to_sql(_s);
@@ -283,6 +286,9 @@ std::string to_sql_impl(const dynamic::Statement& _stmt) noexcept {
} else if constexpr (std::is_same_v<S, dynamic::SelectFrom>) {
return select_from_to_sql(_s);
} else if constexpr (std::is_same_v<S, dynamic::Update>) {
return update_to_sql(_s);
} else {
static_assert(rfl::always_false_v<S>, "Unsupported type.");
}
@@ -325,4 +331,35 @@ std::string type_to_sql(const dynamic::Type& _type) noexcept {
});
}
std::string update_to_sql(const dynamic::Update& _stmt) noexcept {
using namespace std::ranges::views;
const auto to_str = [](const auto& _set) -> std::string {
return wrap_in_quotes(_set.col.name) + " = " +
column_or_value_to_sql(_set.to);
};
std::stringstream stream;
stream << "UPDATE ";
if (_stmt.table.schema) {
stream << wrap_in_quotes(*_stmt.table.schema) << ".";
}
stream << wrap_in_quotes(_stmt.table.name);
stream << " SET ";
stream << internal::strings::join(
", ", internal::collect::vector(_stmt.sets | transform(to_str)));
if (_stmt.where) {
stream << " WHERE " << condition_to_sql(*_stmt.where);
}
stream << ";";
return stream.str();
}
} // namespace sqlgen::postgres

View File

@@ -32,6 +32,10 @@ std::string select_from_to_sql(const dynamic::SelectFrom& _stmt) noexcept;
std::string type_to_sql(const dynamic::Type& _type) noexcept;
std::string update_to_sql(const dynamic::Update& _stmt) noexcept;
// ----------------------------------------------------------------------------
std::string column_or_value_to_sql(
const dynamic::ColumnOrValue& _col) noexcept {
const auto handle_value = [](const auto& _v) -> std::string {
@@ -270,6 +274,9 @@ std::string to_sql_impl(const dynamic::Statement& _stmt) noexcept {
} else if constexpr (std::is_same_v<S, dynamic::SelectFrom>) {
return select_from_to_sql(_s);
} else if constexpr (std::is_same_v<S, dynamic::Update>) {
return update_to_sql(_s);
} else {
static_assert(rfl::always_false_v<S>, "Unsupported type.");
}
@@ -304,4 +311,34 @@ std::string type_to_sql(const dynamic::Type& _type) noexcept {
});
}
std::string update_to_sql(const dynamic::Update& _stmt) noexcept {
using namespace std::ranges::views;
const auto to_str = [](const auto& _set) -> std::string {
return "\"" + _set.col.name + "\" = " + column_or_value_to_sql(_set.to);
};
std::stringstream stream;
stream << "UPDATE ";
if (_stmt.table.schema) {
stream << "\"" << *_stmt.table.schema << "\".";
}
stream << "\"" << _stmt.table.name << "\"";
stream << " SET ";
stream << internal::strings::join(
", ", internal::collect::vector(_stmt.sets | transform(to_str)));
if (_stmt.where) {
stream << " WHERE " << condition_to_sql(*_stmt.where);
}
stream << ";";
return stream.str();
}
} // namespace sqlgen::sqlite

View File

@@ -0,0 +1,27 @@
#include <gtest/gtest.h>
#include <sqlgen.hpp>
#include <sqlgen/postgres.hpp>
namespace test_update_dry {
struct TestTable {
std::string field1;
int32_t field2;
sqlgen::PrimaryKey<uint32_t> id;
std::optional<std::string> nullable;
};
TEST(postgres, test_update_dry) {
using namespace sqlgen;
const auto query =
update<TestTable>("field1"_c.set("Hello"), "nullable"_c.set("field1"_c)) |
where("field2"_c > 0);
const auto expected =
R"(UPDATE "TestTable" SET "field1" = 'Hello', "nullable" = "field1" WHERE "field2" > 0;)";
EXPECT_EQ(sqlgen::postgres::to_sql(query), expected);
}
} // namespace test_update_dry

View File

@@ -0,0 +1,51 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/sqlite.hpp>
#include <vector>
namespace test_update {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(sqlite, test_update) {
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},
Person{
.id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}});
const auto conn = sqlgen::sqlite::connect();
sqlgen::write(conn, people1);
using namespace sqlgen;
const auto query =
update<Person>("first_name"_c.set("last_name"_c), "age"_c.set(100)) |
where("first_name"_c == "Hugo");
query(conn).value();
const auto people2 = sqlgen::read<std::vector<Person>>(conn).value();
std::cout << rfl::json::write(people2) << std::endl;
const std::string expected =
R"([{"id":0,"first_name":"Homer","last_name":"Simpson","age":45},{"id":1,"first_name":"Bart","last_name":"Simpson","age":10},{"id":2,"first_name":"Lisa","last_name":"Simpson","age":8},{"id":3,"first_name":"Maggie","last_name":"Simpson","age":0},{"id":4,"first_name":"Simpson","last_name":"Simpson","age":100}])";
EXPECT_EQ(rfl::json::write(people2), expected);
}
} // namespace test_update