diff --git a/README.md b/README.md index 3b1bb5c..8a159fe 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ sqlgen is closely integrated with our sister project [reflect-cpp](https://githu ## Documentation -Click [here](docs/README.md). +Click [here](docs). ## Inserting data @@ -141,6 +141,16 @@ Without `AlphaNumeric` validation, this code would be vulnerable to SQL injectio get_people(conn, "Homer' OR '1'='1"); // Attempt to bypass filtering ``` +## Dropping a table + +```cpp +using namespace sqlgen; + +const auto query = drop | if_exists; + +query(conn).value(); +``` + ## Deleting data ```cpp diff --git a/docs/delete_from.md b/docs/delete_from.md index 2e85380..942f79c 100644 --- a/docs/delete_from.md +++ b/docs/delete_from.md @@ -74,7 +74,11 @@ using namespace sqlgen; const auto query = delete_from | where("age"_c >= 18 and "last_name"_c == "Simpson"); -const auto result = query(conn).value(); +const auto result = query(conn); + +if (!result) { + // Error handling +} ``` This generates the following SQL: @@ -91,12 +95,12 @@ if you do not want to do that, you can rewrite the example above as follows: const auto query = sqlgen::delete_from | sqlgen::where(sqlgen::col<"age"> >= 18 and sqlgen::col<"last_name"> == "Simpson"); -const auto result = query(conn).value(); +const auto result = query(conn); ``` ## Notes - The `where` clause is optional - if omitted, all records will be deleted -- The `Result` type provides error handling; use `.value()` to extract the result (will throw an exception if there's an error) or refer to the documentation on results for other forms of error handling. +- The `Result` 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 `sqlgen::Result<...>` for other forms of error handling. - `"..."_c` refers to the name of the column diff --git a/include/sqlgen.hpp b/include/sqlgen.hpp index 5be6a8f..923ed6f 100644 --- a/include/sqlgen.hpp +++ b/include/sqlgen.hpp @@ -16,6 +16,8 @@ #include "sqlgen/Varchar.hpp" #include "sqlgen/col.hpp" #include "sqlgen/delete_from.hpp" +#include "sqlgen/drop.hpp" +#include "sqlgen/if_exists.hpp" #include "sqlgen/limit.hpp" #include "sqlgen/order_by.hpp" #include "sqlgen/patterns.hpp" diff --git a/include/sqlgen/drop.hpp b/include/sqlgen/drop.hpp new file mode 100644 index 0000000..c1231e1 --- /dev/null +++ b/include/sqlgen/drop.hpp @@ -0,0 +1,45 @@ +#ifndef SQLGEN_DROP_HPP_ +#define SQLGEN_DROP_HPP_ + +#include + +#include "Connection.hpp" +#include "Ref.hpp" +#include "Result.hpp" +#include "transpilation/to_drop.hpp" + +namespace sqlgen { + +template +Result drop_impl(const Ref& _conn, const bool _if_exists) { + const auto query = transpilation::to_drop(_if_exists); + return _conn->execute(_conn->to_sql(query)); +} + +template +Result drop_impl(const Result>& _res, + const bool _if_exists) { + return _res.and_then([&](const auto& _conn) { + return drop_impl(_conn, _if_exists); + }); +} + +template +struct Drop { + Result operator()(const auto& _conn) const noexcept { + try { + return drop_impl(_conn, if_exists_); + } catch (std::exception& e) { + return error(e.what()); + } + } + + bool if_exists_ = false; +}; + +template +const auto drop = Drop{}; + +} // namespace sqlgen + +#endif diff --git a/include/sqlgen/dynamic/Drop.hpp b/include/sqlgen/dynamic/Drop.hpp new file mode 100644 index 0000000..fa810f8 --- /dev/null +++ b/include/sqlgen/dynamic/Drop.hpp @@ -0,0 +1,17 @@ +#ifndef SQLGEN_DYNAMIC_DROP_HPP_ +#define SQLGEN_DYNAMIC_DROP_HPP_ + +#include + +#include "Table.hpp" + +namespace sqlgen::dynamic { + +struct Drop { + bool if_exists = true; + Table table; +}; + +} // namespace sqlgen::dynamic + +#endif diff --git a/include/sqlgen/dynamic/Statement.hpp b/include/sqlgen/dynamic/Statement.hpp index b217272..4ada452 100644 --- a/include/sqlgen/dynamic/Statement.hpp +++ b/include/sqlgen/dynamic/Statement.hpp @@ -5,13 +5,14 @@ #include "CreateTable.hpp" #include "DeleteFrom.hpp" +#include "Drop.hpp" #include "Insert.hpp" #include "SelectFrom.hpp" namespace sqlgen::dynamic { using Statement = - rfl::TaggedUnion<"stmt", CreateTable, DeleteFrom, Insert, SelectFrom>; + rfl::TaggedUnion<"stmt", CreateTable, DeleteFrom, Drop, Insert, SelectFrom>; } // namespace sqlgen::dynamic diff --git a/include/sqlgen/if_exists.hpp b/include/sqlgen/if_exists.hpp new file mode 100644 index 0000000..db7478d --- /dev/null +++ b/include/sqlgen/if_exists.hpp @@ -0,0 +1,21 @@ +#ifndef SQLGEN_IF_EXISTS_HPP_ +#define SQLGEN_IF_EXISTS_HPP_ + +#include "drop.hpp" + +namespace sqlgen { + +struct IfExists {}; + +template +auto operator|(const OtherType& _o, const IfExists&) { + auto o = _o; + o.if_exists_ = true; + return o; +} + +inline const auto if_exists = IfExists{}; + +} // namespace sqlgen + +#endif diff --git a/include/sqlgen/transpilation/to_drop.hpp b/include/sqlgen/transpilation/to_drop.hpp new file mode 100644 index 0000000..600a92c --- /dev/null +++ b/include/sqlgen/transpilation/to_drop.hpp @@ -0,0 +1,29 @@ +#ifndef SQLGEN_TRANSPILATION_TO_DROP_HPP_ +#define SQLGEN_TRANSPILATION_TO_DROP_HPP_ + +#include +#include +#include +#include + +#include "../Result.hpp" +#include "../dynamic/Drop.hpp" +#include "../dynamic/Table.hpp" +#include "get_schema.hpp" +#include "get_tablename.hpp" +#include "to_condition.hpp" + +namespace sqlgen::transpilation { + +template + requires std::is_class_v> && + std::is_aggregate_v> +dynamic::Drop to_drop(const bool _if_exists) { + return dynamic::Drop{.if_exists = _if_exists, + .table = dynamic::Table{.name = get_tablename(), + .schema = get_schema()}}; +} + +} // namespace sqlgen::transpilation + +#endif diff --git a/include/sqlgen/transpilation/to_sql.hpp b/include/sqlgen/transpilation/to_sql.hpp index 34d7936..6855a55 100644 --- a/include/sqlgen/transpilation/to_sql.hpp +++ b/include/sqlgen/transpilation/to_sql.hpp @@ -6,10 +6,12 @@ #include "../CreateTable.hpp" #include "../Insert.hpp" #include "../delete_from.hpp" +#include "../drop.hpp" #include "../dynamic/Statement.hpp" #include "../read.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 "value_t.hpp" @@ -33,6 +35,13 @@ struct ToSQL> { } }; +template +struct ToSQL> { + dynamic::Statement operator()(const auto& _drop) const { + return to_drop(_drop.if_exists_); + } +}; + template struct ToSQL> { dynamic::Statement operator()(const auto&) const { return to_insert(); } diff --git a/src/sqlgen/postgres/to_sql.cpp b/src/sqlgen/postgres/to_sql.cpp index 7e657e2..ecbab73 100644 --- a/src/sqlgen/postgres/to_sql.cpp +++ b/src/sqlgen/postgres/to_sql.cpp @@ -26,6 +26,8 @@ std::string create_table_to_sql(const dynamic::CreateTable& _stmt) noexcept; std::string delete_from_to_sql(const dynamic::DeleteFrom& _stmt) noexcept; +std::string drop_to_sql(const dynamic::Drop& _stmt) noexcept; + std::vector get_primary_keys( const dynamic::CreateTable& _stmt) noexcept; @@ -180,6 +182,25 @@ std::string delete_from_to_sql(const dynamic::DeleteFrom& _stmt) noexcept { return stream.str(); } +std::string drop_to_sql(const dynamic::Drop& _stmt) noexcept { + std::stringstream stream; + + stream << "DROP TABLE "; + + if (_stmt.if_exists) { + stream << "IF EXISTS "; + } + + if (_stmt.table.schema) { + stream << wrap_in_quotes(*_stmt.table.schema) << "."; + } + stream << wrap_in_quotes(_stmt.table.name); + + stream << ";"; + + return stream.str(); +} + std::vector get_primary_keys( const dynamic::CreateTable& _stmt) noexcept { using namespace std::ranges::views; @@ -253,6 +274,9 @@ std::string to_sql_impl(const dynamic::Statement& _stmt) noexcept { } else if constexpr (std::is_same_v) { return delete_from_to_sql(_s); + } else if constexpr (std::is_same_v) { + return drop_to_sql(_s); + } else if constexpr (std::is_same_v) { return insert_to_sql(_s); diff --git a/src/sqlgen/sqlite/to_sql.cpp b/src/sqlgen/sqlite/to_sql.cpp index e24d190..cc4eed6 100644 --- a/src/sqlgen/sqlite/to_sql.cpp +++ b/src/sqlgen/sqlite/to_sql.cpp @@ -22,6 +22,8 @@ std::string create_table_to_sql(const dynamic::CreateTable& _stmt) noexcept; std::string delete_from_to_sql(const dynamic::DeleteFrom& _stmt) noexcept; +std::string drop_to_sql(const dynamic::Drop& _stmt) noexcept; + std::string insert_to_sql(const dynamic::Insert& _stmt) noexcept; std::string properties_to_sql(const dynamic::types::Properties& _p) noexcept; @@ -152,6 +154,25 @@ std::string delete_from_to_sql(const dynamic::DeleteFrom& _stmt) noexcept { return stream.str(); } +std::string drop_to_sql(const dynamic::Drop& _stmt) noexcept { + std::stringstream stream; + + stream << "DROP TABLE "; + + if (_stmt.if_exists) { + stream << "IF EXISTS "; + } + + if (_stmt.table.schema) { + stream << "\"" << *_stmt.table.schema << "\"."; + } + stream << "\"" << _stmt.table.name << "\""; + + stream << ";"; + + return stream.str(); +} + std::string insert_to_sql(const dynamic::Insert& _stmt) noexcept { using namespace std::ranges::views; @@ -240,6 +261,9 @@ std::string to_sql_impl(const dynamic::Statement& _stmt) noexcept { } else if constexpr (std::is_same_v) { return delete_from_to_sql(_s); + } else if constexpr (std::is_same_v) { + return drop_to_sql(_s); + } else if constexpr (std::is_same_v) { return insert_to_sql(_s); diff --git a/tests/postgres/test_drop_dry.cpp b/tests/postgres/test_drop_dry.cpp new file mode 100644 index 0000000..cec7609 --- /dev/null +++ b/tests/postgres/test_drop_dry.cpp @@ -0,0 +1,25 @@ +#include + +#include +#include +#include + +namespace test_drop_dry { + +struct TestTable { + std::string field1; + int32_t field2; + sqlgen::PrimaryKey id; + std::optional nullable; +}; + +TEST(postgres, test_drop_dry) { + using namespace sqlgen; + + const auto query = drop | if_exists; + + const auto expected = R"(DROP TABLE IF EXISTS "TestTable";)"; + + EXPECT_EQ(sqlgen::postgres::to_sql(query), expected); +} +} // namespace test_drop_dry diff --git a/tests/sqlite/test_drop.cpp b/tests/sqlite/test_drop.cpp new file mode 100644 index 0000000..74585e3 --- /dev/null +++ b/tests/sqlite/test_drop.cpp @@ -0,0 +1,40 @@ +#include + +#include +#include +#include +#include +#include + +namespace test_drop { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + int age; +}; + +TEST(sqlite, test_drop) { + const auto people1 = std::vector( + {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 = drop | if_exists; + + query(conn).value(); +} + +} // namespace test_drop