Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[submodule "vcpkg"]
path = vcpkg
url = git@github.com:microsoft/vcpkg.git
url = https://github.com/microsoft/vcpkg.git
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
36 changes: 36 additions & 0 deletions docs/enum.md
Original file line number Diff line number Diff line change
@@ -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<std::string> 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<T>` 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.
2 changes: 1 addition & 1 deletion include/sqlgen/dynamic/Type.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 7 additions & 0 deletions include/sqlgen/dynamic/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <cstdint>
#include <optional>
#include <string>
#include <vector>

namespace sqlgen::dynamic::types {

Expand Down Expand Up @@ -78,6 +79,12 @@ struct UInt64 {
Properties properties;
};

struct Enum {
std::string name;
std::vector<std::string> values;
Properties properties;
};

struct Text {
Properties properties;
};
Expand Down
21 changes: 20 additions & 1 deletion include/sqlgen/parsing/Parser_default.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,14 @@ struct Parser {
return static_cast<Type>(std::stod(*_str));
} else if constexpr (std::is_integral_v<Type>) {
return static_cast<Type>(std::stoll(*_str));
} else if (std::is_same_v<Type, bool>) {
} else if constexpr (std::is_same_v<Type, bool>) {
return std::stoi(*_str) != 0;
} else if constexpr (std::is_enum_v<Type>) {
if (auto res = rfl::string_to_enum<Type>(*_str)) {
return Type{*res};
} else {
return error(res.error());
}
} else {
static_assert(rfl::always_false_v<Type>, "Unsupported type");
}
Expand All @@ -49,6 +55,8 @@ struct Parser {
if constexpr (transpilation::has_reflection_method<Type>) {
return Parser<std::remove_cvref_t<typename Type::ReflectionType>>::write(
_t.reflection());
} else if constexpr (std::is_enum_v<Type>) {
return rfl::enum_to_string(_t);
} else {
return std::to_string(_t);
}
Expand Down Expand Up @@ -108,6 +116,17 @@ struct Parser {
"Unsupported floating point value.");
}

} else if constexpr (std::is_enum_v<T>) {
constexpr auto values = rfl::get_enumerator_array<T>();
std::array<std::string_view, std::size(values)> 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<T>();
return sqlgen::dynamic::types::Enum{
std::string(type_name),
std::vector<std::string>(enum_names.begin(), enum_names.end())};
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
Expand Down
14 changes: 14 additions & 0 deletions src/sqlgen/mysql/to_sql.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -154,6 +158,8 @@ std::string cast_type_to_sql(const dynamic::Type& _type) noexcept {
} else if constexpr (std::is_same_v<T, dynamic::types::Unknown>) {
return "CHAR";

} else if constexpr (std::is_same_v<T, dynamic::types::Enum>) {
return "ENUM";
} else {
static_assert(rfl::always_false_v<T>, "Not all cases were covered.");
}
Expand Down Expand Up @@ -872,6 +878,14 @@ std::string type_to_sql(const dynamic::Type& _type) noexcept {
std::is_same_v<T, dynamic::types::UInt64>) {
return "BIGINT";

} else if constexpr (std::is_same_v<T, dynamic::types::Enum>) {
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<T, dynamic::types::Float32> ||
std::is_same_v<T, dynamic::types::Float64>) {
return "DECIMAL";
Expand Down
51 changes: 50 additions & 1 deletion src/sqlgen/postgres/to_sql.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ std::string field_to_str(const dynamic::SelectFrom::Field& _field) noexcept;
std::vector<std::string> get_primary_keys(
const dynamic::CreateTable& _stmt) noexcept;

std::vector<std::pair<std::string, std::vector<std::string>>> 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;
Expand All @@ -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<std::string, std::vector<std::string>> get_enum_mapping(
const dynamic::Column& _col) {
return _col.type.visit(
[&](const auto& _t) -> std::pair<std::string, std::vector<std::string>> {
using T = std::remove_cvref_t<decltype(_t)>;
if constexpr (std::is_same_v<T, dynamic::types::Enum>) {
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(
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -385,6 +419,20 @@ std::vector<std::string> get_primary_keys(
transform(wrap_in_quotes));
}

std::vector<std::pair<std::string, std::vector<std::string>>> 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<decltype(_t)>;
return std::is_same_v<T, dynamic::types::Enum>;
});
};
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;

Expand Down Expand Up @@ -751,7 +799,8 @@ std::string type_to_sql(const dynamic::Type& _type) noexcept {
} else if constexpr (std::is_same_v<T, dynamic::types::Int64> ||
std::is_same_v<T, dynamic::types::UInt64>) {
return "BIGINT";

} else if constexpr (std::is_same_v<T, dynamic::types::Enum>) {
return _t.name;
} else if constexpr (std::is_same_v<T, dynamic::types::Float32> ||
std::is_same_v<T, dynamic::types::Float64>) {
return "NUMERIC";
Expand Down
3 changes: 2 additions & 1 deletion src/sqlgen/sqlite/to_sql.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,8 @@ std::string type_to_sql(const dynamic::Type& _type) noexcept {

} else if constexpr (std::is_same_v<T, dynamic::types::Dynamic>) {
return _t.type_name;

} else if constexpr (std::is_same_v<T, dynamic::types::Enum>) {
return "TEXT";
} else {
static_assert(rfl::always_false_v<T>, "Not all cases were covered.");
}
Expand Down
Loading