Skip to content

Commit

Permalink
Merge pull request #133 from LASTRADA-Software/improvement/create_for…
Browse files Browse the repository at this point in the history
…eign_key_entries

Add foreign key handling in  SqlQuery
  • Loading branch information
christianparpart authored Dec 12, 2024
2 parents cf9e064 + a910f78 commit cbd033f
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 0 deletions.
12 changes: 12 additions & 0 deletions src/Lightweight/SqlQuery/Migrate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,18 @@ SqlCreateTableQueryBuilder& SqlCreateTableQueryBuilder::PrimaryKeyWithAutoIncrem
});
}

SqlCreateTableQueryBuilder& SqlCreateTableQueryBuilder::ForeignKey(std::string columnName,
SqlColumnTypeDefinition columnType,
SqlForeignKeyReferenceDefinition foreignKey)
{
return Column(SqlColumnDeclaration {
.name = std::move(columnName),
.type = columnType,
.foreignKey = std::move(foreignKey),
.required = true,
});
}

SqlCreateTableQueryBuilder& SqlCreateTableQueryBuilder::Unique()
{
_plan.columns.back().unique = true;
Expand Down
3 changes: 3 additions & 0 deletions src/Lightweight/SqlQuery/Migrate.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ class [[nodiscard]] SqlCreateTableQueryBuilder final
LIGHTWEIGHT_API SqlCreateTableQueryBuilder& PrimaryKeyWithAutoIncrement(
std::string columnName, SqlColumnTypeDefinition columnType = SqlColumnTypeDefinitions::Bigint {});

// Creates a new foreign key column.
LIGHTWEIGHT_API SqlCreateTableQueryBuilder& ForeignKey(std::string columnName,SqlColumnTypeDefinition columnType, SqlForeignKeyReferenceDefinition foreignKey);

// Enables the UNIQUE constraint on the last declared column.
LIGHTWEIGHT_API SqlCreateTableQueryBuilder& Unique();

Expand Down
7 changes: 7 additions & 0 deletions src/Lightweight/SqlQuery/MigrationPlan.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,18 @@ enum class SqlPrimaryKeyType : uint8_t
GUID,
};

struct SqlForeignKeyReferenceDefinition
{
std::string tableName;
std::string columnName;
};

