Add enum support (#49)

This commit is contained in:
hosein
2025-09-21 09:00:15 +02:00
committed by GitHub
parent 26869c83f1
commit 7a617b0828
19 changed files with 923 additions and 6 deletions

2
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "vcpkg"]
path = vcpkg
url = git@github.com:microsoft/vcpkg.git
url = https://github.com/microsoft/vcpkg.git

View File

@@ -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
@@ -61,4 +62,4 @@ Welcome to the sqlgen documentation. This guide provides detailed information ab
- [PostgreSQL](postgres.md) - How to interact with PostgreSQL and compatible databases (Redshift, Aurora, Greenplum, CockroachDB, ...)
- [SQLite](sqlite.md) - How to interact with SQLite3
For installation instructions, quick start guide, and usage examples, please refer to the [main README](../README.md).
For installation instructions, quick start guide, and usage examples, please refer to the [main README](../README.md).

37
docs/enum.md Normal file
View File

@@ -0,0 +1,37 @@
# `Enums`
Enums can directly be used in sqlgen structs. They are mapped to native enum types in PostgreSQL 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
- Due to naming restrictions in PostgreSQL, the namespace operator `::` is replaced with `__`. Thus, an enum defined as `namespace1::some_struct::MyEnum` will be created in the database as `namespace1__some_struct__MyEnum`. This mangled name can at most be 63 characters long.
- Enums are specific types in PostgreSQL. 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.

View File

@@ -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

View File

@@ -4,6 +4,7 @@
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
namespace sqlgen::dynamic::types {
@@ -78,6 +79,12 @@ struct UInt64 {
Properties properties;
};
struct Enum {
std::string name;
std::vector<std::string> values;
Properties properties;
};
struct Text {
Properties properties;
};

View File

@@ -1,6 +1,7 @@
#ifndef SQLGEN_PARSING_PARSER_DEFAULT_HPP_
#define SQLGEN_PARSING_PARSER_DEFAULT_HPP_
#include <ranges>
#include <rfl.hpp>
#include <string>
#include <type_traits>
@@ -33,8 +34,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");
}
@@ -49,6 +56,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);
}
@@ -108,6 +117,22 @@ 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 auto org_name = rfl::internal::get_type_name_str_view<T>();
static_assert(org_name.size() < 64,
"Enum type exceeds type level. If it's defined in a nested "
"namespace, consider moving it up to a higher level.");
// Transform '::' to '__' to avoid postgres limitations
std::string trf_name(org_name);
std::ranges::replace(trf_name, ':', '_');
return sqlgen::dynamic::types::Enum{
std::move(trf_name),
std::vector<std::string>(enum_names.begin(), enum_names.end())};
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}

View File

@@ -25,6 +25,9 @@ dynamic::Value to_value(const T& _t) {
} else if constexpr (has_reflection_method<Type>) {
return to_value(_t.reflection());
} else if constexpr (std::is_enum_v<Type>) {
return dynamic::Value{dynamic::String{.val = rfl::enum_to_string(_t)}};
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type");
}

View File

@@ -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<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.");
}
@@ -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";

View File

@@ -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;
@@ -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(
@@ -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<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;
@@ -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";

View File

@@ -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.");
}

View File

