Sqlite: Improve constraint support

Now you can add more than one constraint. And we added some
new constraints too.

Change-Id: I849d2d2ef6e44c897a65ff2bdfe8d172a345c991
Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
This commit is contained in:
Marco Bubke
2020-05-13 20:29:49 +02:00
committed by Tim Jenssen
parent a86fd58e40
commit c4bbc74e37
12 changed files with 497 additions and 242 deletions

View File

@@ -41,16 +41,11 @@ void CreateTableSqlStatementBuilder::setTableName(Utils::SmallString &&tableName
void CreateTableSqlStatementBuilder::addColumn(Utils::SmallStringView columnName,
ColumnType columnType,
Contraint constraint,
ForeignKey &&foreignKey)
Constraints &&constraints)
{
m_sqlStatementBuilder.clear();
m_columns.emplace_back(Utils::SmallStringView{},
columnName,
columnType,
constraint,
std::move(foreignKey));
m_columns.emplace_back(Utils::SmallStringView{}, columnName, columnType, std::move(constraints));
}
void CreateTableSqlStatementBuilder::setColumns(const SqliteColumns &columns)
@@ -121,30 +116,90 @@ Utils::SmallStringView actionToText(ForeignKeyAction action)
return "";
}
void appendForeignKey(Utils::SmallString &columnDefinitionString, const ForeignKey &foreignKey)
class ContraintsVisiter
{
columnDefinitionString.append(" REFERENCES ");
columnDefinitionString.append(foreignKey.table);
public:
ContraintsVisiter(Utils::SmallString &columnDefinitionString)
: columnDefinitionString(columnDefinitionString)
{}
if (foreignKey.column.hasContent()) {
columnDefinitionString.append("(");
columnDefinitionString.append(foreignKey.column);
void operator()(const Unique &) { columnDefinitionString.append(" UNIQUE"); }
void operator()(const PrimaryKey &) { columnDefinitionString.append(" PRIMARY KEY"); }
void operator()(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");
}
void operator()(const NotNull &) { columnDefinitionString.append(" NOT NULL"); }
void operator()(const DefaultValue &defaultValue)
{
columnDefinitionString.append(" DEFAULT ");
switch (defaultValue.value.type()) {
case Sqlite::ValueType::Integer:
columnDefinitionString.append(
Utils::SmallString::number(defaultValue.value.toInteger()));
break;
case Sqlite::ValueType::Float:
columnDefinitionString.append(Utils::SmallString::number(defaultValue.value.toFloat()));
break;
case Sqlite::ValueType::String:
columnDefinitionString.append("\"");
columnDefinitionString.append(defaultValue.value.toStringView());
columnDefinitionString.append("\"");
break;
}
}
void operator()(const DefaultExpression &defaultexpression)
{
columnDefinitionString.append(" DEFAULT (");
columnDefinitionString.append(defaultexpression.expression);
columnDefinitionString.append(")");
}
if (foreignKey.updateAction != ForeignKeyAction::NoAction) {
columnDefinitionString.append(" ON UPDATE ");
columnDefinitionString.append(actionToText(foreignKey.updateAction));
void operator()(const Collate &collate)
{
columnDefinitionString.append(" COLLATE ");
columnDefinitionString.append(collate.collation);
}
if (foreignKey.deleteAction != ForeignKeyAction::NoAction) {
columnDefinitionString.append(" ON DELETE ");
columnDefinitionString.append(actionToText(foreignKey.deleteAction));
void operator()(const GeneratedAlways &generatedAlways)
{
columnDefinitionString.append(" GENERATED ALWAYS AS (");
columnDefinitionString.append(generatedAlways.expression);
columnDefinitionString.append(")");
if (generatedAlways.storage == Sqlite::GeneratedAlwaysStorage::Virtual)
columnDefinitionString.append(" VIRTUAL");
else
columnDefinitionString.append(" STORED");
}
if (foreignKey.enforcement == Enforment::Deferred)
columnDefinitionString.append(" DEFERRABLE INITIALLY DEFERRED");
}
Utils::SmallString &columnDefinitionString;
};
} // namespace
void CreateTableSqlStatementBuilder::bindColumnDefinitions() const
{
@@ -154,19 +209,10 @@ void CreateTableSqlStatementBuilder::bindColumnDefinitions() const
for (const Column &column : m_columns) {
Utils::SmallString columnDefinitionString = {column.name, " ", column.typeString()};
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;
}
ContraintsVisiter visiter{columnDefinitionString};
for (const Constraint &constraint : column.constraints)
mpark::visit(visiter, constraint);
columnDefinitionStrings.push_back(columnDefinitionString);
}

View File

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

View File

@@ -27,56 +27,135 @@
#include "sqliteforeignkey.h"
#include <sqlitevalue.h>
#include <utils/smallstring.h>
#include <utils/variant.h>
#include <functional>
namespace Sqlite {
class Unique
{
friend bool operator==(Unique, Unique) { return true; }
};
class PrimaryKey
{
friend bool operator==(PrimaryKey, PrimaryKey) { return true; }
};
class NotNull
{
friend bool operator==(NotNull, NotNull) { return true; }
};
class DefaultValue
{
public:
DefaultValue(long long value)
: value(value)
{}
DefaultValue(double value)
: value(value)
{}
DefaultValue(Utils::SmallStringView value)
: value(value)
{}
friend bool operator==(const DefaultValue &first, const DefaultValue &second)
{
return first.value == second.value;
}
public:
Sqlite::Value value;
};
class DefaultExpression
{
public:
DefaultExpression(Utils::SmallStringView expression)
: expression(expression)
{}
friend bool operator==(const DefaultExpression &first, const DefaultExpression &second)
{
return first.expression == second.expression;
}
public:
Utils::SmallString expression;
};
class Collate
{
public:
Collate(Utils::SmallStringView collation)
: collation(collation)
{}
friend bool operator==(const Collate &first, const Collate &second)
{
return first.collation == second.collation;
}
public:
Utils::SmallString collation;
};
enum class GeneratedAlwaysStorage { Stored, Virtual };
class GeneratedAlways
{
public:
GeneratedAlways(Utils::SmallStringView expression, GeneratedAlwaysStorage storage)
: expression(expression)
, storage(storage)
{}
friend bool operator==(const GeneratedAlways &first, const GeneratedAlways &second)
{
return first.expression == second.expression;
}
public:
Utils::SmallString expression;
GeneratedAlwaysStorage storage = {};
};
using Constraint = Utils::variant<Unique,
PrimaryKey,
ForeignKey,
NotNull,
DefaultValue,
DefaultExpression,
Collate,
GeneratedAlways>;
using Constraints = std::vector<Constraint>;
class Column
{
public:
Column() = default;
Column(Utils::SmallStringView tableName,
Utils::SmallStringView name,
ColumnType type = ColumnType::Numeric,
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)
Constraints &&constraints = {})
: constraints(std::move(constraints))
, name(name)
, tableName(tableName)
, type(type)
, constraint(constraint)
{}
void clear()
{
name.clear();
type = ColumnType::Numeric;
constraint = Contraint::NoConstraint;
foreignKey = {};
constraints = {};
}
Utils::SmallString typeString() const
@@ -100,16 +179,14 @@ public:
friend bool operator==(const Column &first, const Column &second)
{
return first.name == second.name && first.type == second.type
&& first.constraint
== second.constraint /* && first.foreignKey == second.foreignKey*/;
&& first.constraints == second.constraints && first.tableName == second.tableName;
}
public:
ForeignKey foreignKey;
Constraints constraints;
Utils::SmallString name;
Utils::SmallString tableName;
ColumnType type = ColumnType::Numeric;
Contraint constraint = Contraint::NoConstraint;
}; // namespace Sqlite
using SqliteColumns = std::vector<Column>;

View File

@@ -48,7 +48,7 @@ enum class ColumnType : char
None
};
enum class Contraint : char { NoConstraint, PrimaryKey, Unique, ForeignKey };
enum class ConstraintType : char { NoConstraint, PrimaryKey, Unique, ForeignKey };
enum class ForeignKeyAction : char { NoAction, Restrict, SetNull, SetDefault, Cascade };

