From 7d527a4c64cf79e0778fef4543f0bd66cb7dc087 Mon Sep 17 00:00:00 2001 From: hos-b Date: Thu, 21 Aug 2025 09:24:56 +0200 Subject: [PATCH 1/7] add Enum type to sqlgen::dynamic::types --- include/sqlgen/dynamic/Type.hpp | 2 +- include/sqlgen/dynamic/types.hpp | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/include/sqlgen/dynamic/Type.hpp b/include/sqlgen/dynamic/Type.hpp index 7180993..c70c091 100644 --- a/include/sqlgen/dynamic/Type.hpp +++ b/include/sqlgen/dynamic/Type.hpp @@ -14,7 +14,7 @@ using Type = types::Int32, types::Int64, types::JSON, types::UInt8, types::UInt16, types::UInt32, types::UInt64, types::Text, types::Date, types::Timestamp, types::TimestampWithTZ, - types::VarChar>; + types::VarChar, types::Enum>; } // namespace sqlgen::dynamic diff --git a/include/sqlgen/dynamic/types.hpp b/include/sqlgen/dynamic/types.hpp index ca7aba7..ec9133f 100644 --- a/include/sqlgen/dynamic/types.hpp +++ b/include/sqlgen/dynamic/types.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace sqlgen::dynamic::types { @@ -78,6 +79,12 @@ struct UInt64 { Properties properties; }; +struct Enum { + std::string name; + std::vector values; + Properties properties; +}; + struct Text { Properties properties; }; From 1731d86611d5d23324ec94682f474228d59279ec Mon Sep 17 00:00:00 2001 From: hos-b Date: Thu, 21 Aug 2025 09:25:39 +0200 Subject: [PATCH 2/7] implement Enum creation for postgres --- src/sqlgen/postgres/to_sql.cpp | 51 +++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/sqlgen/postgres/to_sql.cpp b/src/sqlgen/postgres/to_sql.cpp index 95cab03..5595d93 100644 --- a/src/sqlgen/postgres/to_sql.cpp +++ b/src/sqlgen/postgres/to_sql.cpp @@ -42,6 +42,9 @@ std::string field_to_str(const dynamic::SelectFrom::Field& _field) noexcept; std::vector get_primary_keys( const dynamic::CreateTable& _stmt) noexcept; +std::vector>> get_enum_types( + const dynamic::CreateTable& _stmt) noexcept; + std::string insert_to_sql(const dynamic::Insert& _stmt) noexcept; std::string join_to_sql(const dynamic::Join& _stmt) noexcept; @@ -66,10 +69,26 @@ std::string write_to_sql(const dynamic::Write& _stmt) noexcept; inline std::string get_name(const dynamic::Column& _col) { return _col.name; } +inline std::pair> get_enum_mapping( + const dynamic::Column& _col) { + return _col.type.visit( + [&](const auto& _t) -> std::pair> { + using T = std::remove_cvref_t; + if constexpr (std::is_same_v) { + return {type_to_sql(_t), _t.values}; + } + return {}; + }); +} + inline std::string wrap_in_quotes(const std::string& _name) noexcept { return "\"" + _name + "\""; } +inline std::string wrap_in_single_quotes(const std::string& _name) noexcept { + return "'" + _name + "'"; +} + // ---------------------------------------------------------------------------- std::string aggregation_to_sql( @@ -254,6 +273,21 @@ std::string create_table_to_sql(const dynamic::CreateTable& _stmt) noexcept { }; std::stringstream stream; + + for (const auto& [enum_name, enum_values] : get_enum_types(_stmt)) { + if (_stmt.if_not_exists) { + stream << "DO $$ BEGIN "; + } + stream << "CREATE TYPE " << enum_name << " AS ENUM (" + << internal::strings::join( + ", ", internal::collect::vector( + enum_values | transform(wrap_in_single_quotes))) + << "); "; + if (_stmt.if_not_exists) { + stream << "EXCEPTION WHEN duplicate_object THEN NULL; END $$;"; + } + } + stream << "CREATE TABLE "; if (_stmt.if_not_exists) { @@ -385,6 +419,20 @@ std::vector get_primary_keys( transform(wrap_in_quotes)); } +std::vector>> get_enum_types( + const dynamic::CreateTable& _stmt) noexcept { + using namespace std::ranges::views; + + const auto is_enum = [](const dynamic::Column& _col) -> bool { + return _col.type.visit([&](const auto& _t) -> bool { + using T = std::remove_cvref_t; + return std::is_same_v; + }); + }; + return internal::collect::vector(_stmt.columns | filter(is_enum) | + transform(get_enum_mapping)); +} + std::string insert_to_sql(const dynamic::Insert& _stmt) noexcept { using namespace std::ranges::views; @@ -751,7 +799,8 @@ std::string type_to_sql(const dynamic::Type& _type) noexcept { } else if constexpr (std::is_same_v || std::is_same_v) { return "BIGINT"; - + } else if constexpr (std::is_same_v) { + return _t.name; } else if constexpr (std::is_same_v || std::is_same_v) { return "NUMERIC"; From a4e983d002712306dcb444a0e84486ad221449e5 Mon Sep 17 00:00:00 2001 From: hos-b Date: Sat, 13 Sep 2025 09:12:18 +0000 Subject: [PATCH 3/7] implement Enum creation for sqlite --- src/sqlgen/sqlite/to_sql.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sqlgen/sqlite/to_sql.cpp b/src/sqlgen/sqlite/to_sql.cpp index 472c902..4802233 100644 --- a/src/sqlgen/sqlite/to_sql.cpp +++ b/src/sqlgen/sqlite/to_sql.cpp @@ -769,7 +769,8 @@ std::string type_to_sql(const dynamic::Type& _type) noexcept { } else if constexpr (std::is_same_v) { return _t.type_name; - + } else if constexpr (std::is_same_v) { + return "TEXT"; } else { static_assert(rfl::always_false_v, "Not all cases were covered."); } From eee56228e2792ba7938cef3254cde451cc6a46cc Mon Sep 17 00:00:00 2001 From: hos-b Date: Sat, 13 Sep 2025 09:22:20 -0900 Subject: [PATCH 4/7] implement Enum creation for mysql --- src/sqlgen/mysql/to_sql.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/sqlgen/mysql/to_sql.cpp b/src/sqlgen/mysql/to_sql.cpp index 8236ae5..6a390e6 100644 --- a/src/sqlgen/mysql/to_sql.cpp +++ b/src/sqlgen/mysql/to_sql.cpp @@ -82,6 +82,10 @@ inline std::string wrap_in_quotes(const std::string& _name) noexcept { return "`" + _name + "`"; } +inline std::string wrap_in_single_quotes(const std::string& _name) noexcept { + return "'" + _name + "'"; +} + // ---------------------------------------------------------------------------- std::string aggregation_to_sql( @@ -154,6 +158,8 @@ std::string cast_type_to_sql(const dynamic::Type& _type) noexcept { } else if constexpr (std::is_same_v) { return "CHAR"; + } else if constexpr (std::is_same_v) { + return "ENUM"; } else { static_assert(rfl::always_false_v, "Not all cases were covered."); } @@ -872,6 +878,14 @@ std::string type_to_sql(const dynamic::Type& _type) noexcept { std::is_same_v) { return "BIGINT"; + } else if constexpr (std::is_same_v) { + return "ENUM(" + + internal::strings::join( + ", ", internal::collect::vector(_t.values | + std::ranges::views::transform( + wrap_in_single_quotes))) + + ")"; + } else if constexpr (std::is_same_v || std::is_same_v) { return "DECIMAL"; From 250874ff6234b045cf96fe89965e4d63169d16b3 Mon Sep 17 00:00:00 2001 From: hos-b Date: Sat, 13 Sep 2025 09:22:32 -0900 Subject: [PATCH 5/7] add enum handlers to Parser --- include/sqlgen/parsing/Parser_default.hpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/include/sqlgen/parsing/Parser_default.hpp b/include/sqlgen/parsing/Parser_default.hpp index 88fbfd8..5f1f877 100644 --- a/include/sqlgen/parsing/Parser_default.hpp +++ b/include/sqlgen/parsing/Parser_default.hpp @@ -33,8 +33,14 @@ struct Parser { return static_cast(std::stod(*_str)); } else if constexpr (std::is_integral_v) { return static_cast(std::stoll(*_str)); - } else if (std::is_same_v) { + } else if constexpr (std::is_same_v) { return std::stoi(*_str) != 0; + } else if constexpr (std::is_enum_v) { + if (auto res = rfl::string_to_enum(*_str)) { + return Type{*res}; + } else { + return error(res.error()); + } } else { static_assert(rfl::always_false_v, "Unsupported type"); } @@ -49,6 +55,8 @@ struct Parser { if constexpr (transpilation::has_reflection_method) { return Parser>::write( _t.reflection()); + } else if constexpr (std::is_enum_v) { + return rfl::enum_to_string(_t); } else { return std::to_string(_t); } @@ -108,6 +116,17 @@ struct Parser { "Unsupported floating point value."); } + } else if constexpr (std::is_enum_v) { + constexpr auto values = rfl::get_enumerator_array(); + std::array enum_names{}; + for (std::size_t i = 0; i < std::size(values); ++i) { + enum_names[i] = values[i].first; + } + constexpr std::string_view type_name = + rfl::internal::get_type_name_str_view(); + return sqlgen::dynamic::types::Enum{ + std::string(type_name), + std::vector(enum_names.begin(), enum_names.end())}; } else { static_assert(rfl::always_false_v, "Unsupported type."); } From f61a3d9fc03fc60c4c46d140ce7716f15e7f5456 Mon Sep 17 00:00:00 2001 From: hos-b Date: Sun, 14 Sep 2025 01:28:09 -0900 Subject: [PATCH 6/7] add docs for enum types --- docs/README.md | 1 + docs/enum.md | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 docs/enum.md diff --git a/docs/README.md b/docs/README.md index d4fad81..bbdea6d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -38,6 +38,7 @@ Welcome to the sqlgen documentation. This guide provides detailed information ab - [Type Conversion Operations](type_conversion_operations.md) - How to convert between types safely in queries (e.g., cast int to double). - [Null Handling Operations](null_handling_operations.md) - How to handle nullable values and propagate nullability correctly (e.g., with coalesce and nullability rules). - [Timestamp and Date/Time Functions](timestamp_operations.md) - How to work with timestamps, dates, and times (e.g., extract parts, perform arithmetic, convert formats). +- [Enums](enum.md) - How to work with enums sqlgen ## Data Types and Validation diff --git a/docs/enum.md b/docs/enum.md new file mode 100644 index 0000000..82b1ce0 --- /dev/null +++ b/docs/enum.md @@ -0,0 +1,36 @@ +# `Enums` + +Enums can directly be used in sqlgen structs. They are mapped to native enum types in postgres and MySQL. In sqlite, which does not support native enum types, they are stored as `TEXT` by default. + +## Usage + +### Basic Definition + +```cpp +enum class Color : int { RED = 1, GREEN = 2, BLUE = 3 }; +struct Flower { + sqlgen::PrimaryKey name; + Color color; +} + +const auto red_rose = Flower{ + .name = "Rose", + .color = Color::RED, +}; +``` + +This generates the following SQL schema: +```sql +CREATE TYPE IF NOT EXISTS "Color" AS ENUM ('RED', 'GREEN', 'BLUE'); +CREATE TABLE IF NOT EXISTS "Flower"( + "name" TEXT NOT NULL, + "color" Color NOT NULL, + PRIMARY_KEY("name") +); +``` + +## Notes +- Enums are specific types in postgres. They are only created once and shared between tables. +- In MySQL, enums are stored as native types but they are not shared between tables. +- In sqlite, enums are stored as `TEXT` by default. If you need to use integers, you can specialize the `Parser` struct as explained [here](dynamic.md). +- You can use `rfl::enum_to_string` and `rfl::string_to_enum` to convert between enum values and their string representations. From d8a4541ca9b7374010bb4e4936966f9f9aeb762c Mon Sep 17 00:00:00 2001 From: hos-b Date: Sun, 14 Sep 2025 01:28:38 -0900 Subject: [PATCH 7/7] switch vcpkg url to https --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 53e4058..a0a57f3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "vcpkg"] path = vcpkg - url = git@github.com:microsoft/vcpkg.git + url = https://github.com/microsoft/vcpkg.git