@@ -0,0 +1,107 @@
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/mysql.hpp>
#include <vector>
namespace test_enum_cross_table {
enum class AccessRestriction { PUBLIC = 1, INTERNAL = 2, CONFIDENTIAL = 3 };
struct Employee {
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
std::string first_name;
std::string last_name;
AccessRestriction access_level;
};
struct Document {
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
AccessRestriction min_access_level;
std::string name;
std::string path;
};
TEST(mysql, test_enum_cross_table) {
using namespace sqlgen;
using namespace sqlgen::literals;
auto employees = std::vector<Employee>({
Employee{.first_name = "Homer",
.last_name = "Simpson",
.access_level = AccessRestriction::PUBLIC},
Employee{.first_name = "Waylon",
.last_name = "Smithers",
.access_level = AccessRestriction::INTERNAL},
Employee{.first_name = "Montgomery",
.last_name = "Burns",
.access_level = AccessRestriction::CONFIDENTIAL},
});
auto documents = std::vector<Document>({
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Power Plant Safety Manual",
.path = "/documents/powerplant/safety_manual.txt"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Staff Memo",
.path = "/documents/powerplant/staff_memo.txt"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "Operations Report",
.path = "/documents/powerplant/operations_report.pdf"},
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Project Plan",
.path = "/documents/powerplant/project_plan.md"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Budget Q1",
.path = "/documents/powerplant/budget_q1.pdf"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "HR Policies",
.path = "/documents/powerplant/hr_policies.pdf"},
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Team Photo",
.path = "/documents/powerplant/team.jpg"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "Executive Summary",
.path = "/documents/powerplant/executive_summary.docx"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Release Notes",
.path = "/documents/powerplant/release_notes.txt"},
});
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
.user = "sqlgen",
.password = "password",
.dbname = "mysql"};
const auto conn = mysql::connect(credentials);
conn.and_then(drop<Employee> | if_exists)
.and_then(drop<Document> | if_exists);
write(conn, employees);
write(conn, documents);
const auto smithers = conn.and_then(sqlgen::read<Employee> |
where("last_name"_c == "Smithers" and
"first_name"_c == "Waylon"))
.value();
const auto smithers_level = smithers.access_level;
const auto smithers_documents =
conn.and_then(sqlgen::read<std::vector<Document>> |
where("min_access_level"_c == smithers_level ||
"min_access_level"_c == AccessRestriction::PUBLIC) |
order_by("name"_c))
.value();
const auto expected_ids = std::set<uint32_t>{1, 2, 4, 5, 7, 9};
std::set<uint32_t> actual_ids;
for (const auto &d : smithers_documents) {
actual_ids.emplace(d.id());
}
EXPECT_EQ(expected_ids, actual_ids);
}
} // namespace test_enum_cross_table
#endif

View File

@@ -0,0 +1,91 @@
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/mysql.hpp>
#include <vector>
namespace test_enum_lookup {
enum class AccessRestriction { PUBLIC = 1, INTERNAL = 2, CONFIDENTIAL = 3 };
struct Document {
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
AccessRestriction min_access_level;
std::string name;
std::string path;
};
TEST(mysql, test_enum_lookup) {
auto documents = std::vector<Document>({
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Power Plant Safety Manual",
.path = "/documents/powerplant/safety_manual.txt"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Staff Memo",
.path = "/documents/powerplant/staff_memo.txt"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "Operations Report",
.path = "/documents/powerplant/operations_report.pdf"},
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Project Plan",
.path = "/documents/powerplant/project_plan.md"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Budget Q1",
.path = "/documents/powerplant/budget_q1.pdf"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "HR Policies",
.path = "/documents/powerplant/hr_policies.pdf"},
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Team Photo",
.path = "/documents/powerplant/team.jpg"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "Executive Summary",
.path = "/documents/powerplant/executive_summary.docx"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Release Notes",
.path = "/documents/powerplant/release_notes.txt"},
});
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
.user = "sqlgen",
.password = "password",
.dbname = "mysql"};
using namespace sqlgen;
using namespace sqlgen::literals;
const auto public_documents =
mysql::connect(credentials)
.and_then(drop<Document> | if_exists)
.and_then(write(std::ref(documents)))
.and_then(sqlgen::read<std::vector<Document>> |
where("min_access_level"_c == AccessRestriction::PUBLIC) |
order_by("name"_c.desc()))
.value();
const auto expected = std::vector<Document>({
Document{.id = 7,
.min_access_level = AccessRestriction::PUBLIC,
.name = "Team Photo",
.path = "/documents/powerplant/team.jpg"},
Document{.id = 4,
.min_access_level = AccessRestriction::PUBLIC,
.name = "Project Plan",
.path = "/documents/powerplant/project_plan.md"},
Document{.id = 1,
.min_access_level = AccessRestriction::PUBLIC,
.name = "Power Plant Safety Manual",
.path = "/documents/powerplant/safety_manual.txt"},
});
const auto json1 = rfl::json::write(expected);
const auto json2 = rfl::json::write(public_documents);
EXPECT_EQ(json1, json2);
}
} // namespace test_enum_lookup
#endif

