mirror of
https://github.com/getml/sqlgen.git
synced 2025-12-31 14:39:31 -06:00
Added update
This commit is contained in:
@@ -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"
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
27
include/sqlgen/dynamic/Update.hpp
Normal file
27
include/sqlgen/dynamic/Update.hpp
Normal 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
|
||||
15
include/sqlgen/transpilation/Set.hpp
Normal file
15
include/sqlgen/transpilation/Set.hpp
Normal 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
|
||||
69
include/sqlgen/transpilation/to_sets.hpp
Normal file
69
include/sqlgen/transpilation/to_sets.hpp
Normal 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
|
||||
@@ -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);
|
||||
|
||||
31
include/sqlgen/transpilation/to_update.hpp
Normal file
31
include/sqlgen/transpilation/to_update.hpp
Normal 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
54
include/sqlgen/update.hpp
Normal 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
|
||||
@@ -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};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
27
tests/postgres/test_update_dry.cpp
Normal file
27
tests/postgres/test_update_dry.cpp
Normal 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
|
||||
51
tests/sqlite/test_update.cpp
Normal file
51
tests/sqlite/test_update.cpp
Normal 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
|
||||
Reference in New Issue
Block a user