struct SqlColumnDeclaration
{
std::string name;
SqlColumnTypeDefinition type;
SqlPrimaryKeyType primaryKey { SqlPrimaryKeyType::NONE };
std::optional<SqlForeignKeyReferenceDefinition> foreignKey {};
bool required { false };
bool unique { false };
bool index { false };
Expand Down
60 changes: 60 additions & 0 deletions src/Lightweight/SqlQueryFormatter.cpp
Original file line number Diff line number Diff line change
@@ -1,18 +1,57 @@
// SPDX-License-Identifier: Apache-2.0

#include "SqlQueryFormatter.hpp"
#include "Utils.hpp"

#include <reflection-cpp/reflection.hpp>

#include <cassert>
#include <concepts>
#include <format>
#include <type_traits>

using namespace std::string_view_literals;

namespace
{

class BasicSqlQueryFormatter;
class PostgreSqlFormatter;
class SqlServerQueryFormatter;
class OracleSqlQueryFormatter;

template <typename Database>
struct ForeignKeyFormatter;

template <typename Database>
requires(detail::OneOf<Database, BasicSqlQueryFormatter, PostgreSqlFormatter>)
struct ForeignKeyFormatter<Database>
{

[[nodiscard]] std::string operator()(SqlColumnDeclaration const& column)
{
return std::format(R"(FOREIGN KEY ("{}") REFERENCES "{}"("{}"))",
column.name,
column.foreignKey->tableName,
column.foreignKey->columnName);
}
};

template <typename Database>
requires(detail::OneOf<Database, SqlServerQueryFormatter, OracleSqlQueryFormatter>)
struct ForeignKeyFormatter<Database>
{

[[nodiscard]] std::string operator()(SqlColumnDeclaration const& column)
{
return std::format(R"(CONSTRAINT {} FOREIGN KEY ("{}") REFERENCES "{}"("{}"))",
std::format("FK_{}", column.name),
column.name,
column.foreignKey->tableName,
column.foreignKey->columnName);
}
};

class BasicSqlQueryFormatter: public SqlQueryFormatter
{
public:
Expand Down Expand Up @@ -167,6 +206,12 @@ class BasicSqlQueryFormatter: public SqlQueryFormatter
[[nodiscard]] virtual std::string BuildColumnDefinition(SqlColumnDeclaration const& column) const
{
std::stringstream sqlQueryString;

if (column.foreignKey)
{
return ForeignKeyFormatter<std::remove_cvref_t<decltype(*this)>> {}(column);
}

sqlQueryString << '"' << column.name << "\" ";

if (column.primaryKey != SqlPrimaryKeyType::AUTO_INCREMENT)
Expand Down Expand Up @@ -438,6 +483,11 @@ class SqlServerQueryFormatter final: public BasicSqlQueryFormatter

[[nodiscard]] std::string BuildColumnDefinition(SqlColumnDeclaration const& column) const override
{
if (column.foreignKey)
{
return ForeignKeyFormatter<std::remove_cvref_t<decltype(*this)>> {}(column);
}

std::stringstream sqlQueryString;
sqlQueryString << '"' << column.name << "\" " << ColumnType(column.type);

Expand Down Expand Up @@ -552,6 +602,11 @@ class OracleSqlQueryFormatter final: public BasicSqlQueryFormatter

[[nodiscard]] std::string BuildColumnDefinition(SqlColumnDeclaration const& column) const override
{
if (column.foreignKey)
{
return ForeignKeyFormatter<std::remove_cvref_t<decltype(*this)>> {}(column);
}

std::stringstream sqlQueryString;
sqlQueryString << '"' << column.name << "\" " << ColumnType(column.type);

Expand Down Expand Up @@ -584,6 +639,11 @@ class PostgreSqlFormatter final: public BasicSqlQueryFormatter

[[nodiscard]] std::string BuildColumnDefinition(SqlColumnDeclaration const& column) const override
{
if (column.foreignKey)
{
return ForeignKeyFormatter<std::remove_cvref_t<decltype(*this)>> {}(column);
}

std::stringstream sqlQueryString;

sqlQueryString << '"' << column.name << "\" ";
Expand Down
31 changes: 31 additions & 0 deletions src/tests/QueryBuilderTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,7 @@ TEST_CASE_METHOD(SqlTestFixture, "CreateTable with PrimaryKeyWithAutoIncrement",
)sql",
});
}

TEST_CASE_METHOD(SqlTestFixture, "CreateTable with Index", "[SqlQueryBuilder][Migration]")
{
using namespace SqlColumnTypeDefinitions;
Expand All @@ -799,6 +800,36 @@ TEST_CASE_METHOD(SqlTestFixture, "CreateTable with Index", "[SqlQueryBuilder][Mi
)sql"));
}

TEST_CASE_METHOD(SqlTestFixture, "CreateTable with Foreign key", "[SqlQueryBuilder][Migration]")
{
using namespace SqlColumnTypeDefinitions;
checkSqlQueryBuilder(
[](SqlQueryBuilder& q) {
auto migration = q.Migration();
migration.CreateTable("Table").ForeignKey("other_id",
Integer {},
SqlForeignKeyReferenceDefinition {
.tableName = "OtherTable",
.columnName = "id",
});
return migration.GetPlan();
},
QueryExpectations {
.sqlite = R"sql(CREATE TABLE "Table" (
FOREIGN KEY ("other_id") REFERENCES "OtherTable"("id")
);)sql",
.postgres = R"sql(CREATE TABLE "Table" (
FOREIGN KEY ("other_id") REFERENCES "OtherTable"("id")
);)sql",
.sqlServer = R"sql(CREATE TABLE "Table" (
CONSTRAINT FK_other_id FOREIGN KEY ("other_id") REFERENCES "OtherTable"("id")
);)sql",
.oracle = R"sql(CREATE TABLE "Table" (
CONSTRAINT FK_other_id FOREIGN KEY ("other_id") REFERENCES "OtherTable"("id")
);)sql",
});
}

TEST_CASE_METHOD(SqlTestFixture, "CreateTable complex demo", "[SqlQueryBuilder][Migration]")
{
using namespace SqlColumnTypeDefinitions;
Expand Down

0 comments on commit cbd033f

Please sign in to comment.