View File

@@ -0,0 +1,66 @@
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/mysql.hpp>
#include <vector>
namespace test_enum_namespace {
namespace first {
enum class IdenticallyNamed { VALUE0, VALUE1, VALUE2 };
}
namespace second {
enum class IdenticallyNamed { VALUE3, VALUE4, VALUE5 };
}
struct MultiStruct {
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
first::IdenticallyNamed enum_one;
second::IdenticallyNamed enum_two;
};
TEST(mysql, test_enum_namespace) {
using namespace sqlgen;
using namespace sqlgen::literals;
auto objects = std::vector<MultiStruct>({
MultiStruct{.enum_one = first::IdenticallyNamed::VALUE0,
.enum_two = second::IdenticallyNamed::VALUE3},
MultiStruct{.enum_one = first::IdenticallyNamed::VALUE1,
.enum_two = second::IdenticallyNamed::VALUE4},
MultiStruct{.enum_one = first::IdenticallyNamed::VALUE2,
.enum_two = second::IdenticallyNamed::VALUE5},
});
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
.user = "sqlgen",
.password = "password",
.dbname = "mysql"};
const auto conn = mysql::connect(credentials);
conn.and_then(drop<MultiStruct> | if_exists);
write(conn, objects);
const auto read_objects =
sqlgen::read<std::vector<MultiStruct>>(conn).value();
std::vector<uint32_t> actual_ids;
for (const auto& obj : read_objects) {
if (obj.enum_one == first::IdenticallyNamed::VALUE0) {
EXPECT_EQ(obj.enum_two, second::IdenticallyNamed::VALUE3);
} else if (obj.enum_one == first::IdenticallyNamed::VALUE1) {
EXPECT_EQ(obj.enum_two, second::IdenticallyNamed::VALUE4);
} else if (obj.enum_one == first::IdenticallyNamed::VALUE2) {
EXPECT_EQ(obj.enum_two, second::IdenticallyNamed::VALUE5);
} else {
FAIL() << "Unexpected enum value";
}
}
}
} // namespace test_enum_namespace
#endif

View File

@@ -0,0 +1,108 @@
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/postgres.hpp>
#include <vector>
namespace test_enum_cross_table {
enum class AccessRestriction { PUBLIC = 1, INTERNAL = 2, CONFIDENTIAL = 3 };
struct Employee {
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
std::string first_name;
std::string last_name;
AccessRestriction access_level;
};
struct Document {
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
AccessRestriction min_access_level;
std::string name;
std::string path;
};
TEST(postgres, test_enum_cross_table) {
using namespace sqlgen;
using namespace sqlgen::literals;
auto employees = std::vector<Employee>({
Employee{.first_name = "Homer",
.last_name = "Simpson",
.access_level = AccessRestriction::PUBLIC},
Employee{.first_name = "Waylon",
.last_name = "Smithers",
.access_level = AccessRestriction::INTERNAL},
Employee{.first_name = "Montgomery",
.last_name = "Burns",
.access_level = AccessRestriction::CONFIDENTIAL},
});
auto documents = std::vector<Document>({
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Power Plant Safety Manual",
.path = "/documents/powerplant/safety_manual.txt"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Staff Memo",
.path = "/documents/powerplant/staff_memo.txt"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "Operations Report",
.path = "/documents/powerplant/operations_report.pdf"},
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Project Plan",
.path = "/documents/powerplant/project_plan.md"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Budget Q1",
.path = "/documents/powerplant/budget_q1.pdf"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "HR Policies",
.path = "/documents/powerplant/hr_policies.pdf"},
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Team Photo",
.path = "/documents/powerplant/team.jpg"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "Executive Summary",
.path = "/documents/powerplant/executive_summary.docx"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Release Notes",
.path = "/documents/powerplant/release_notes.txt"},
});
const auto credentials = sqlgen::postgres::Credentials{.user = "postgres",
.password = "password",
.host = "localhost",
.dbname = "postgres"};
const auto conn = postgres::connect(credentials);
conn.and_then(drop<Employee> | if_exists)
.and_then(drop<Document> | if_exists);
write(conn, employees);
write(conn, documents);
const auto smithers = conn.and_then(sqlgen::read<Employee> |
where("last_name"_c == "Smithers" and
"first_name"_c == "Waylon"))
.value();
// EXPECT_FALSE(smithers.empty());
const auto smithers_level = smithers.access_level;
const auto smithers_documents =
conn.and_then(sqlgen::read<std::vector<Document>> |
where("min_access_level"_c == smithers_level ||
"min_access_level"_c == AccessRestriction::PUBLIC) |
order_by("name"_c))
.value();
const auto expected_ids = std::set<uint32_t>{1, 2, 4, 5, 7, 9};
std::set<uint32_t> actual_ids;
for (const auto &d : smithers_documents) {
actual_ids.emplace(d.id());
}
EXPECT_EQ(expected_ids, actual_ids);
}
} // namespace test_enum_cross_table
#endif

