mirror of
https://github.com/getml/sqlgen.git
synced 2025-12-31 06:30:18 -06:00
Added support for foreign key constraints (#37)
This commit is contained in:
committed by
GitHub
parent
7d9cd0d38f
commit
098deb9477
@@ -39,6 +39,7 @@ Welcome to the sqlgen documentation. This guide provides detailed information ab
|
||||
|
||||
## Data Types and Validation
|
||||
|
||||
- [sqlgen::ForeignKey](foreign_key.md) - How to establish referential integrity between tables
|
||||
- [sqlgen::Pattern](pattern.md) - How to add regex pattern validation to avoid SQL injection
|
||||
- [sqlgen::Timestamp](timestamp.md) - How timestamps work in sqlgen
|
||||
- [sqlgen::Varchar](varchar.md) - How varchars work in sqlgen
|
||||
|
||||
166
docs/foreign_key.md
Normal file
166
docs/foreign_key.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# `sqlgen::ForeignKey`
|
||||
|
||||
`sqlgen::ForeignKey` is used to establish referential integrity between tables by creating foreign key relationships. It ensures that values in one table reference valid values in another table's primary key.
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Definition
|
||||
|
||||
Define a foreign key field in your struct by specifying the type, the referenced table type, and the column name:
|
||||
|
||||
```cpp
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
struct Relationship {
|
||||
sqlgen::ForeignKey<uint32_t, Person, "id"> parent_id;
|
||||
uint32_t child_id;
|
||||
};
|
||||
```
|
||||
|
||||
This generates the following SQL schema:
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS "Person"(
|
||||
"id" INTEGER NOT NULL,
|
||||
"first_name" TEXT NOT NULL,
|
||||
"last_name" TEXT NOT NULL,
|
||||
"age" INTEGER NOT NULL,
|
||||
PRIMARY KEY("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "Relationship"(
|
||||
"parent_id" INTEGER NOT NULL REFERENCES "Person"("id"),
|
||||
"child_id" INTEGER NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
### Template Parameters
|
||||
|
||||
The `sqlgen::ForeignKey` template takes three parameters:
|
||||
|
||||
1. **T**: The type of the foreign key field (must match the referenced column's type)
|
||||
2. **ForeignTableType**: The struct type of the table being referenced
|
||||
3. **col_name**: The name of the column in the referenced table (as a string literal)
|
||||
|
||||
```cpp
|
||||
sqlgen::ForeignKey<Type, ReferencedTable, "column_name"> field_name;
|
||||
```
|
||||
|
||||
**Important**: The referenced column must be a primary key in the foreign table.
|
||||
|
||||
### Type Safety and Validation
|
||||
|
||||
`sqlgen::ForeignKey` provides compile-time validation to ensure:
|
||||
|
||||
1. **Column Existence**: The referenced column must exist in the foreign table
|
||||
2. **Primary Key Constraint**: The referenced column must be a primary key
|
||||
3. **Type Compatibility**: The foreign key type must match the referenced column's type
|
||||
|
||||
```cpp
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct Order {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
// This will compile successfully - "id" exists in Person, is a primary key, and types match
|
||||
sqlgen::ForeignKey<uint32_t, Person, "id"> person_id;
|
||||
|
||||
// This would cause a compile error - type mismatch
|
||||
// sqlgen::ForeignKey<std::string, Person, "id"> person_id;
|
||||
};
|
||||
```
|
||||
|
||||
### Assignment and Access
|
||||
|
||||
Assign values to foreign key fields:
|
||||
|
||||
```cpp
|
||||
const auto relationship = Relationship{
|
||||
.parent_id = 1, // References Person with id = 1
|
||||
.child_id = 2
|
||||
};
|
||||
```
|
||||
|
||||
Access the underlying value using any of these methods:
|
||||
|
||||
```cpp
|
||||
relationship.parent_id();
|
||||
relationship.parent_id.get();
|
||||
relationship.parent_id.value();
|
||||
```
|
||||
|
||||
### Working with Related Data
|
||||
|
||||
Foreign keys enable you to establish relationships between tables and perform joins:
|
||||
|
||||
```cpp
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
};
|
||||
|
||||
struct Relationship {
|
||||
sqlgen::ForeignKey<uint32_t, Person, "id"> parent_id;
|
||||
uint32_t child_id;
|
||||
};
|
||||
|
||||
// Insert parent records
|
||||
auto people = std::vector<Person>({
|
||||
Person{.id = 1, .first_name = "Homer", .last_name = "Simpson"},
|
||||
Person{.id = 2, .first_name = "Marge", .last_name = "Simpson"}
|
||||
});
|
||||
|
||||
// Insert relationship records that reference the parents
|
||||
auto relationships = std::vector<Relationship>({
|
||||
Relationship{.parent_id = 1, .child_id = 3}, // Homer -> child 3
|
||||
Relationship{.parent_id = 1, .child_id = 4}, // Homer -> child 4
|
||||
Relationship{.parent_id = 2, .child_id = 3}, // Marge -> child 3
|
||||
Relationship{.parent_id = 2, .child_id = 4} // Marge -> child 4
|
||||
});
|
||||
|
||||
// Write both tables to the database
|
||||
conn.and_then(create_table<Person> | if_not_exists)
|
||||
.and_then(create_table<Relationship> | if_not_exists)
|
||||
.and_then(insert(std::ref(people)))
|
||||
.and_then(insert(std::ref(relationships)));
|
||||
```
|
||||
|
||||
### Referential Integrity
|
||||
|
||||
Foreign keys enforce referential integrity at the database level:
|
||||
|
||||
- **Insert Validation**: You cannot insert a foreign key value that doesn't exist in the referenced table
|
||||
- **Delete Protection**: You cannot delete a referenced record without handling the foreign key constraints
|
||||
- **Update Consistency**: Updates to referenced primary keys are handled according to the database's foreign key rules
|
||||
|
||||
```cpp
|
||||
// This would fail if Person with id = 999 doesn't exist
|
||||
auto invalid_relationship = Relationship{
|
||||
.parent_id = 999, // This Person doesn't exist
|
||||
.child_id = 1
|
||||
};
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- The template parameters are:
|
||||
- `T`: The type of the foreign key field
|
||||
- `ForeignTableType`: The struct type of the referenced table
|
||||
- `col_name`: The name of the referenced column (string literal)
|
||||
- The class supports:
|
||||
- Direct value assignment
|
||||
- Multiple access methods for the underlying value
|
||||
- Reflection for SQL operations
|
||||
- Move and copy semantics
|
||||
- Compile-time validation of column existence, primary key constraint, and type compatibility
|
||||
- Foreign keys can reference any supported SQL data type
|
||||
- The referenced column must exist in the foreign table, be a primary key, and have a compatible type
|
||||
- Foreign key relationships are enforced at the database level for data integrity
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "sqlgen/ConnectionPool.hpp"
|
||||
#include "sqlgen/Flatten.hpp"
|
||||
#include "sqlgen/ForeignKey.hpp"
|
||||
#include "sqlgen/Iterator.hpp"
|
||||
#include "sqlgen/IteratorBase.hpp"
|
||||
#include "sqlgen/Literal.hpp"
|
||||
@@ -17,6 +18,7 @@
|
||||
#include "sqlgen/aggregations.hpp"
|
||||
#include "sqlgen/as.hpp"
|
||||
#include "sqlgen/begin_transaction.hpp"
|
||||
#include "sqlgen/cascade.hpp"
|
||||
#include "sqlgen/col.hpp"
|
||||
#include "sqlgen/commit.hpp"
|
||||
#include "sqlgen/create_index.hpp"
|
||||
|
||||
113
include/sqlgen/ForeignKey.hpp
Normal file
113
include/sqlgen/ForeignKey.hpp
Normal file
@@ -0,0 +1,113 @@
|
||||
#ifndef SQLGEN_FOREIGN_KEY_HPP_
|
||||
#define SQLGEN_FOREIGN_KEY_HPP_
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <type_traits>
|
||||
|
||||
#include "Literal.hpp"
|
||||
#include "transpilation/Col.hpp"
|
||||
#include "transpilation/all_columns_exist.hpp"
|
||||
#include "transpilation/is_primary_key.hpp"
|
||||
#include "transpilation/remove_reflection_t.hpp"
|
||||
#include "transpilation/underlying_t.hpp"
|
||||
|
||||
namespace sqlgen {
|
||||
|
||||
template <class T, class _ForeignTableType,
|
||||
rfl::internal::StringLiteral _col_name>
|
||||
struct ForeignKey {
|
||||
using ReflectionType = T;
|
||||
using ForeignTableType = _ForeignTableType;
|
||||
using ColumnType = Literal<_col_name>;
|
||||
|
||||
static_assert(transpilation::column_exists_v<
|
||||
_ForeignTableType,
|
||||
typename rfl::named_tuple_t<_ForeignTableType>::Names,
|
||||
transpilation::Col<_col_name>>,
|
||||
"The column referenced to in ForeignKey<...> does not exist on "
|
||||
"the referenced table.");
|
||||
static_assert(
|
||||
std::is_same_v<
|
||||
std::remove_cvref_t<transpilation::remove_reflection_t<T>>,
|
||||
std::remove_cvref_t<
|
||||
transpilation::remove_reflection_t<transpilation::underlying_t<
|
||||
ForeignTableType, transpilation::Col<_col_name>>>>>,
|
||||
"The type of the column and the type of the referenced column must be "
|
||||
"the same.");
|
||||
static_assert(
|
||||
transpilation::is_primary_key_v<
|
||||
std::remove_cvref_t<rfl::field_type_t<_col_name, ForeignTableType>>>,
|
||||
"The referenced column must be a primary key.");
|
||||
|
||||
ForeignKey() : value_(T()) {}
|
||||
|
||||
ForeignKey(const T& _value) : value_(_value) {}
|
||||
|
||||
ForeignKey(ForeignKey&& _other) noexcept = default;
|
||||
|
||||
ForeignKey(const ForeignKey& _other) = default;
|
||||
|
||||
template <class U,
|
||||
typename std::enable_if<std::is_convertible_v<U, ReflectionType>,
|
||||
bool>::type = true>
|
||||
ForeignKey(const U& _value) : value_(_value) {}
|
||||
|
||||
template <class U,
|
||||
typename std::enable_if<std::is_convertible_v<U, ReflectionType>,
|
||||
bool>::type = true>
|
||||
ForeignKey(U&& _value) noexcept : value_(std::forward<U>(_value)) {}
|
||||
|
||||
~ForeignKey() = default;
|
||||
|
||||
/// Returns the underlying object.
|
||||
ReflectionType& get() { return value_; }
|
||||
|
||||
/// Returns the underlying object.
|
||||
const ReflectionType& get() const { return value_; }
|
||||
|
||||
/// Returns the underlying object.
|
||||
ReflectionType& operator()() { return value_; }
|
||||
|
||||
/// Returns the underlying object.
|
||||
const ReflectionType& operator()() const { return value_; }
|
||||
|
||||
/// Assigns the underlying object.
|
||||
auto& operator=(const ReflectionType& _value) {
|
||||
value_ = _value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Assigns the underlying object.
|
||||
template <class U,
|
||||
typename std::enable_if<std::is_convertible_v<U, ReflectionType>,
|
||||
bool>::type = true>
|
||||
auto& operator=(const U& _value) {
|
||||
value_ = _value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Assigns the underlying object.
|
||||
ForeignKey& operator=(const ForeignKey& _other) = default;
|
||||
|
||||
/// Assigns the underlying object.
|
||||
ForeignKey& operator=(ForeignKey&& _other) = default;
|
||||
|
||||
/// Necessary for the automated transpilation to work.
|
||||
const T& reflection() const { return value_; }
|
||||
|
||||
/// Assigns the underlying object.
|
||||
void set(const T& _value) { value_ = _value; }
|
||||
|
||||
/// Returns the underlying object.
|
||||
T& value() { return value_; }
|
||||
|
||||
/// Returns the underlying object.
|
||||
const T& value() const { return value_; }
|
||||
|
||||
/// The underlying value.
|
||||
T value_;
|
||||
};
|
||||
|
||||
} // namespace sqlgen
|
||||
|
||||
#endif
|
||||
19
include/sqlgen/cascade.hpp
Normal file
19
include/sqlgen/cascade.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef SQLGEN_CASCADE_HPP_
|
||||
#define SQLGEN_CASCADE_HPP_
|
||||
|
||||
namespace sqlgen {
|
||||
|
||||
struct Cascade {};
|
||||
|
||||
template <class OtherType>
|
||||
auto operator|(const OtherType& _o, const Cascade&) {
|
||||
auto o = _o;
|
||||
o.cascade_ = true;
|
||||
return o;
|
||||
}
|
||||
|
||||
inline const auto cascade = Cascade{};
|
||||
|
||||
} // namespace sqlgen
|
||||
|
||||
#endif
|
||||
@@ -13,8 +13,8 @@ namespace sqlgen {
|
||||
template <class ValueType, class Connection>
|
||||
requires is_connection<Connection>
|
||||
Result<Ref<Connection>> drop_impl(const Ref<Connection>& _conn,
|
||||
const bool _if_exists) {
|
||||
const auto query = transpilation::to_drop<ValueType>(_if_exists);
|
||||
const bool _if_exists, const bool _cascade) {
|
||||
const auto query = transpilation::to_drop<ValueType>(_if_exists, _cascade);
|
||||
return _conn->execute(_conn->to_sql(query)).transform([&](const auto&) {
|
||||
return _conn;
|
||||
});
|
||||
@@ -23,18 +23,19 @@ Result<Ref<Connection>> drop_impl(const Ref<Connection>& _conn,
|
||||
template <class ValueType, class Connection>
|
||||
requires is_connection<Connection>
|
||||
Result<Ref<Connection>> drop_impl(const Result<Ref<Connection>>& _res,
|
||||
const bool _if_exists) {
|
||||
const bool _if_exists, const bool _cascade) {
|
||||
return _res.and_then([&](const auto& _conn) {
|
||||
return drop_impl<ValueType>(_conn, _if_exists);
|
||||
return drop_impl<ValueType>(_conn, _if_exists, _cascade);
|
||||
});
|
||||
}
|
||||
|
||||
template <class ValueType>
|
||||
struct Drop {
|
||||
auto operator()(const auto& _conn) const {
|
||||
return drop_impl<ValueType>(_conn, if_exists_);
|
||||
return drop_impl<ValueType>(_conn, if_exists_, cascade_);
|
||||
}
|
||||
|
||||
bool cascade_ = false;
|
||||
bool if_exists_ = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
namespace sqlgen::dynamic {
|
||||
|
||||
struct Drop {
|
||||
bool if_exists = true;
|
||||
bool if_exists = false;
|
||||
bool cascade = false;
|
||||
Table table;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,14 +2,21 @@
|
||||
#define SQLGEN_DYNAMIC_TABLE_TYPES_HPP_
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace sqlgen::dynamic::types {
|
||||
|
||||
struct ForeignKeyReference {
|
||||
std::string table;
|
||||
std::string column;
|
||||
};
|
||||
|
||||
struct Properties {
|
||||
bool auto_incr = false;
|
||||
bool primary = false;
|
||||
bool nullable = false;
|
||||
std::optional<ForeignKeyReference> foreign_key_reference = std::nullopt;
|
||||
};
|
||||
|
||||
// To be used as the default value.
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "Parser_base.hpp"
|
||||
#include "Parser_default.hpp"
|
||||
#include "Parser_foreign_key.hpp"
|
||||
#include "Parser_optional.hpp"
|
||||
#include "Parser_primary_key.hpp"
|
||||
#include "Parser_shared_ptr.hpp"
|
||||
|
||||
44
include/sqlgen/parsing/Parser_foreign_key.hpp
Normal file
44
include/sqlgen/parsing/Parser_foreign_key.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#ifndef SQLGEN_PARSING_PARSER_FOREIGN_KEY_HPP_
|
||||
#define SQLGEN_PARSING_PARSER_FOREIGN_KEY_HPP_
|
||||
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "../ForeignKey.hpp"
|
||||
#include "../Result.hpp"
|
||||
#include "../dynamic/Type.hpp"
|
||||
#include "../transpilation/get_tablename.hpp"
|
||||
#include "Parser_base.hpp"
|
||||
|
||||
namespace sqlgen::parsing {
|
||||
|
||||
template <class T, class _ForeignTableType,
|
||||
rfl::internal::StringLiteral _col_name>
|
||||
struct Parser<ForeignKey<T, _ForeignTableType, _col_name>> {
|
||||
static Result<ForeignKey<T, _ForeignTableType, _col_name>> read(
|
||||
const std::optional<std::string>& _str) noexcept {
|
||||
return Parser<std::remove_cvref_t<T>>::read(_str).transform([](auto&& _t) {
|
||||
return ForeignKey<T, _ForeignTableType, _col_name>(std::move(_t));
|
||||
});
|
||||
}
|
||||
|
||||
static std::optional<std::string> write(
|
||||
const ForeignKey<T, _ForeignTableType, _col_name>& _f) noexcept {
|
||||
return Parser<std::remove_cvref_t<T>>::write(_f.value());
|
||||
}
|
||||
|
||||
static dynamic::Type to_type() noexcept {
|
||||
return Parser<std::remove_cvref_t<T>>::to_type().visit(
|
||||
[](auto _t) -> dynamic::Type {
|
||||
_t.properties.foreign_key_reference =
|
||||
dynamic::types::ForeignKeyReference{
|
||||
.table = transpilation::get_tablename<_ForeignTableType>(),
|
||||
.column = _col_name.str()};
|
||||
return _t;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace sqlgen::parsing
|
||||
|
||||
#endif
|
||||
@@ -18,8 +18,9 @@ namespace sqlgen::transpilation {
|
||||
template <class T>
|
||||
requires std::is_class_v<std::remove_cvref_t<T>> &&
|
||||
std::is_aggregate_v<std::remove_cvref_t<T>>
|
||||
dynamic::Drop to_drop(const bool _if_exists) {
|
||||
dynamic::Drop to_drop(const bool _if_exists, const bool _cascade) {
|
||||
return dynamic::Drop{.if_exists = _if_exists,
|
||||
.cascade = _cascade,
|
||||
.table = dynamic::Table{.name = get_tablename<T>(),
|
||||
.schema = get_schema<T>()}};
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ struct ToSQL<DeleteFrom<T, WhereType>> {
|
||||
template <class T>
|
||||
struct ToSQL<Drop<T>> {
|
||||
dynamic::Statement operator()(const auto& _drop) const {
|
||||
return to_drop<T>(_drop.if_exists_);
|
||||
return to_drop<T>(_drop.if_exists_, _drop.cascade_);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -43,6 +43,14 @@ std::string escape_single_quote(const std::string& _str) noexcept;
|
||||
|
||||
std::string field_to_str(const dynamic::SelectFrom::Field& _field) noexcept;
|
||||
|
||||
std::string foreign_keys_to_sql(
|
||||
const std::vector<
|
||||
std::pair<std::string, dynamic::types::ForeignKeyReference>>&
|
||||
_foreign_keys) noexcept;
|
||||
|
||||
std::vector<std::pair<std::string, dynamic::types::ForeignKeyReference>>
|
||||
get_foreign_keys(const dynamic::CreateTable& _stmt) noexcept;
|
||||
|
||||
std::vector<std::string> get_primary_keys(
|
||||
const dynamic::CreateTable& _stmt) noexcept;
|
||||
|
||||
@@ -320,6 +328,12 @@ std::string create_table_to_sql(const dynamic::CreateTable& _stmt) noexcept {
|
||||
<< ")";
|
||||
}
|
||||
|
||||
const auto foreign_keys = get_foreign_keys(_stmt);
|
||||
|
||||
if (foreign_keys.size() != 0) {
|
||||
stream << ", " << foreign_keys_to_sql(foreign_keys);
|
||||
}
|
||||
|
||||
stream << ");";
|
||||
|
||||
return stream.str();
|
||||
@@ -379,6 +393,10 @@ std::string drop_to_sql(const dynamic::Drop& _stmt) noexcept {
|
||||
}
|
||||
stream << wrap_in_quotes(_stmt.table.name);
|
||||
|
||||
if (_stmt.cascade) {
|
||||
stream << " CASCADE";
|
||||
}
|
||||
|
||||
stream << ";";
|
||||
|
||||
return stream.str();
|
||||
@@ -400,6 +418,47 @@ std::string field_to_str(const dynamic::SelectFrom::Field& _field) noexcept {
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string foreign_keys_to_sql(
|
||||
const std::vector<
|
||||
std::pair<std::string, dynamic::types::ForeignKeyReference>>&
|
||||
_foreign_keys) noexcept {
|
||||
using namespace std::ranges::views;
|
||||
|
||||
const auto to_str =
|
||||
[](const std::pair<std::string, dynamic::types::ForeignKeyReference>&
|
||||
_p) {
|
||||
return "FOREIGN KEY (" + wrap_in_quotes(_p.first) + ") REFERENCES " +
|
||||
wrap_in_quotes(_p.second.table) + "(" +
|
||||
wrap_in_quotes(_p.second.column) + ")";
|
||||
};
|
||||
|
||||
return internal::strings::join(
|
||||
", ", internal::collect::vector(_foreign_keys | transform(to_str)));
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, dynamic::types::ForeignKeyReference>>
|
||||
get_foreign_keys(const dynamic::CreateTable& _stmt) noexcept {
|
||||
using namespace std::ranges::views;
|
||||
|
||||
const auto get_foreign_key_ref = [](const auto& _col)
|
||||
-> std::optional<dynamic::types::ForeignKeyReference> {
|
||||
return _col.type.visit(
|
||||
[](const auto& _t) { return _t.properties.foreign_key_reference; });
|
||||
};
|
||||
|
||||
const auto has_reference = [&](const auto& _col) -> bool {
|
||||
return (true && get_foreign_key_ref(_col));
|
||||
};
|
||||
|
||||
const auto to_pair = [&](const auto& _col)
|
||||
-> std::pair<std::string, dynamic::types::ForeignKeyReference> {
|
||||
return std::make_pair(get_name(_col), get_foreign_key_ref(_col).value());
|
||||
};
|
||||
|
||||
return internal::collect::vector(_stmt.columns | filter(has_reference) |
|
||||
transform(to_pair));
|
||||
}
|
||||
|
||||
std::vector<std::string> get_primary_keys(
|
||||
const dynamic::CreateTable& _stmt) noexcept {
|
||||
using namespace std::ranges::views;
|
||||
|
||||
@@ -46,7 +46,8 @@ std::string join_to_sql(const dynamic::Join& _stmt) noexcept;
|
||||
|
||||
std::string operation_to_sql(const dynamic::Operation& _stmt) noexcept;
|
||||
|
||||
std::string properties_to_sql(const dynamic::types::Properties& _p) noexcept;
|
||||
std::string properties_to_sql(
|
||||
const dynamic::types::Properties& _properties) noexcept;
|
||||
|
||||
std::string select_from_to_sql(const dynamic::SelectFrom& _stmt) noexcept;
|
||||
|
||||
@@ -308,6 +309,10 @@ std::string drop_to_sql(const dynamic::Drop& _stmt) noexcept {
|
||||
}
|
||||
stream << wrap_in_quotes(_stmt.table.name);
|
||||
|
||||
if (_stmt.cascade) {
|
||||
stream << " CASCADE";
|
||||
}
|
||||
|
||||
stream << ";";
|
||||
|
||||
return stream.str();
|
||||
@@ -567,13 +572,22 @@ std::string operation_to_sql(const dynamic::Operation& _stmt) noexcept {
|
||||
}
|
||||
|
||||
std::string properties_to_sql(const dynamic::types::Properties& _p) noexcept {
|
||||
if (_p.auto_incr) {
|
||||
return " GENERATED ALWAYS AS IDENTITY";
|
||||
} else if (!_p.nullable) {
|
||||
return " NOT NULL";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
return [&]() -> std::string {
|
||||
if (_p.auto_incr) {
|
||||
return " GENERATED ALWAYS AS IDENTITY";
|
||||
} else if (!_p.nullable) {
|
||||
return " NOT NULL";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}() + [&]() -> std::string {
|
||||
if (!_p.foreign_key_reference) {
|
||||
return "";
|
||||
}
|
||||
const auto& ref = *_p.foreign_key_reference;
|
||||
return " REFERENCES " + wrap_in_quotes(ref.table) + "(" +
|
||||
wrap_in_quotes(ref.column) + ")";
|
||||
}();
|
||||
}
|
||||
|
||||
std::string select_from_to_sql(const dynamic::SelectFrom& _stmt) noexcept {
|
||||
|
||||
@@ -579,9 +579,18 @@ std::string operation_to_sql(const dynamic::Operation& _stmt) noexcept {
|
||||
}
|
||||
|
||||
std::string properties_to_sql(const dynamic::types::Properties& _p) noexcept {
|
||||
return std::string(_p.primary ? " PRIMARY KEY" : "") +
|
||||
std::string(_p.auto_incr ? " AUTOINCREMENT" : "") +
|
||||
std::string(_p.nullable ? "" : " NOT NULL");
|
||||
return [&]() -> std::string {
|
||||
return std::string(_p.primary ? " PRIMARY KEY" : "") +
|
||||
std::string(_p.auto_incr ? " AUTOINCREMENT" : "") +
|
||||
std::string(_p.nullable ? "" : " NOT NULL");
|
||||
}() + [&]() -> std::string {
|
||||
if (!_p.foreign_key_reference) {
|
||||
return "";
|
||||
}
|
||||
const auto& ref = *_p.foreign_key_reference;
|
||||
return " REFERENCES " + wrap_in_quotes(ref.table) + "(" +
|
||||
wrap_in_quotes(ref.column) + ")";
|
||||
}();
|
||||
}
|
||||
|
||||
std::string select_from_to_sql(const dynamic::SelectFrom& _stmt) noexcept {
|
||||
|
||||
66
tests/mysql/test_foreign_key.cpp
Normal file
66
tests/mysql/test_foreign_key.cpp
Normal 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_foreign_key {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
struct Relationship {
|
||||
sqlgen::ForeignKey<uint32_t, Person, "id"> parent_id;
|
||||
uint32_t child_id;
|
||||
};
|
||||
|
||||
TEST(mysql, test_foreign_key) {
|
||||
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}});
|
||||
|
||||
const auto relationships =
|
||||
std::vector<Relationship>({Relationship{.parent_id = 0, .child_id = 2},
|
||||
Relationship{.parent_id = 0, .child_id = 3},
|
||||
Relationship{.parent_id = 0, .child_id = 4},
|
||||
Relationship{.parent_id = 1, .child_id = 2},
|
||||
Relationship{.parent_id = 1, .child_id = 3},
|
||||
Relationship{.parent_id = 1, .child_id = 4}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto people = mysql::connect(credentials)
|
||||
.and_then(drop<Relationship> | if_exists)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(begin_transaction)
|
||||
.and_then(create_table<Person>)
|
||||
.and_then(create_table<Relationship>)
|
||||
.and_then(insert(std::ref(people1)))
|
||||
.and_then(insert(std::ref(relationships)))
|
||||
.and_then(drop<Relationship> | if_exists)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(commit)
|
||||
.value();
|
||||
}
|
||||
|
||||
} // namespace test_foreign_key
|
||||
|
||||
#endif
|
||||
66
tests/postgres/test_foreign_key.cpp
Normal file
66
tests/postgres/test_foreign_key.cpp
Normal 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_foreign_key {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
struct Relationship {
|
||||
sqlgen::ForeignKey<uint32_t, Person, "id"> parent_id;
|
||||
uint32_t child_id;
|
||||
};
|
||||
|
||||
TEST(postgres, test_foreign_key) {
|
||||
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}});
|
||||
|
||||
const auto relationships =
|
||||
std::vector<Relationship>({Relationship{.parent_id = 0, .child_id = 2},
|
||||
Relationship{.parent_id = 0, .child_id = 3},
|
||||
Relationship{.parent_id = 0, .child_id = 4},
|
||||
Relationship{.parent_id = 1, .child_id = 2},
|
||||
Relationship{.parent_id = 1, .child_id = 3},
|
||||
Relationship{.parent_id = 1, .child_id = 4}});
|
||||
|
||||
const auto credentials = sqlgen::postgres::Credentials{.user = "postgres",
|
||||
.password = "password",
|
||||
.host = "localhost",
|
||||
.dbname = "postgres"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto people = postgres::connect(credentials)
|
||||
.and_then(drop<Person> | if_exists | cascade)
|
||||
.and_then(drop<Relationship> | if_exists)
|
||||
.and_then(begin_transaction)
|
||||
.and_then(create_table<Person>)
|
||||
.and_then(create_table<Relationship>)
|
||||
.and_then(insert(std::ref(people1)))
|
||||
.and_then(insert(std::ref(relationships)))
|
||||
.and_then(drop<Person> | if_exists | cascade)
|
||||
.and_then(drop<Relationship> | if_exists)
|
||||
.and_then(commit)
|
||||
.value();
|
||||
}
|
||||
|
||||
} // namespace test_foreign_key
|
||||
|
||||
#endif
|
||||
58
tests/sqlite/test_foreign_key.cpp
Normal file
58
tests/sqlite/test_foreign_key.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/sqlite.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_foreign_key {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
struct Relationship {
|
||||
sqlgen::ForeignKey<uint32_t, Person, "id"> parent_id;
|
||||
uint32_t child_id;
|
||||
};
|
||||
|
||||
TEST(sqlite, test_foreign_key) {
|
||||
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}});
|
||||
|
||||
const auto relationships =
|
||||
std::vector<Relationship>({Relationship{.parent_id = 0, .child_id = 2},
|
||||
Relationship{.parent_id = 0, .child_id = 3},
|
||||
Relationship{.parent_id = 0, .child_id = 4},
|
||||
Relationship{.parent_id = 1, .child_id = 2},
|
||||
Relationship{.parent_id = 1, .child_id = 3},
|
||||
Relationship{.parent_id = 1, .child_id = 4}});
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto people = sqlite::connect()
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(drop<Relationship> | if_exists)
|
||||
.and_then(begin_transaction)
|
||||
.and_then(create_table<Person>)
|
||||
.and_then(create_table<Relationship>)
|
||||
.and_then(insert(std::ref(people1)))
|
||||
.and_then(insert(std::ref(relationships)))
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(drop<Relationship> | if_exists)
|
||||
.and_then(commit)
|
||||
.value();
|
||||
}
|
||||
|
||||
} // namespace test_foreign_key
|
||||
|
||||
Reference in New Issue
Block a user