mirror of
https://github.com/getml/sqlgen.git
synced 2026-02-17 22:28:34 -06:00
Added delete_from
This commit is contained in:
26
README.md
26
README.md
@@ -49,7 +49,7 @@ CREATE TABLE IF NOT EXISTS "People" (
|
||||
"age" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO "Person" ("first_name", "last_name", "age") VALUES (?, ?, ?);
|
||||
INSERT INTO "People" ("first_name", "last_name", "age") VALUES (?, ?, ?);
|
||||
```
|
||||
|
||||
## Retrieving data
|
||||
@@ -87,7 +87,7 @@ The resulting SQL code:
|
||||
|
||||
```sql
|
||||
SELECT "first_name", "last_name", "age"
|
||||
FROM "Person"
|
||||
FROM "People"
|
||||
WHERE "age" < 18
|
||||
ORDER BY "age", "first_name"
|
||||
LIMIT 100;
|
||||
@@ -116,10 +116,10 @@ sqlgen provides input validation to protect against SQL injection.
|
||||
|
||||
```cpp
|
||||
// Safe query function using AlphaNumeric for filtering
|
||||
std::vector<Person> get_people(const auto& conn,
|
||||
std::vector<People> get_people(const auto& conn,
|
||||
const sqlgen::AlphaNumeric& first_name) {
|
||||
using namespace sqlgen;
|
||||
const auto query = sqlgen::read<std::vector<Person>> |
|
||||
const auto query = sqlgen::read<std::vector<People>> |
|
||||
where("first_name"_c == first_name);
|
||||
return query(conn).value();
|
||||
}
|
||||
@@ -132,6 +132,24 @@ Without `AlphaNumeric` validation, this code would be vulnerable to SQL injectio
|
||||
get_people(conn, "Homer' OR '1'='1"); // Attempt to bypass filtering
|
||||
```
|
||||
|
||||
## Deleting data
|
||||
|
||||
```cpp
|
||||
using namespace sqlgen;
|
||||
|
||||
const auto query = delete_from<People> |
|
||||
where("first_name"_c == "Hugo");
|
||||
|
||||
query(conn).value();
|
||||
```
|
||||
|
||||
This generates the following SQL:
|
||||
|
||||
```sql
|
||||
DELETE FROM "People"
|
||||
WHERE "first_name" = 'Hugo';
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
These three libraries are needed for PostgreSQL support:
|
||||
|
||||
102
docs/delete_from.md
Normal file
102
docs/delete_from.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# `sqlgen::delete_from`
|
||||
|
||||
The `sqlgen::delete_from` interface provides a type-safe way to delete records from a SQL database. It supports composable query building with `where` clauses to specify which records should be deleted.
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Delete
|
||||
|
||||
Delete all records from a table:
|
||||
|
||||
```cpp
|
||||
const auto conn = sqlgen::sqlite::connect("database.db");
|
||||
|
||||
sqlgen::delete_from<Person>(conn).value();
|
||||
```
|
||||
|
||||
This generates the following SQL:
|
||||
|
||||
```sql
|
||||
DELETE FROM "Person";
|
||||
```
|
||||
|
||||
Note that `conn` is actually a connection wrapped into an `sqlgen::Result<...>`.
|
||||
This means you can use monadic error handling and fit this into a single line:
|
||||
|
||||
```cpp
|
||||
// sqlgen::Result<Nothing>
|
||||
const auto result = sqlgen::sqlite::connect("database.db").and_then(
|
||||
sqlgen::delete_from<Person>);
|
||||
```
|
||||
|
||||
Please refer to the documentation on `sqlgen::Result<...>` for more information on error handling.
|
||||
|
||||
### With `where` clause
|
||||
|
||||
Delete specific records using a `where` clause:
|
||||
|
||||
```cpp
|
||||
using namespace sqlgen;
|
||||
|
||||
const auto query = delete_from<Person> |
|
||||
where("first_name"_c == "Hugo");
|
||||
|
||||
query(conn).value();
|
||||
```
|
||||
|
||||
This generates the following SQL:
|
||||
|
||||
```sql
|
||||
DELETE FROM "Person"
|
||||
WHERE "first_name" = 'Hugo';
|
||||
```
|
||||
|
||||
Note that `"..."_c` refers to the name of the column. If such a field does not
|
||||
exist on the struct `Person`, the code will fail to compile.
|
||||
|
||||
You can also use monadic error handling here:
|
||||
|
||||
```cpp
|
||||
using namespace sqlgen;
|
||||
|
||||
const auto query = delete_from<Person> |
|
||||
where("first_name"_c == "Hugo");
|
||||
|
||||
// sqlgen::Result<Nothing>
|
||||
const auto result = sqlite::connect("database.db").and_then(query);
|
||||
```
|
||||
|
||||
## Example: Full Query Composition
|
||||
|
||||
```cpp
|
||||
using namespace sqlgen;
|
||||
|
||||
const auto query = delete_from<Person> |
|
||||
where("age"_c >= 18 and "last_name"_c == "Simpson");
|
||||
|
||||
const auto result = query(conn).value();
|
||||
```
|
||||
|
||||
This generates the following SQL:
|
||||
|
||||
```sql
|
||||
DELETE FROM "Person"
|
||||
WHERE ("age" >= 18) AND ("last_name" = 'Simpson');
|
||||
```
|
||||
|
||||
It is strongly recommended that you use `using namespace sqlgen`. However,
|
||||
if you do not want to do that, you can rewrite the example above as follows:
|
||||
|
||||
```cpp
|
||||
const auto query = sqlgen::delete_from<Person> |
|
||||
sqlgen::where(sqlgen::col<"age"> >= 18 and sqlgen::col<"last_name"> == "Simpson");
|
||||
|
||||
const auto result = query(conn).value();
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- The `where` clause is optional - if omitted, all records will be deleted
|
||||
- The `Result<Nothing>` type provides error handling; use `.value()` to extract the result (will throw an exception if there's an error) or handle errors as needed or refer to the documentation on results for other forms of error handling.
|
||||
- `"..."_c` refers to the name of the column
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "sqlgen/Result.hpp"
|
||||
#include "sqlgen/Varchar.hpp"
|
||||
#include "sqlgen/col.hpp"
|
||||
#include "sqlgen/delete_from.hpp"
|
||||
#include "sqlgen/limit.hpp"
|
||||
#include "sqlgen/order_by.hpp"
|
||||
#include "sqlgen/patterns.hpp"
|
||||
|
||||
47
include/sqlgen/delete_from.hpp
Normal file
47
include/sqlgen/delete_from.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#ifndef SQLGEN_DELETE_FROM_HPP_
|
||||
#define SQLGEN_DELETE_FROM_HPP_
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "Connection.hpp"
|
||||
#include "Ref.hpp"
|
||||
#include "Result.hpp"
|
||||
#include "transpilation/to_delete_from.hpp"
|
||||
|
||||
namespace sqlgen {
|
||||
|
||||
template <class ValueType, class WhereType>
|
||||
Result<Nothing> delete_from_impl(const Ref<Connection>& _conn,
|
||||
const WhereType& _where) {
|
||||
const auto query =
|
||||
transpilation::to_delete_from<ValueType, WhereType>(_where);
|
||||
return _conn->execute(_conn->to_sql(query));
|
||||
}
|
||||
|
||||
template <class ValueType, class WhereType>
|
||||
Result<Nothing> delete_from_impl(const Result<Ref<Connection>>& _res,
|
||||
const WhereType& _where) {
|
||||
return _res.and_then([&](const auto& _conn) {
|
||||
return delete_from_impl<ValueType, WhereType>(_conn, _where);
|
||||
});
|
||||
}
|
||||
|
||||
template <class ValueType, class WhereType = Nothing>
|
||||
struct DeleteFrom {
|
||||
Result<Nothing> operator()(const auto& _conn) const noexcept {
|
||||
try {
|
||||
return delete_from_impl<ValueType, WhereType>(_conn, where_);
|
||||
} catch (std::exception& e) {
|
||||
return error(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
WhereType where_;
|
||||
};
|
||||
|
||||
template <class ContainerType>
|
||||
const auto delete_from = DeleteFrom<ContainerType>{};
|
||||
|
||||
} // namespace sqlgen
|
||||
|
||||
#endif
|
||||
18
include/sqlgen/dynamic/DeleteFrom.hpp
Normal file
18
include/sqlgen/dynamic/DeleteFrom.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef SQLGEN_DYNAMIC_DELETEFROM_HPP_
|
||||
#define SQLGEN_DYNAMIC_DELETEFROM_HPP_
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "Condition.hpp"
|
||||
#include "Table.hpp"
|
||||
|
||||
namespace sqlgen::dynamic {
|
||||
|
||||
struct DeleteFrom {
|
||||
Table table;
|
||||
std::optional<Condition> where = std::nullopt;
|
||||
};
|
||||
|
||||
} // namespace sqlgen::dynamic
|
||||
|
||||
#endif
|
||||
@@ -4,12 +4,14 @@
|
||||
#include <rfl.hpp>
|
||||
|
||||
#include "CreateTable.hpp"
|
||||
#include "DeleteFrom.hpp"
|
||||
#include "Insert.hpp"
|
||||
#include "SelectFrom.hpp"
|
||||
|
||||
namespace sqlgen::dynamic {
|
||||
|
||||
using Statement = rfl::TaggedUnion<"stmt", CreateTable, Insert, SelectFrom>;
|
||||
using Statement =
|
||||
rfl::TaggedUnion<"stmt", CreateTable, DeleteFrom, Insert, SelectFrom>;
|
||||
|
||||
} // namespace sqlgen::dynamic
|
||||
|
||||
|
||||
30
include/sqlgen/transpilation/to_delete_from.hpp
Normal file
30
include/sqlgen/transpilation/to_delete_from.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef SQLGEN_TRANSPILATION_TO_DELETE_FROM_HPP_
|
||||
#define SQLGEN_TRANSPILATION_TO_DELETE_FROM_HPP_
|
||||
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "../Result.hpp"
|
||||
#include "../dynamic/DeleteFrom.hpp"
|
||||
#include "../dynamic/Table.hpp"
|
||||
#include "get_schema.hpp"
|
||||
#include "get_tablename.hpp"
|
||||
#include "to_condition.hpp"
|
||||
|
||||
namespace sqlgen::transpilation {
|
||||
|
||||
template <class T, class WhereType>
|
||||
requires std::is_class_v<std::remove_cvref_t<T>> &&
|
||||
std::is_aggregate_v<std::remove_cvref_t<T>>
|
||||
dynamic::DeleteFrom to_delete_from(const WhereType& _where) {
|
||||
return dynamic::DeleteFrom{
|
||||
.table =
|
||||
dynamic::Table{.name = get_tablename<T>(), .schema = get_schema<T>()},
|
||||
.where = to_condition<std::remove_cvref_t<T>>(_where)};
|
||||
}
|
||||
|
||||
} // namespace sqlgen::transpilation
|
||||
|
||||
#endif
|
||||
@@ -5,9 +5,11 @@
|
||||
|
||||
#include "../CreateTable.hpp"
|
||||
#include "../Insert.hpp"
|
||||
#include "../delete_from.hpp"
|
||||
#include "../dynamic/Statement.hpp"
|
||||
#include "../read.hpp"
|
||||
#include "to_create_table.hpp"
|
||||
#include "to_delete_from.hpp"
|
||||
#include "to_insert.hpp"
|
||||
#include "to_select_from.hpp"
|
||||
#include "value_t.hpp"
|
||||
@@ -24,6 +26,13 @@ struct ToSQL<CreateTable<T>> {
|
||||
}
|
||||
};
|
||||
|
||||
template <class T, class WhereType>
|
||||
struct ToSQL<DeleteFrom<T, WhereType>> {
|
||||
dynamic::Statement operator()(const auto& _delete_from) const {
|
||||
return to_delete_from<T>(_delete_from.where_);
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct ToSQL<Insert<T>> {
|
||||
dynamic::Statement operator()(const auto&) const { return to_insert<T>(); }
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <type_traits>
|
||||
|
||||
#include "Result.hpp"
|
||||
#include "delete_from.hpp"
|
||||
#include "read.hpp"
|
||||
#include "transpilation/Limit.hpp"
|
||||
#include "transpilation/value_t.hpp"
|
||||
@@ -15,6 +16,15 @@ struct Where {
|
||||
ConditionType condition;
|
||||
};
|
||||
|
||||
template <class ValueType, class WhereType, class ConditionType>
|
||||
auto operator|(const DeleteFrom<ValueType, WhereType>& _d,
|
||||
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 DeleteFrom<ValueType, ConditionType>{.where_ = _where.condition};
|
||||
}
|
||||
|
||||
template <class ContainerType, class WhereType, class OrderByType,
|
||||
class LimitType, class ConditionType>
|
||||
auto operator|(const Read<ContainerType, WhereType, OrderByType, LimitType>& _r,
|
||||
|
||||
@@ -24,6 +24,8 @@ std::string column_to_sql_definition(const dynamic::Column& _col) noexcept;
|
||||
|
||||
std::string create_table_to_sql(const dynamic::CreateTable& _stmt) noexcept;
|
||||
|
||||
std::string delete_from_to_sql(const dynamic::DeleteFrom& _stmt) noexcept;
|
||||
|
||||
std::vector<std::string> get_primary_keys(
|
||||
const dynamic::CreateTable& _stmt) noexcept;
|
||||
|
||||
@@ -159,6 +161,25 @@ std::string create_table_to_sql(const dynamic::CreateTable& _stmt) noexcept {
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string delete_from_to_sql(const dynamic::DeleteFrom& _stmt) noexcept {
|
||||
std::stringstream stream;
|
||||
|
||||
stream << "DELETE FROM ";
|
||||
|
||||
if (_stmt.table.schema) {
|
||||
stream << wrap_in_quotes(*_stmt.table.schema) << ".";
|
||||
}
|
||||
stream << wrap_in_quotes(_stmt.table.name);
|
||||
|
||||
if (_stmt.where) {
|
||||
stream << " WHERE " << condition_to_sql(*_stmt.where);
|
||||
}
|
||||
|
||||
stream << ";";
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::vector<std::string> get_primary_keys(
|
||||
const dynamic::CreateTable& _stmt) noexcept {
|
||||
using namespace std::ranges::views;
|
||||
@@ -228,10 +249,16 @@ std::string to_sql_impl(const dynamic::Statement& _stmt) noexcept {
|
||||
using S = std::remove_cvref_t<decltype(_s)>;
|
||||
if constexpr (std::is_same_v<S, dynamic::CreateTable>) {
|
||||
return create_table_to_sql(_s);
|
||||
|
||||
} else if constexpr (std::is_same_v<S, dynamic::DeleteFrom>) {
|
||||
return delete_from_to_sql(_s);
|
||||
|
||||
} else if constexpr (std::is_same_v<S, dynamic::Insert>) {
|
||||
return insert_to_sql(_s);
|
||||
|
||||
} else if constexpr (std::is_same_v<S, dynamic::SelectFrom>) {
|
||||
return select_from_to_sql(_s);
|
||||
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<S>, "Unsupported type.");
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept;
|
||||
|
||||
std::string create_table_to_sql(const dynamic::CreateTable& _stmt) noexcept;
|
||||
|
||||
std::string delete_from_to_sql(const dynamic::DeleteFrom& _stmt) noexcept;
|
||||
|
||||
std::string insert_to_sql(const dynamic::Insert& _stmt) noexcept;
|
||||
|
||||
std::string properties_to_sql(const dynamic::types::Properties& _p) noexcept;
|
||||
@@ -131,6 +133,25 @@ std::string create_table_to_sql(const dynamic::CreateTable& _stmt) noexcept {
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string delete_from_to_sql(const dynamic::DeleteFrom& _stmt) noexcept {
|
||||
std::stringstream stream;
|
||||
|
||||
stream << "DELETE FROM ";
|
||||
|
||||
if (_stmt.table.schema) {
|
||||
stream << "\"" << *_stmt.table.schema << "\".";
|
||||
}
|
||||
stream << "\"" << _stmt.table.name << "\"";
|
||||
|
||||
if (_stmt.where) {
|
||||
stream << " WHERE " << condition_to_sql(*_stmt.where);
|
||||
}
|
||||
|
||||
stream << ";";
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string insert_to_sql(const dynamic::Insert& _stmt) noexcept {
|
||||
using namespace std::ranges::views;
|
||||
|
||||
@@ -215,10 +236,16 @@ std::string to_sql_impl(const dynamic::Statement& _stmt) noexcept {
|
||||
using S = std::remove_cvref_t<decltype(_s)>;
|
||||
if constexpr (std::is_same_v<S, dynamic::CreateTable>) {
|
||||
return create_table_to_sql(_s);
|
||||
|
||||
} else if constexpr (std::is_same_v<S, dynamic::DeleteFrom>) {
|
||||
return delete_from_to_sql(_s);
|
||||
|
||||
} else if constexpr (std::is_same_v<S, dynamic::Insert>) {
|
||||
return insert_to_sql(_s);
|
||||
|
||||
} else if constexpr (std::is_same_v<S, dynamic::SelectFrom>) {
|
||||
return select_from_to_sql(_s);
|
||||
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<S>, "Unsupported type.");
|
||||
}
|
||||
|
||||
25
tests/postgres/test_delete_from_dry.cpp
Normal file
25
tests/postgres/test_delete_from_dry.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/postgres.hpp>
|
||||
#include <sqlgen/transpilation/to_select_from.hpp>
|
||||
|
||||
namespace test_delete_from_dry {
|
||||
|
||||
struct TestTable {
|
||||
std::string field1;
|
||||
int32_t field2;
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::optional<std::string> nullable;
|
||||
};
|
||||
|
||||
TEST(postgres, test_delete_from_dry) {
|
||||
using namespace sqlgen;
|
||||
|
||||
const auto query = delete_from<TestTable> | where("field2"_c > 0);
|
||||
|
||||
const auto expected = R"(DELETE FROM "TestTable" WHERE "field2" > 0;)";
|
||||
|
||||
EXPECT_EQ(sqlgen::postgres::to_sql(query), expected);
|
||||
}
|
||||
} // namespace test_delete_from_dry
|
||||
47
tests/sqlite/test_delete_from.cpp
Normal file
47
tests/sqlite/test_delete_from.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/sqlite.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_delete_from {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(sqlite, test_delete_from) {
|
||||
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 = delete_from<Person> | where("first_name"_c == "Hugo");
|
||||
|
||||
query(conn).value();
|
||||
|
||||
const auto people2 = sqlgen::read<std::vector<Person>>(conn).value();
|
||||
|
||||
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}])";
|
||||
|
||||
EXPECT_EQ(rfl::json::write(people2), expected);
|
||||
}
|
||||
|
||||
} // namespace test_delete_from
|
||||
Reference in New Issue
Block a user