View File

@@ -73,9 +73,9 @@ public:
Column &addColumn(Utils::SmallStringView name,
ColumnType type = ColumnType::Numeric,
Contraint constraint = Contraint::NoConstraint)
Constraints &&constraints = {})
{
m_sqliteColumns.emplace_back(m_tableName, name, type, constraint);
m_sqliteColumns.emplace_back(m_tableName, name, type, std::move(constraints));
return m_sqliteColumns.back();
}
@@ -85,17 +85,16 @@ public:
ForeignKeyAction foreignKeyupdateAction = {},
ForeignKeyAction foreignKeyDeleteAction = {},
Enforment foreignKeyEnforcement = {},
Constraints &&constraints = {},
ColumnType type = ColumnType::Integer)
{
m_sqliteColumns.emplace_back(m_tableName,
name,
type,
Contraint::ForeignKey,
referencedTable.name(),
"",
foreignKeyupdateAction,
foreignKeyDeleteAction,
foreignKeyEnforcement);
constraints.emplace_back(ForeignKey{referencedTable.name(),
"",
foreignKeyupdateAction,
foreignKeyDeleteAction,
foreignKeyEnforcement});
m_sqliteColumns.emplace_back(m_tableName, name, type, std::move(constraints));
return m_sqliteColumns.back();
}
@@ -104,20 +103,22 @@ public:
const Column &referencedColumn,
ForeignKeyAction foreignKeyupdateAction = {},
ForeignKeyAction foreignKeyDeleteAction = {},
Enforment foreignKeyEnforcement = {})
Enforment foreignKeyEnforcement = {},
Constraints &&constraints = {})
{
if (referencedColumn.constraint != Contraint::Unique)
if (!constainsUniqueIndex(referencedColumn.constraints))
throw ForeignKeyColumnIsNotUnique("Foreign column key must be unique!");
constraints.emplace_back(ForeignKey{referencedColumn.tableName,
referencedColumn.name,
foreignKeyupdateAction,
foreignKeyDeleteAction,
foreignKeyEnforcement});
m_sqliteColumns.emplace_back(m_tableName,
name,
referencedColumn.type,
Contraint::ForeignKey,
referencedColumn.tableName,
referencedColumn.name,
foreignKeyupdateAction,
foreignKeyDeleteAction,
foreignKeyEnforcement);
std::move(constraints));
return m_sqliteColumns.back();
}
@@ -181,6 +182,16 @@ public:
&& first.m_sqliteColumns == second.m_sqliteColumns;
}
static bool constainsUniqueIndex(const Constraints &constraints)
{
return std::find_if(constraints.begin(),
constraints.end(),
[](const Constraint &constraint) {
return Utils::holds_alternative<Unique>(constraint);
})
!= constraints.end();
}
private:
Utils::SmallStringVector sqliteColumnNames(const SqliteColumnConstReferences &columns)
{