Sqlite: Add foreign key support

It is still only support references in columns but so far it is enough.

Change-Id: Iebb4866cf738d651270e54357b5e4a2837f05417
Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
This commit is contained in:
Marco Bubke
2020-05-05 14:05:17 +02:00
parent d5b83fc637
commit f02934458e
11 changed files with 674 additions and 143 deletions

View File

@@ -39,13 +39,18 @@ void CreateTableSqlStatementBuilder::setTableName(Utils::SmallString &&tableName
this->m_tableName = std::move(tableName); this->m_tableName = std::move(tableName);
} }
void CreateTableSqlStatementBuilder::addColumn(Utils::SmallString &&columnName, void CreateTableSqlStatementBuilder::addColumn(Utils::SmallStringView columnName,
ColumnType columnType, ColumnType columnType,
Contraint constraint) Contraint constraint,
ForeignKey &&foreignKey)
{ {
m_sqlStatementBuilder.clear(); 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) void CreateTableSqlStatementBuilder::setColumns(const SqliteColumns &columns)
@@ -97,17 +102,70 @@ bool CreateTableSqlStatementBuilder::isValid() const
return m_tableName.hasContent() && !m_columns.empty(); 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 void CreateTableSqlStatementBuilder::bindColumnDefinitions() const
{ {
Utils::SmallStringVector columnDefinitionStrings; Utils::SmallStringVector columnDefinitionStrings;
columnDefinitionStrings.reserve(m_columns.size());
for (const Column &columns : m_columns) { for (const Column &column : m_columns) {
Utils::SmallString columnDefinitionString = {columns.name(), " ", columns.typeString()}; Utils::SmallString columnDefinitionString = {column.name, " ", column.typeString()};
switch (columns.constraint()) { switch (column.constraint) {
case Contraint::PrimaryKey: columnDefinitionString.append(" PRIMARY KEY"); break; case Contraint::PrimaryKey:
case Contraint::Unique: columnDefinitionString.append(" UNIQUE"); break; columnDefinitionString.append(" PRIMARY KEY");
case Contraint::NoConstraint: break; break;
case Contraint::Unique:
columnDefinitionString.append(" UNIQUE");
break;
case Contraint::ForeignKey:
appendForeignKey(columnDefinitionString, column.foreignKey);
break;
case Contraint::NoConstraint:
break;
} }
columnDefinitionStrings.push_back(columnDefinitionString); columnDefinitionStrings.push_back(columnDefinitionString);

View File

@@ -36,9 +36,10 @@ public:
CreateTableSqlStatementBuilder(); CreateTableSqlStatementBuilder();
void setTableName(Utils::SmallString &&tableName); void setTableName(Utils::SmallString &&tableName);
void addColumn(Utils::SmallString &&columnName, void addColumn(Utils::SmallStringView columnName,
ColumnType columnType, ColumnType columnType,
Contraint constraint = Contraint::NoConstraint); Contraint constraint = Contraint::NoConstraint,
ForeignKey &&foreignKey = {});
void setColumns(const SqliteColumns &columns); void setColumns(const SqliteColumns &columns);
void setUseWithoutRowId(bool useWithoutRowId); void setUseWithoutRowId(bool useWithoutRowId);
void setUseIfNotExists(bool useIfNotExists); void setUseIfNotExists(bool useIfNotExists);

View File

@@ -28,6 +28,7 @@ HEADERS += \
$$PWD/sqlitedatabasebackend.h \ $$PWD/sqlitedatabasebackend.h \
$$PWD/sqlitedatabaseinterface.h \ $$PWD/sqlitedatabaseinterface.h \
$$PWD/sqliteexception.h \ $$PWD/sqliteexception.h \
$$PWD/sqliteforeignkey.h \
$$PWD/sqliteglobal.h \ $$PWD/sqliteglobal.h \
$$PWD/sqlitereadstatement.h \ $$PWD/sqlitereadstatement.h \
$$PWD/sqlitereadwritestatement.h \ $$PWD/sqlitereadwritestatement.h \
@@ -45,7 +46,8 @@ HEADERS += \
$$PWD/sqlitebasestatement.h $$PWD/sqlitebasestatement.h
DEFINES += SQLITE_THREADSAFE=2 SQLITE_ENABLE_FTS4 SQLITE_ENABLE_FTS3_PARENTHESIS \ 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 OTHER_FILES += README.md

View File

@@ -25,7 +25,7 @@
#pragma once #pragma once
#include "sqliteglobal.h" #include "sqliteforeignkey.h"
#include <utils/smallstring.h> #include <utils/smallstring.h>
@@ -38,59 +38,60 @@ class Column
public: public:
Column() = default; Column() = default;
Column(Utils::SmallString &&name, Column(Utils::SmallStringView tableName,
Utils::SmallStringView name,
ColumnType type = ColumnType::Numeric, ColumnType type = ColumnType::Numeric,
Contraint constraint = Contraint::NoConstraint) Contraint constraint = Contraint::NoConstraint,
: m_name(std::move(name)), ForeignKey &&foreignKey = {})
m_type(type), : foreignKey(std::move(foreignKey))
m_constraint(constraint) , 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() void clear()
{ {
m_name.clear(); name.clear();
m_type = ColumnType::Numeric; type = ColumnType::Numeric;
m_constraint = Contraint::NoConstraint; constraint = Contraint::NoConstraint;
} foreignKey = {};
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;
} }
Utils::SmallString typeString() const Utils::SmallString typeString() const
{ {
switch (m_type) { switch (type) {
case ColumnType::None: return {}; case ColumnType::None:
case ColumnType::Numeric: return "NUMERIC"; return {};
case ColumnType::Integer: return "INTEGER"; case ColumnType::Numeric:
case ColumnType::Real: return "REAL"; return "NUMERIC";
case ColumnType::Text: return "TEXT"; case ColumnType::Integer:
return "INTEGER";
case ColumnType::Real:
return "REAL";
case ColumnType::Text:
return "TEXT";
} }
Q_UNREACHABLE(); Q_UNREACHABLE();
@@ -98,16 +99,18 @@ public:
friend bool operator==(const Column &first, const Column &second) friend bool operator==(const Column &first, const Column &second)
{ {
return first.m_name == second.m_name return first.name == second.name && first.type == second.type
&& first.m_type == second.m_type && first.constraint
&& first.m_constraint == second.m_constraint; == second.constraint /* && first.foreignKey == second.foreignKey*/;
} }
private: public:
Utils::SmallString m_name; ForeignKey foreignKey;
ColumnType m_type = ColumnType::Numeric; Utils::SmallString name;
Contraint m_constraint = Contraint::NoConstraint; Utils::SmallString tableName;
}; ColumnType type = ColumnType::Numeric;
Contraint constraint = Contraint::NoConstraint;
}; // namespace Sqlite
using SqliteColumns = std::vector<Column>; using SqliteColumns = std::vector<Column>;
using SqliteColumnConstReference = std::reference_wrapper<const Column>; using SqliteColumnConstReference = std::reference_wrapper<const Column>;

View File

@@ -272,4 +272,12 @@ public:
{} {}
}; };
class ForeignKeyColumnIsNotUnique : public Exception
{
public:
ForeignKeyColumnIsNotUnique(const char *whatErrorHasHappen)
: Exception(whatErrorHasHappen)
{}
};
} // namespace Sqlite } // namespace Sqlite

View File

@@ -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 <utils/smallstring.h>
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

View File

@@ -48,17 +48,13 @@ enum class ColumnType : char
None None
}; };
enum class Contraint : char enum class Contraint : char { NoConstraint, PrimaryKey, Unique, ForeignKey };
{
NoConstraint,
PrimaryKey,
Unique
};
enum class ColumnConstraint : char enum class ForeignKeyAction : char { NoAction, Restrict, SetNull, SetDefault, Cascade };
{
PrimaryKey enum class Enforment : char { Immediate, Deferred };
};
enum class ColumnConstraint : char { PrimaryKey };
enum class JournalMode : char enum class JournalMode : char
{ {

View File

@@ -44,10 +44,7 @@ public:
m_sqliteIndices.reserve(reserve); m_sqliteIndices.reserve(reserve);
} }
void setName(Utils::SmallString &&name) void setName(Utils::SmallStringView name) { m_tableName = name; }
{
m_tableName = std::move(name);
}
Utils::SmallStringView name() const Utils::SmallStringView name() const
{ {
@@ -74,11 +71,53 @@ public:
m_useTemporaryTable = useTemporaryTable; m_useTemporaryTable = useTemporaryTable;
} }
Column &addColumn(Utils::SmallString &&name, Column &addColumn(Utils::SmallStringView name,
ColumnType type = ColumnType::Numeric, ColumnType type = ColumnType::Numeric,
Contraint constraint = Contraint::NoConstraint) 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(); return m_sqliteColumns.back();
} }
@@ -148,7 +187,7 @@ private:
Utils::SmallStringVector columnNames; Utils::SmallStringVector columnNames;
for (const Column &column : columns) for (const Column &column : columns)
columnNames.push_back(column.name()); columnNames.push_back(column.name);
return columnNames; return columnNames;
} }