View File

@@ -0,0 +1,91 @@
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/postgres.hpp>
#include <vector>
namespace test_enum_lookup {
enum class AccessRestriction { PUBLIC = 1, INTERNAL = 2, CONFIDENTIAL = 3 };
struct Document {
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
AccessRestriction min_access_level;
std::string name;
std::string path;
};
TEST(postgres, test_enum_lookup) {
auto documents = std::vector<Document>({
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Power Plant Safety Manual",
.path = "/documents/powerplant/safety_manual.txt"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Staff Memo",
.path = "/documents/powerplant/staff_memo.txt"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "Operations Report",
.path = "/documents/powerplant/operations_report.pdf"},
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Project Plan",
.path = "/documents/powerplant/project_plan.md"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Budget Q1",
.path = "/documents/powerplant/budget_q1.pdf"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "HR Policies",
.path = "/documents/powerplant/hr_policies.pdf"},
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Team Photo",
.path = "/documents/powerplant/team.jpg"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "Executive Summary",
.path = "/documents/powerplant/executive_summary.docx"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Release Notes",
.path = "/documents/powerplant/release_notes.txt"},
});
const auto credentials = sqlgen::postgres::Credentials{.user = "postgres",
.password = "password",
.host = "localhost",
.dbname = "postgres"};
using namespace sqlgen;
using namespace sqlgen::literals;
const auto public_documents =
postgres::connect(credentials)
.and_then(drop<Document> | if_exists)
.and_then(write(std::ref(documents)))
.and_then(sqlgen::read<std::vector<Document>> |
where("min_access_level"_c == AccessRestriction::PUBLIC) |
order_by("name"_c.desc()))
.value();
const auto expected = std::vector<Document>({
Document{.id = 7,
.min_access_level = AccessRestriction::PUBLIC,
.name = "Team Photo",
.path = "/documents/powerplant/team.jpg"},
Document{.id = 4,
.min_access_level = AccessRestriction::PUBLIC,
.name = "Project Plan",
.path = "/documents/powerplant/project_plan.md"},
Document{.id = 1,
.min_access_level = AccessRestriction::PUBLIC,
.name = "Power Plant Safety Manual",
.path = "/documents/powerplant/safety_manual.txt"},
});
const auto json1 = rfl::json::write(expected);
const auto json2 = rfl::json::write(public_documents);
EXPECT_EQ(json1, json2);
}
} // namespace test_enum_lookup
#endif

View File

