diff --git a/src/libs/sqlite/createtablesqlstatementbuilder.cpp b/src/libs/sqlite/createtablesqlstatementbuilder.cpp index 9a64d8e488d..c02169d2fd8 100644 --- a/src/libs/sqlite/createtablesqlstatementbuilder.cpp +++ b/src/libs/sqlite/createtablesqlstatementbuilder.cpp @@ -39,13 +39,18 @@ void CreateTableSqlStatementBuilder::setTableName(Utils::SmallString &&tableName this->m_tableName = std::move(tableName); } -void CreateTableSqlStatementBuilder::addColumn(Utils::SmallString &&columnName, +void CreateTableSqlStatementBuilder::addColumn(Utils::SmallStringView columnName, ColumnType columnType, - Contraint constraint) + Contraint constraint, + ForeignKey &&foreignKey) { m_sqlStatementBuilder.clear(); - m_columns.emplace_back(std::move(columnName), columnType, constraint); + m_columns.emplace_back(Utils::SmallStringView{}, + columnName, + columnType, + constraint, + std::move(foreignKey)); } void CreateTableSqlStatementBuilder::setColumns(const SqliteColumns &columns) @@ -97,17 +102,70 @@ bool CreateTableSqlStatementBuilder::isValid() const return m_tableName.hasContent() && !m_columns.empty(); } +namespace { +Utils::SmallStringView actionToText(ForeignKeyAction action) +{ + switch (action) { + case ForeignKeyAction::NoAction: + return "NO ACTION"; + case ForeignKeyAction::Restrict: + return "RESTRICT"; + case ForeignKeyAction::SetNull: + return "SET NULL"; + case ForeignKeyAction::SetDefault: + return "SET DEFAULT"; + case ForeignKeyAction::Cascade: + return "CASCADE"; + } + + return ""; +} + +void appendForeignKey(Utils::SmallString &columnDefinitionString, const ForeignKey &foreignKey) +{ + columnDefinitionString.append(" REFERENCES "); + columnDefinitionString.append(foreignKey.table); + + if (foreignKey.column.hasContent()) { + columnDefinitionString.append("("); + columnDefinitionString.append(foreignKey.column); + columnDefinitionString.append(")"); + } + + if (foreignKey.updateAction != ForeignKeyAction::NoAction) { + columnDefinitionString.append(" ON UPDATE "); + columnDefinitionString.append(actionToText(foreignKey.updateAction)); + } + + if (foreignKey.deleteAction != ForeignKeyAction::NoAction) { + columnDefinitionString.append(" ON DELETE "); + columnDefinitionString.append(actionToText(foreignKey.deleteAction)); + } + + if (foreignKey.enforcement == Enforment::Deferred) + columnDefinitionString.append(" DEFERRABLE INITIALLY DEFERRED"); +} +} // namespace void CreateTableSqlStatementBuilder::bindColumnDefinitions() const { Utils::SmallStringVector columnDefinitionStrings; + columnDefinitionStrings.reserve(m_columns.size()); - for (const Column &columns : m_columns) { - Utils::SmallString columnDefinitionString = {columns.name(), " ", columns.typeString()}; + for (const Column &column : m_columns) { + Utils::SmallString columnDefinitionString = {column.name, " ", column.typeString()}; - switch (columns.constraint()) { - case Contraint::PrimaryKey: columnDefinitionString.append(" PRIMARY KEY"); break; - case Contraint::Unique: columnDefinitionString.append(" UNIQUE"); break; - case Contraint::NoConstraint: break; + switch (column.constraint) { + case Contraint::PrimaryKey: + columnDefinitionString.append(" PRIMARY KEY"); + break; + case Contraint::Unique: + columnDefinitionString.append(" UNIQUE"); + break; + case Contraint::ForeignKey: + appendForeignKey(columnDefinitionString, column.foreignKey); + break; + case Contraint::NoConstraint: + break; } columnDefinitionStrings.push_back(columnDefinitionString); diff --git a/src/libs/sqlite/createtablesqlstatementbuilder.h b/src/libs/sqlite/createtablesqlstatementbuilder.h index 00c9ebd45a1..a62f1f4adb4 100644 --- a/src/libs/sqlite/createtablesqlstatementbuilder.h +++ b/src/libs/sqlite/createtablesqlstatementbuilder.h @@ -36,9 +36,10 @@ public: CreateTableSqlStatementBuilder(); void setTableName(Utils::SmallString &&tableName); - void addColumn(Utils::SmallString &&columnName, + void addColumn(Utils::SmallStringView columnName, ColumnType columnType, - Contraint constraint = Contraint::NoConstraint); + Contraint constraint = Contraint::NoConstraint, + ForeignKey &&foreignKey = {}); void setColumns(const SqliteColumns &columns); void setUseWithoutRowId(bool useWithoutRowId); void setUseIfNotExists(bool useIfNotExists); diff --git a/src/libs/sqlite/sqlite-lib.pri b/src/libs/sqlite/sqlite-lib.pri index 1ce343b6247..2fd286f3a8c 100644 --- a/src/libs/sqlite/sqlite-lib.pri +++ b/src/libs/sqlite/sqlite-lib.pri @@ -28,6 +28,7 @@ HEADERS += \ $$PWD/sqlitedatabasebackend.h \ $$PWD/sqlitedatabaseinterface.h \ $$PWD/sqliteexception.h \ + $$PWD/sqliteforeignkey.h \ $$PWD/sqliteglobal.h \ $$PWD/sqlitereadstatement.h \ $$PWD/sqlitereadwritestatement.h \ @@ -45,7 +46,8 @@ HEADERS += \ $$PWD/sqlitebasestatement.h DEFINES += SQLITE_THREADSAFE=2 SQLITE_ENABLE_FTS4 SQLITE_ENABLE_FTS3_PARENTHESIS \ - SQLITE_ENABLE_UNLOCK_NOTIFY SQLITE_ENABLE_COLUMN_METADATA SQLITE_ENABLE_JSON1 + SQLITE_ENABLE_UNLOCK_NOTIFY SQLITE_ENABLE_COLUMN_METADATA SQLITE_ENABLE_JSON1 \ + SQLITE_DEFAULT_FOREIGN_KEYS=1 OTHER_FILES += README.md diff --git a/src/libs/sqlite/sqlitecolumn.h b/src/libs/sqlite/sqlitecolumn.h index d6401d299b2..fd31998f44c 100644 --- a/src/libs/sqlite/sqlitecolumn.h +++ b/src/libs/sqlite/sqlitecolumn.h @@ -25,7 +25,7 @@ #pragma once -#include "sqliteglobal.h" +#include "sqliteforeignkey.h" #include @@ -38,59 +38,60 @@ class Column public: Column() = default; - Column(Utils::SmallString &&name, + Column(Utils::SmallStringView tableName, + Utils::SmallStringView name, ColumnType type = ColumnType::Numeric, - Contraint constraint = Contraint::NoConstraint) - : m_name(std::move(name)), - m_type(type), - m_constraint(constraint) + Contraint constraint = Contraint::NoConstraint, + ForeignKey &&foreignKey = {}) + : foreignKey(std::move(foreignKey)) + , name(name) + , tableName(tableName) + , type(type) + , constraint(constraint) + {} + + Column(Utils::SmallStringView tableName, + Utils::SmallStringView name, + ColumnType type, + Contraint constraint, + Utils::SmallStringView foreignKeyTable, + Utils::SmallStringView foreignKeycolumn, + ForeignKeyAction foreignKeyUpdateAction, + ForeignKeyAction foreignKeyDeleteAction, + Enforment foreignKeyEnforcement) + : foreignKey(foreignKeyTable, + foreignKeycolumn, + foreignKeyUpdateAction, + foreignKeyDeleteAction, + foreignKeyEnforcement) + , name(name) + , tableName(tableName) + , type(type) + , constraint(constraint) + {} void clear() { - m_name.clear(); - m_type = ColumnType::Numeric; - m_constraint = Contraint::NoConstraint; - } - - void setName(Utils::SmallString &&newName) - { - m_name = newName; - } - - const Utils::SmallString &name() const - { - return m_name; - } - - void setType(ColumnType newType) - { - m_type = newType; - } - - ColumnType type() const - { - return m_type; - } - - void setContraint(Contraint constraint) - { - m_constraint = constraint; - } - - Contraint constraint() const - { - return m_constraint; + name.clear(); + type = ColumnType::Numeric; + constraint = Contraint::NoConstraint; + foreignKey = {}; } Utils::SmallString typeString() const { - switch (m_type) { - case ColumnType::None: return {}; - case ColumnType::Numeric: return "NUMERIC"; - case ColumnType::Integer: return "INTEGER"; - case ColumnType::Real: return "REAL"; - case ColumnType::Text: return "TEXT"; + switch (type) { + case ColumnType::None: + return {}; + case ColumnType::Numeric: + return "NUMERIC"; + case ColumnType::Integer: + return "INTEGER"; + case ColumnType::Real: + return "REAL"; + case ColumnType::Text: + return "TEXT"; } Q_UNREACHABLE(); @@ -98,16 +99,18 @@ public: friend bool operator==(const Column &first, const Column &second) { - return first.m_name == second.m_name - && first.m_type == second.m_type - && first.m_constraint == second.m_constraint; + return first.name == second.name && first.type == second.type + && first.constraint + == second.constraint /* && first.foreignKey == second.foreignKey*/; } -private: - Utils::SmallString m_name; - ColumnType m_type = ColumnType::Numeric; - Contraint m_constraint = Contraint::NoConstraint; -}; +public: + ForeignKey foreignKey; + Utils::SmallString name; + Utils::SmallString tableName; + ColumnType type = ColumnType::Numeric; + Contraint constraint = Contraint::NoConstraint; +}; // namespace Sqlite using SqliteColumns = std::vector; using SqliteColumnConstReference = std::reference_wrapper; diff --git a/src/libs/sqlite/sqliteexception.h b/src/libs/sqlite/sqliteexception.h index 6f898504a4c..3bf2792d9dd 100644 --- a/src/libs/sqlite/sqliteexception.h +++ b/src/libs/sqlite/sqliteexception.h @@ -272,4 +272,12 @@ public: {} }; +class ForeignKeyColumnIsNotUnique : public Exception +{ +public: + ForeignKeyColumnIsNotUnique(const char *whatErrorHasHappen) + : Exception(whatErrorHasHappen) + {} +}; + } // namespace Sqlite diff --git a/src/libs/sqlite/sqliteforeignkey.h b/src/libs/sqlite/sqliteforeignkey.h new file mode 100644 index 00000000000..011ba16b653 --- /dev/null +++ b/src/libs/sqlite/sqliteforeignkey.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "sqliteglobal.h" + +#include + +namespace Sqlite { + +class ForeignKey +{ +public: + ForeignKey() = default; + ForeignKey(Utils::SmallStringView table, + Utils::SmallStringView column, + ForeignKeyAction updateAction = {}, + ForeignKeyAction deleteAction = {}, + Enforment enforcement = {}) + : table(table) + , column(column) + , updateAction(updateAction) + , deleteAction(deleteAction) + , enforcement(enforcement) + {} + + friend bool operator==(const ForeignKey &first, const ForeignKey &second) + { + return first.table == second.table && first.column == second.column + && first.updateAction == second.updateAction + && first.deleteAction == second.deleteAction; + } + +public: + Utils::SmallString table; + Utils::SmallString column; + ForeignKeyAction updateAction = {}; + ForeignKeyAction deleteAction = {}; + Enforment enforcement = {}; +}; + +} // namespace Sqlite diff --git a/src/libs/sqlite/sqliteglobal.h b/src/libs/sqlite/sqliteglobal.h index af9d9ac36c0..6df0849e18e 100644 --- a/src/libs/sqlite/sqliteglobal.h +++ b/src/libs/sqlite/sqliteglobal.h @@ -48,17 +48,13 @@ enum class ColumnType : char None }; -enum class Contraint : char -{ - NoConstraint, - PrimaryKey, - Unique -}; +enum class Contraint : char { NoConstraint, PrimaryKey, Unique, ForeignKey }; -enum class ColumnConstraint : char -{ - PrimaryKey -}; +enum class ForeignKeyAction : char { NoAction, Restrict, SetNull, SetDefault, Cascade }; + +enum class Enforment : char { Immediate, Deferred }; + +enum class ColumnConstraint : char { PrimaryKey }; enum class JournalMode : char { diff --git a/src/libs/sqlite/sqlitetable.h b/src/libs/sqlite/sqlitetable.h index ef0725ab41f..8c98959ef92 100644 --- a/src/libs/sqlite/sqlitetable.h +++ b/src/libs/sqlite/sqlitetable.h @@ -44,10 +44,7 @@ public: m_sqliteIndices.reserve(reserve); } - void setName(Utils::SmallString &&name) - { - m_tableName = std::move(name); - } + void setName(Utils::SmallStringView name) { m_tableName = name; } Utils::SmallStringView name() const { @@ -74,11 +71,53 @@ public: m_useTemporaryTable = useTemporaryTable; } - Column &addColumn(Utils::SmallString &&name, - ColumnType type = ColumnType::Numeric, - Contraint constraint = Contraint::NoConstraint) + Column &addColumn(Utils::SmallStringView name, + ColumnType type = ColumnType::Numeric, + Contraint constraint = Contraint::NoConstraint) { - m_sqliteColumns.emplace_back(std::move(name), type, constraint); + m_sqliteColumns.emplace_back(m_tableName, name, type, constraint); + + return m_sqliteColumns.back(); + } + + Column &addForeignKeyColumn(Utils::SmallStringView name, + const Table &referencedTable, + ForeignKeyAction foreignKeyupdateAction = {}, + ForeignKeyAction foreignKeyDeleteAction = {}, + Enforment foreignKeyEnforcement = {}, + ColumnType type = ColumnType::Integer) + { + m_sqliteColumns.emplace_back(m_tableName, + name, + type, + Contraint::ForeignKey, + referencedTable.name(), + "", + foreignKeyupdateAction, + foreignKeyDeleteAction, + foreignKeyEnforcement); + + return m_sqliteColumns.back(); + } + + Column &addForeignKeyColumn(Utils::SmallStringView name, + const Column &referencedColumn, + ForeignKeyAction foreignKeyupdateAction = {}, + ForeignKeyAction foreignKeyDeleteAction = {}, + Enforment foreignKeyEnforcement = {}) + { + if (referencedColumn.constraint != Contraint::Unique) + throw ForeignKeyColumnIsNotUnique("Foreign column key must be unique!"); + + m_sqliteColumns.emplace_back(m_tableName, + name, + referencedColumn.type, + Contraint::ForeignKey, + referencedColumn.tableName, + referencedColumn.name, + foreignKeyupdateAction, + foreignKeyDeleteAction, + foreignKeyEnforcement); return m_sqliteColumns.back(); } @@ -148,7 +187,7 @@ private: Utils::SmallStringVector columnNames; for (const Column &column : columns) - columnNames.push_back(column.name()); + columnNames.push_back(column.name); return columnNames; } diff --git a/tests/unit/unittest/createtablesqlstatementbuilder-test.cpp b/tests/unit/unittest/createtablesqlstatementbuilder-test.cpp index 9a56777a172..39e927797be 100644 --- a/tests/unit/unittest/createtablesqlstatementbuilder-test.cpp +++ b/tests/unit/unittest/createtablesqlstatementbuilder-test.cpp @@ -30,13 +30,14 @@ namespace { +using Sqlite::Column; using Sqlite::ColumnType; using Sqlite::Contraint; +using Sqlite::Enforment; +using Sqlite::ForeignKeyAction; using Sqlite::JournalMode; using Sqlite::OpenMode; -using Sqlite::Column; using Sqlite::SqliteColumns; - using Sqlite::SqlStatementBuilderException; class CreateTableSqlStatementBuilder : public ::testing::Test @@ -199,11 +200,210 @@ void CreateTableSqlStatementBuilder::bindValues() SqliteColumns CreateTableSqlStatementBuilder::createColumns() { SqliteColumns columns; - columns.emplace_back("id", ColumnType::Integer, Contraint::PrimaryKey); - columns.emplace_back("name", ColumnType::Text); - columns.emplace_back("number", ColumnType::Numeric); + columns.emplace_back("", "id", ColumnType::Integer, Contraint::PrimaryKey); + columns.emplace_back("", "name", ColumnType::Text); + columns.emplace_back("", "number", ColumnType::Numeric); return columns; } +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyWithoutColumn) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", ColumnType::Integer, Contraint::ForeignKey, {"otherTable", ""}); + + ASSERT_THAT(builder.sqlStatement(), "CREATE TABLE test(id INTEGER REFERENCES otherTable)"); } + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyWithColumn) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", ColumnType::Integer, Contraint::ForeignKey, {"otherTable", "otherColumn"}); + + ASSERT_THAT(builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn))"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyUpdateNoAction) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", ColumnType::Integer, Contraint::ForeignKey, {"otherTable", "otherColumn"}); + + ASSERT_THAT(builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn))"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyUpdateRestrict) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Integer, + Contraint::ForeignKey, + {"otherTable", "otherColumn", ForeignKeyAction::Restrict}); + + ASSERT_THAT( + builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn) ON UPDATE RESTRICT)"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyUpdateSetNull) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Integer, + Contraint::ForeignKey, + {"otherTable", "otherColumn", ForeignKeyAction::SetNull}); + + ASSERT_THAT( + builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn) ON UPDATE SET NULL)"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyUpdateSetDefault) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Integer, + Contraint::ForeignKey, + {"otherTable", "otherColumn", ForeignKeyAction::SetDefault}); + + ASSERT_THAT( + builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn) ON UPDATE SET DEFAULT)"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyUpdateCascade) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Integer, + Contraint::ForeignKey, + {"otherTable", "otherColumn", ForeignKeyAction::Cascade}); + + ASSERT_THAT( + builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn) ON UPDATE CASCADE)"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyDeleteNoAction) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", ColumnType::Integer, Contraint::ForeignKey, {"otherTable", "otherColumn"}); + + ASSERT_THAT(builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn))"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyDeleteRestrict) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Integer, + Contraint::ForeignKey, + {"otherTable", "otherColumn", {}, ForeignKeyAction::Restrict}); + + ASSERT_THAT( + builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn) ON DELETE RESTRICT)"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyDeleteSetNull) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Integer, + Contraint::ForeignKey, + {"otherTable", "otherColumn", {}, ForeignKeyAction::SetNull}); + + ASSERT_THAT( + builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn) ON DELETE SET NULL)"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyDeleteSetDefault) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Integer, + Contraint::ForeignKey, + {"otherTable", "otherColumn", {}, ForeignKeyAction::SetDefault}); + + ASSERT_THAT( + builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn) ON DELETE SET DEFAULT)"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyDeleteCascade) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Integer, + Contraint::ForeignKey, + {"otherTable", "otherColumn", {}, ForeignKeyAction::Cascade}); + + ASSERT_THAT( + builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn) ON DELETE CASCADE)"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyDeleteAndUpdateAction) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Integer, + Contraint::ForeignKey, + {"otherTable", + "otherColumn", + ForeignKeyAction::SetDefault, + ForeignKeyAction::Cascade}); + + ASSERT_THAT(builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn) ON UPDATE SET " + "DEFAULT ON DELETE CASCADE)"); +} + +TEST_F(CreateTableSqlStatementBuilder, ForeignKeyDeferred) +{ + builder.clear(); + builder.setTableName("test"); + + builder.addColumn("id", + ColumnType::Integer, + Contraint::ForeignKey, + {"otherTable", + "otherColumn", + ForeignKeyAction::SetDefault, + ForeignKeyAction::Cascade, + Enforment::Deferred}); + + ASSERT_THAT(builder.sqlStatement(), + "CREATE TABLE test(id INTEGER REFERENCES otherTable(otherColumn) ON UPDATE SET " + "DEFAULT ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)"); +} +} // namespace diff --git a/tests/unit/unittest/sqlitecolumn-test.cpp b/tests/unit/unittest/sqlitecolumn-test.cpp index 3daa32bd231..0d6b1401ad9 100644 --- a/tests/unit/unittest/sqlitecolumn-test.cpp +++ b/tests/unit/unittest/sqlitecolumn-test.cpp @@ -29,70 +29,111 @@ namespace { -using testing::AllOf; -using testing::Contains; -using testing::Property; - using Sqlite::ColumnType; using Sqlite::Contraint; using Sqlite::JournalMode; using Sqlite::OpenMode; using Column = Sqlite::Column; +using Sqlite::Enforment; +using Sqlite::ForeignKey; +using Sqlite::ForeignKeyAction; using Sqlite::SqliteColumns; class SqliteColumn : public ::testing::Test { protected: - void SetUp() override; - Sqlite::Column column; }; -TEST_F(SqliteColumn, ChangeName) +TEST_F(SqliteColumn, DefaultConstruct) { - column.setName("Claudia"); - - ASSERT_THAT(column.name(), "Claudia"); + ASSERT_THAT(column, + AllOf(Field(&Column::name, IsEmpty()), + Field(&Column::tableName, IsEmpty()), + Field(&Column::type, ColumnType::Numeric), + Field(&Column::constraint, Contraint::NoConstraint), + Field(&Column::foreignKey, + AllOf(Field(&ForeignKey::table, IsEmpty()), + Field(&ForeignKey::column, IsEmpty()), + Field(&ForeignKey::updateAction, ForeignKeyAction::NoAction), + Field(&ForeignKey::deleteAction, ForeignKeyAction::NoAction), + Field(&ForeignKey::enforcement, Enforment::Immediate))))); } -TEST_F(SqliteColumn, DefaultType) +TEST_F(SqliteColumn, Clear) { - ASSERT_THAT(column.type(), ColumnType::Numeric); -} + column.name = "foo"; + column.name = "foo"; + column.type = ColumnType::Text; + column.constraint = Contraint::ForeignKey; + column.foreignKey.table = "bar"; + column.foreignKey.column = "hmm"; + column.foreignKey.updateAction = ForeignKeyAction::Cascade; + column.foreignKey.deleteAction = ForeignKeyAction::SetNull; -TEST_F(SqliteColumn, ChangeType) -{ - column.setType(ColumnType::Text); - - ASSERT_THAT(column.type(), ColumnType::Text); -} - -TEST_F(SqliteColumn, DefaultConstraint) -{ - ASSERT_THAT(column.constraint(), Contraint::NoConstraint); -} - -TEST_F(SqliteColumn, SetConstraint) -{ - column.setContraint(Contraint::PrimaryKey); - - ASSERT_THAT(column.constraint(), Contraint::PrimaryKey); -} - -TEST_F(SqliteColumn, GetColumnDefinition) -{ - column.setName("Claudia"); + column.clear(); ASSERT_THAT(column, - AllOf( - Property(&Column::name, "Claudia"), - Property(&Column::type, ColumnType::Numeric), - Property(&Column::constraint, Contraint::NoConstraint))); + AllOf(Field(&Column::name, IsEmpty()), + Field(&Column::tableName, IsEmpty()), + Field(&Column::type, ColumnType::Numeric), + Field(&Column::constraint, Contraint::NoConstraint), + Field(&Column::foreignKey, + AllOf(Field(&ForeignKey::table, IsEmpty()), + Field(&ForeignKey::column, IsEmpty()), + Field(&ForeignKey::updateAction, ForeignKeyAction::NoAction), + Field(&ForeignKey::deleteAction, ForeignKeyAction::NoAction), + Field(&ForeignKey::enforcement, Enforment::Immediate))))); } -void SqliteColumn::SetUp() +TEST_F(SqliteColumn, Constructor) { - column.clear(); + column = Sqlite::Column{"table", + "column", + ColumnType::Text, + Contraint::ForeignKey, + {"referencedTable", + "referencedColumn", + ForeignKeyAction::SetNull, + ForeignKeyAction::Cascade, + Enforment::Deferred}}; + + ASSERT_THAT(column, + AllOf(Field(&Column::name, Eq("column")), + Field(&Column::tableName, Eq("table")), + Field(&Column::type, ColumnType::Text), + Field(&Column::constraint, Contraint::ForeignKey), + Field(&Column::foreignKey, + AllOf(Field(&ForeignKey::table, Eq("referencedTable")), + Field(&ForeignKey::column, Eq("referencedColumn")), + Field(&ForeignKey::updateAction, ForeignKeyAction::SetNull), + Field(&ForeignKey::deleteAction, ForeignKeyAction::Cascade), + Field(&ForeignKey::enforcement, Enforment::Deferred))))); } +TEST_F(SqliteColumn, FlatConstructor) +{ + column = Sqlite::Column{"table", + "column", + ColumnType::Text, + Contraint::ForeignKey, + "referencedTable", + "referencedColumn", + ForeignKeyAction::SetNull, + ForeignKeyAction::Cascade, + Enforment::Deferred}; + + ASSERT_THAT(column, + AllOf(Field(&Column::name, Eq("column")), + Field(&Column::tableName, Eq("table")), + Field(&Column::type, ColumnType::Text), + Field(&Column::constraint, Contraint::ForeignKey), + Field(&Column::foreignKey, + AllOf(Field(&ForeignKey::table, Eq("referencedTable")), + Field(&ForeignKey::column, Eq("referencedColumn")), + Field(&ForeignKey::updateAction, ForeignKeyAction::SetNull), + Field(&ForeignKey::deleteAction, ForeignKeyAction::Cascade), + Field(&ForeignKey::enforcement, Enforment::Deferred))))); } + +} // namespace diff --git a/tests/unit/unittest/sqlitetable-test.cpp b/tests/unit/unittest/sqlitetable-test.cpp index c5ce8e325dd..89dfb7f867b 100644 --- a/tests/unit/unittest/sqlitetable-test.cpp +++ b/tests/unit/unittest/sqlitetable-test.cpp @@ -32,11 +32,15 @@ namespace { +using Sqlite::Column; using Sqlite::ColumnType; +using Sqlite::Contraint; +using Sqlite::Database; +using Sqlite::Enforment; +using Sqlite::ForeignKey; +using Sqlite::ForeignKeyAction; using Sqlite::JournalMode; using Sqlite::OpenMode; -using Sqlite::Column; -using Sqlite::Database; class SqliteTable : public ::testing::Test { @@ -110,21 +114,135 @@ TEST_F(SqliteTable, InitializeTableWithIndex) table.initialize(mockDatabase); } - -TEST_F(SqliteTable, InitializeTableWithUniqueIndex) +TEST_F(SqliteTable, AddForeignKeyColumnWithTableCalls) { - InSequence sequence; - table.setName(tableName.clone()); - auto &column = table.addColumn("name"); - auto &column2 = table.addColumn("value"); - table.addUniqueIndex({column}); - table.addIndex({column2}); + Sqlite::Table foreignTable; + foreignTable.setName("foreignTable"); + table.setName(tableName); + table.addForeignKeyColumn("name", + foreignTable, + ForeignKeyAction::SetNull, + ForeignKeyAction::Cascade, + Enforment::Deferred); - EXPECT_CALL(mockDatabase, execute(Eq("CREATE TABLE testTable(name NUMERIC, value NUMERIC)"))); - EXPECT_CALL(mockDatabase, execute(Eq("CREATE UNIQUE INDEX IF NOT EXISTS index_testTable_name ON testTable(name)"))); - EXPECT_CALL(mockDatabase, execute(Eq("CREATE INDEX IF NOT EXISTS index_testTable_value ON testTable(value)"))); + EXPECT_CALL(mockDatabase, + execute(Eq("CREATE TABLE testTable(name INTEGER REFERENCES foreignTable ON UPDATE " + "SET NULL ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)"))); table.initialize(mockDatabase); } +TEST_F(SqliteTable, AddForeignKeyColumnWithColumnCalls) +{ + Sqlite::Table foreignTable; + foreignTable.setName("foreignTable"); + auto &foreignColumn = foreignTable.addColumn("foreignColumn", + ColumnType::Text, + Sqlite::Contraint::Unique); + table.setName(tableName); + table.addForeignKeyColumn("name", + foreignColumn, + ForeignKeyAction::SetDefault, + ForeignKeyAction::Restrict, + Enforment::Deferred); + + EXPECT_CALL( + mockDatabase, + execute( + Eq("CREATE TABLE testTable(name TEXT REFERENCES foreignTable(foreignColumn) ON UPDATE " + "SET DEFAULT ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED)"))); + + table.initialize(mockDatabase); } + +TEST_F(SqliteTable, AddColumn) +{ + table.setName(tableName); + + auto &column = table.addColumn("name", ColumnType::Text, Sqlite::Contraint::Unique); + + ASSERT_THAT(column, + AllOf(Field(&Column::name, Eq("name")), + Field(&Column::tableName, Eq(tableName)), + Field(&Column::type, ColumnType::Text), + Field(&Column::constraint, Contraint::Unique), + Field(&Column::foreignKey, + AllOf(Field(&ForeignKey::table, IsEmpty()), + Field(&ForeignKey::column, IsEmpty()), + Field(&ForeignKey::updateAction, ForeignKeyAction::NoAction), + Field(&ForeignKey::deleteAction, ForeignKeyAction::NoAction), + Field(&ForeignKey::enforcement, Enforment::Immediate))))); +} + +TEST_F(SqliteTable, AddForeignKeyColumnWithTable) +{ + Sqlite::Table foreignTable; + foreignTable.setName("foreignTable"); + + table.setName(tableName); + + auto &column = table.addForeignKeyColumn("name", + foreignTable, + ForeignKeyAction::SetNull, + ForeignKeyAction::Cascade, + Enforment::Deferred); + + ASSERT_THAT(column, + AllOf(Field(&Column::name, Eq("name")), + Field(&Column::tableName, Eq(tableName)), + Field(&Column::type, ColumnType::Integer), + Field(&Column::constraint, Contraint::ForeignKey), + Field(&Column::foreignKey, + AllOf(Field(&ForeignKey::table, Eq("foreignTable")), + Field(&ForeignKey::column, IsEmpty()), + Field(&ForeignKey::updateAction, ForeignKeyAction::SetNull), + Field(&ForeignKey::deleteAction, ForeignKeyAction::Cascade), + Field(&ForeignKey::enforcement, Enforment::Deferred))))); +} + +TEST_F(SqliteTable, AddForeignKeyColumnWithColumn) +{ + Sqlite::Table foreignTable; + foreignTable.setName("foreignTable"); + auto &foreignColumn = foreignTable.addColumn("foreignColumn", + ColumnType::Text, + Sqlite::Contraint::Unique); + table.setName(tableName); + + auto &column = table.addForeignKeyColumn("name", + foreignColumn, + ForeignKeyAction::SetNull, + ForeignKeyAction::Cascade, + Enforment::Deferred); + + ASSERT_THAT(column, + AllOf(Field(&Column::name, Eq("name")), + Field(&Column::tableName, Eq(tableName)), + Field(&Column::type, ColumnType::Text), + Field(&Column::constraint, Contraint::ForeignKey), + Field(&Column::foreignKey, + AllOf(Field(&ForeignKey::table, Eq("foreignTable")), + Field(&ForeignKey::column, Eq("foreignColumn")), + Field(&ForeignKey::updateAction, ForeignKeyAction::SetNull), + Field(&ForeignKey::deleteAction, ForeignKeyAction::Cascade), + Field(&ForeignKey::enforcement, Enforment::Deferred))))); +} + +TEST_F(SqliteTable, AddForeignKeyWhichIsNotUniqueThrowsAnExceptions) +{ + Sqlite::Table foreignTable; + foreignTable.setName("foreignTable"); + auto &foreignColumn = foreignTable.addColumn("foreignColumn", + ColumnType::Text, + Sqlite::Contraint::NoConstraint); + table.setName(tableName); + + ASSERT_THROW(table.addForeignKeyColumn("name", + foreignColumn, + ForeignKeyAction::SetNull, + ForeignKeyAction::Cascade, + Enforment::Deferred), + Sqlite::ForeignKeyColumnIsNotUnique); +} + +} // namespace