forked from qt-creator/qt-creator
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:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sqliteglobal.h"
|
||||
#include "sqliteforeignkey.h"
|
||||
|
||||
#include <utils/smallstring.h>
|
||||
|
||||
@@ -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<Column>;
|
||||
using SqliteColumnConstReference = std::reference_wrapper<const Column>;
|
||||
|
||||
@@ -272,4 +272,12 @@ public:
|
||||
{}
|
||||
};
|
||||
|
||||
class ForeignKeyColumnIsNotUnique : public Exception
|
||||
{
|
||||
public:
|
||||
ForeignKeyColumnIsNotUnique(const char *whatErrorHasHappen)
|
||||
: Exception(whatErrorHasHappen)
|
||||
{}
|
||||
};
|
||||
|
||||
} // namespace Sqlite
|
||||
|
||||
65
src/libs/sqlite/sqliteforeignkey.h
Normal file
65
src/libs/sqlite/sqliteforeignkey.h
Normal 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
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user