@@ -0,0 +1,66 @@
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/postgres.hpp>
#include <vector>
namespace test_enum_namespace {
namespace first {
enum class IdenticallyNamed { VALUE0, VALUE1, VALUE2 };
}
namespace second {
enum class IdenticallyNamed { VALUE3, VALUE4, VALUE5 };
}
struct MultiStruct {
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
first::IdenticallyNamed enum_one;
second::IdenticallyNamed enum_two;
};
TEST(mysql, test_enum_namespace) {
using namespace sqlgen;
using namespace sqlgen::literals;
auto objects = std::vector<MultiStruct>({
MultiStruct{.enum_one = first::IdenticallyNamed::VALUE0,
.enum_two = second::IdenticallyNamed::VALUE3},
MultiStruct{.enum_one = first::IdenticallyNamed::VALUE1,
.enum_two = second::IdenticallyNamed::VALUE4},
MultiStruct{.enum_one = first::IdenticallyNamed::VALUE2,
.enum_two = second::IdenticallyNamed::VALUE5},
});
const auto credentials = sqlgen::postgres::Credentials{.user = "postgres",
.password = "password",
.host = "localhost",
.dbname = "postgres"};
const auto conn = postgres::connect(credentials);
conn.and_then(drop<MultiStruct> | if_exists);
write(conn, objects);
const auto read_objects =
sqlgen::read<std::vector<MultiStruct>>(conn).value();
std::vector<uint32_t> actual_ids;
for (const auto& obj : read_objects) {
if (obj.enum_one == first::IdenticallyNamed::VALUE0) {
EXPECT_EQ(obj.enum_two, second::IdenticallyNamed::VALUE3);
} else if (obj.enum_one == first::IdenticallyNamed::VALUE1) {
EXPECT_EQ(obj.enum_two, second::IdenticallyNamed::VALUE4);
} else if (obj.enum_one == first::IdenticallyNamed::VALUE2) {
EXPECT_EQ(obj.enum_two, second::IdenticallyNamed::VALUE5);
} else {
FAIL() << "Unexpected enum value";
}
}
}
} // namespace test_enum_namespace
#endif

View File

@@ -0,0 +1,103 @@
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/sqlite.hpp>
#include <vector>
namespace test_enum_cross_table {
enum class AccessRestriction { PUBLIC = 1, INTERNAL = 2, CONFIDENTIAL = 3 };
struct Employee {
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
std::string first_name;
std::string last_name;
AccessRestriction access_level;
};
struct Document {
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
AccessRestriction min_access_level;
std::string name;
std::string path;
};
TEST(sqlite, test_enum_cross_table) {
using namespace sqlgen;
using namespace sqlgen::literals;
auto employees = std::vector<Employee>({
Employee{.first_name = "Homer",
.last_name = "Simpson",
.access_level = AccessRestriction::PUBLIC},
Employee{.first_name = "Waylon",
.last_name = "Smithers",
.access_level = AccessRestriction::INTERNAL},
Employee{.first_name = "Montgomery",
.last_name = "Burns",
.access_level = AccessRestriction::CONFIDENTIAL},
});
auto documents = std::vector<Document>({
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Power Plant Safety Manual",
.path = "/documents/powerplant/safety_manual.txt"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Staff Memo",
.path = "/documents/powerplant/staff_memo.txt"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "Operations Report",
.path = "/documents/powerplant/operations_report.pdf"},
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Project Plan",
.path = "/documents/powerplant/project_plan.md"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Budget Q1",
.path = "/documents/powerplant/budget_q1.pdf"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "HR Policies",
.path = "/documents/powerplant/hr_policies.pdf"},
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Team Photo",
.path = "/documents/powerplant/team.jpg"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "Executive Summary",
.path = "/documents/powerplant/executive_summary.docx"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Release Notes",
.path = "/documents/powerplant/release_notes.txt"},
});
const auto conn = sqlite::connect();
conn.and_then(drop<Employee> | if_exists)
.and_then(drop<Document> | if_exists);
write(conn, employees);
write(conn, documents);
const auto smithers = conn.and_then(sqlgen::read<Employee> |
where("last_name"_c == "Smithers" and
"first_name"_c == "Waylon"))
.value();
const auto smithers_level = smithers.access_level;
const auto smithers_documents =
conn.and_then(sqlgen::read<std::vector<Document>> |
where("min_access_level"_c == smithers_level ||
"min_access_level"_c == AccessRestriction::PUBLIC) |
order_by("name"_c))
.value();
const auto expected_ids = std::set<uint32_t>{1, 2, 4, 5, 7, 9};
std::set<uint32_t> actual_ids;
for (const auto &d : smithers_documents) {
actual_ids.emplace(d.id());
}
EXPECT_EQ(expected_ids, actual_ids);
}
} // namespace test_enum_cross_table
#endif

View File