View File

@@ -30,13 +30,14 @@
namespace { namespace {
using Sqlite::Column;
using Sqlite::ColumnType; using Sqlite::ColumnType;
using Sqlite::Contraint; using Sqlite::Contraint;
using Sqlite::Enforment;
using Sqlite::ForeignKeyAction;
using Sqlite::JournalMode; using Sqlite::JournalMode;
using Sqlite::OpenMode; using Sqlite::OpenMode;
using Sqlite::Column;
using Sqlite::SqliteColumns; using Sqlite::SqliteColumns;
using Sqlite::SqlStatementBuilderException; using Sqlite::SqlStatementBuilderException;
class CreateTableSqlStatementBuilder : public ::testing::Test class CreateTableSqlStatementBuilder : public ::testing::Test
@@ -199,11 +200,210 @@ void CreateTableSqlStatementBuilder::bindValues()
SqliteColumns CreateTableSqlStatementBuilder::createColumns() SqliteColumns CreateTableSqlStatementBuilder::createColumns()
{ {
SqliteColumns columns; SqliteColumns columns;
columns.emplace_back("id", ColumnType::Integer, Contraint::PrimaryKey); columns.emplace_back("", "id", ColumnType::Integer, Contraint::PrimaryKey);
columns.emplace_back("name", ColumnType::Text); columns.emplace_back("", "name", ColumnType::Text);
columns.emplace_back("number", ColumnType::Numeric); columns.emplace_back("", "number", ColumnType::Numeric);
return columns; 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

View File

@@ -29,70 +29,111 @@
namespace { namespace {
using testing::AllOf;
using testing::Contains;
using testing::Property;
using Sqlite::ColumnType; using Sqlite::ColumnType;
using Sqlite::Contraint; using Sqlite::Contraint;
using Sqlite::JournalMode; using Sqlite::JournalMode;
using Sqlite::OpenMode; using Sqlite::OpenMode;
using Column = Sqlite::Column; using Column = Sqlite::Column;
using Sqlite::Enforment;
using Sqlite::ForeignKey;
using Sqlite::ForeignKeyAction;
using Sqlite::SqliteColumns; using Sqlite::SqliteColumns;
class SqliteColumn : public ::testing::Test class SqliteColumn : public ::testing::Test
{ {
protected: protected:
void SetUp() override;
Sqlite::Column column; Sqlite::Column column;
}; };
TEST_F(SqliteColumn, ChangeName) TEST_F(SqliteColumn, DefaultConstruct)
{ {
column.setName("Claudia"); ASSERT_THAT(column,
AllOf(Field(&Column::name, IsEmpty()),
ASSERT_THAT(column.name(), "Claudia"); 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.clear();
{
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");
ASSERT_THAT(column, ASSERT_THAT(column,
AllOf( AllOf(Field(&Column::name, IsEmpty()),
Property(&Column::name, "Claudia"), Field(&Column::tableName, IsEmpty()),
Property(&Column::type, ColumnType::Numeric), Field(&Column::type, ColumnType::Numeric),
Property(&Column::constraint, Contraint::NoConstraint))); 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

View File

@@ -32,11 +32,15 @@
namespace { namespace {
using Sqlite::Column;
using Sqlite::ColumnType; using Sqlite::ColumnType;
using Sqlite::Contraint;
using Sqlite::Database;
using Sqlite::Enforment;
using Sqlite::ForeignKey;
using Sqlite::ForeignKeyAction;
using Sqlite::JournalMode; using Sqlite::JournalMode;
using Sqlite::OpenMode; using Sqlite::OpenMode;
using Sqlite::Column;
using Sqlite::Database;
class SqliteTable : public ::testing::Test class SqliteTable : public ::testing::Test
{ {
@@ -110,21 +114,135 @@ TEST_F(SqliteTable, InitializeTableWithIndex)
table.initialize(mockDatabase); table.initialize(mockDatabase);
} }
TEST_F(SqliteTable, AddForeignKeyColumnWithTableCalls)
TEST_F(SqliteTable, InitializeTableWithUniqueIndex)
{ {
InSequence sequence; Sqlite::Table foreignTable;
table.setName(tableName.clone()); foreignTable.setName("foreignTable");
auto &column = table.addColumn("name"); table.setName(tableName);
auto &column2 = table.addColumn("value"); table.addForeignKeyColumn("name",
table.addUniqueIndex({column}); foreignTable,
table.addIndex({column2}); ForeignKeyAction::SetNull,
ForeignKeyAction::Cascade,
Enforment::Deferred);
EXPECT_CALL(mockDatabase, execute(Eq("CREATE TABLE testTable(name NUMERIC, value NUMERIC)"))); EXPECT_CALL(mockDatabase,
EXPECT_CALL(mockDatabase, execute(Eq("CREATE UNIQUE INDEX IF NOT EXISTS index_testTable_name ON testTable(name)"))); execute(Eq("CREATE TABLE testTable(name INTEGER REFERENCES foreignTable ON UPDATE "
EXPECT_CALL(mockDatabase, execute(Eq("CREATE INDEX IF NOT EXISTS index_testTable_value ON testTable(value)"))); "SET NULL ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)")));
table.initialize(mockDatabase); 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