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);
}
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);

View File

@@ -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);

View File

@@ -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

View File

@@ -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>;

View File

@@ -272,4 +272,12 @@ public:
{}
};
class ForeignKeyColumnIsNotUnique : public Exception
{
public:
ForeignKeyColumnIsNotUnique(const char *whatErrorHasHappen)
: Exception(whatErrorHasHappen)
{}
};
} // 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
};
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
{

View File

@@ -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;
}