@@ -0,0 +1,86 @@
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/sqlite.hpp>
#include <vector>
namespace test_enum_lookup {
enum class AccessRestriction { PUBLIC = 1, INTERNAL = 2, CONFIDENTIAL = 3 };
struct Document {
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
AccessRestriction min_access_level;
std::string name;
std::string path;
};
TEST(sqlite, test_enum_lookup) {
auto documents = std::vector<Document>({
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Power Plant Safety Manual",
.path = "/documents/powerplant/safety_manual.txt"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Staff Memo",
.path = "/documents/powerplant/staff_memo.txt"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "Operations Report",
.path = "/documents/powerplant/operations_report.pdf"},
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Project Plan",
.path = "/documents/powerplant/project_plan.md"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Budget Q1",
.path = "/documents/powerplant/budget_q1.pdf"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "HR Policies",
.path = "/documents/powerplant/hr_policies.pdf"},
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Team Photo",
.path = "/documents/powerplant/team.jpg"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "Executive Summary",
.path = "/documents/powerplant/executive_summary.docx"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Release Notes",
.path = "/documents/powerplant/release_notes.txt"},
});
using namespace sqlgen;
using namespace sqlgen::literals;
const auto public_documents =
sqlite::connect()
.and_then(drop<Document> | if_exists)
.and_then(write(std::ref(documents)))
.and_then(sqlgen::read<std::vector<Document>> |
where("min_access_level"_c == AccessRestriction::PUBLIC) |
order_by("name"_c.desc()))
.value();
const auto expected = std::vector<Document>({
Document{.id = 7,
.min_access_level = AccessRestriction::PUBLIC,
.name = "Team Photo",
.path = "/documents/powerplant/team.jpg"},
Document{.id = 4,
.min_access_level = AccessRestriction::PUBLIC,
.name = "Project Plan",
.path = "/documents/powerplant/project_plan.md"},
Document{.id = 1,
.min_access_level = AccessRestriction::PUBLIC,
.name = "Power Plant Safety Manual",
.path = "/documents/powerplant/safety_manual.txt"},
});
const auto json1 = rfl::json::write(expected);
const auto json2 = rfl::json::write(public_documents);
EXPECT_EQ(json1, json2);
}
} // namespace test_enum_lookup
#endif

View File

@@ -0,0 +1,62 @@
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/sqlite.hpp>
#include <vector>
namespace test_enum_namespace {
namespace first {
enum class IdenticallyNamed { VALUE0, VALUE1, VALUE2 };
}
namespace second {
enum class IdenticallyNamed { VALUE3, VALUE4, VALUE5 };
}
struct MultiStruct {
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
first::IdenticallyNamed enum_one;
second::IdenticallyNamed enum_two;
};
TEST(mysql, test_enum_namespace) {
using namespace sqlgen;
using namespace sqlgen::literals;
auto objects = std::vector<MultiStruct>({
MultiStruct{.enum_one = first::IdenticallyNamed::VALUE0,
.enum_two = second::IdenticallyNamed::VALUE3},
MultiStruct{.enum_one = first::IdenticallyNamed::VALUE1,
.enum_two = second::IdenticallyNamed::VALUE4},
MultiStruct{.enum_one = first::IdenticallyNamed::VALUE2,
.enum_two = second::IdenticallyNamed::VALUE5},
});
const auto conn = sqlite::connect();
conn.and_then(drop<MultiStruct> | if_exists);
write(conn, objects);
const auto read_objects =
sqlgen::read<std::vector<MultiStruct>>(conn).value();
std::vector<uint32_t> actual_ids;
for (const auto& obj : read_objects) {
if (obj.enum_one == first::IdenticallyNamed::VALUE0) {
EXPECT_EQ(obj.enum_two, second::IdenticallyNamed::VALUE3);
} else if (obj.enum_one == first::IdenticallyNamed::VALUE1) {
EXPECT_EQ(obj.enum_two, second::IdenticallyNamed::VALUE4);
} else if (obj.enum_one == first::IdenticallyNamed::VALUE2) {
EXPECT_EQ(obj.enum_two, second::IdenticallyNamed::VALUE5);
} else {
FAIL() << "Unexpected enum value";
}
}
}
} // namespace test_enum_namespace
#endif