Refactor data structures for table constraints

This is a long overdue continuation of some previous refactoring effort.
Before this we used to store the columns a table constraint belongs to
within the constraint object itself. So for example, a foreign key
constraint object would store the referencing as well as the referenced
column names. While initially simple, this approach has the downside of
duplicating certain data, thus breaking ownership and complicating
matters later on. This becomes obvious when renaming the referencing
column. The column name clearly is a feature of the table but in the
previous approach it also needs to be changed in the foreign key object
as well as in any other constraint for this field even though the
constraint itself has not been touched. This illustrates how a
constraint is not only a property of a table but the field names (a
property of the table) are also a property of the constraint, creating a
circular ownership. This makes the code hard to maintain. It also
invalidates references to constraints in the program needlessly, e.g.
when only changing a column name.

With this commit the column names are removed from the constraint types.
Instead they are now solely a property of the table. This, however,
raised another issue. For unique constraints and primary keys it is
possible to use expressions and/or sorted keys whereas for foreign keys
this is not possible. Additionally check constraints have no columns at
all. So when not storing the used columns inside the constraint objects
we need to have different storage types for each of them. So in a second
step this commit moves the code from a single data structure for storing
all table constraints to three data structures, one for PK and unique,
one for foreign keys, and one for check constraints.

By doing all this, this commit also changes the interface for handling
quite a bit. The new interface tends to use more explicit types which
makes the usage code easier to read.

Please note that this is still far from finished. But future development
on this should be a lot easier now.
This commit is contained in:
Martin Kleusberg
2022-03-21 14:57:29 +01:00
parent e55fed2473
commit 43107ea773
15 changed files with 1250 additions and 1209 deletions

View File

@@ -184,8 +184,8 @@ void AddRecordDialog::populateFields()
ui->treeWidget->setItemDelegateForColumn(kValue, new EditDelegate(this));
sqlb::FieldVector fields;
std::vector<sqlb::ConstraintPtr> fks;
sqlb::StringVector pk;
std::vector<std::shared_ptr<sqlb::ForeignKeyClause>> fks;
sqlb::IndexedColumnVector pk;
bool auto_increment = false;
// Initialize fields, fks and pk differently depending on whether it's a table or a view.
@@ -194,19 +194,19 @@ void AddRecordDialog::populateFields()
if (!table->isView())
{
std::transform(fields.begin(), fields.end(), std::back_inserter(fks), [table](const auto& f) {
return table->constraint({f.name()}, sqlb::Constraint::ForeignKeyConstraintType);
return table->foreignKey({f.name()});
});
const auto pk_constraint = table->primaryKey();
if(pk_constraint)
{
pk = pk_constraint->columnList();
pk = table->primaryKeyColumns();
auto_increment = pk_constraint->autoIncrement();
}
} else {
// It's a view
fks.resize(fields.size(), sqlb::ConstraintPtr(nullptr));
pk = pseudo_pk;
fks.resize(fields.size(), nullptr);
std::transform(pseudo_pk.begin(), pseudo_pk.end(), std::back_inserter(pk), [](const auto& e) { return sqlb::IndexedColumn(e, false); });
}
for(uint i = 0; i < fields.size(); i++)
@@ -244,7 +244,7 @@ void AddRecordDialog::populateFields()
if (!f.check().empty())
toolTip.append(tr("Check constraint:\t %1\n").arg(QString::fromStdString(f.check())));
auto fk = std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(fks[i]);
auto fk = fks[i];
if(fk)
toolTip.append(tr("Foreign key:\t %1\n").arg(QString::fromStdString(fk->toString())));

View File

@@ -334,18 +334,18 @@ void DbStructureModel::buildTree(QTreeWidgetItem* parent, const std::string& sch
// Add an extra node for the browsable section
addNode(schema, obj.second->name(), obj.second->isView() ? "view" : "table", obj.second->originalSql(), browsablesRootItem);
sqlb::StringVector pk_columns;
sqlb::IndexedColumnVector pk_columns;
if(!obj.second->isView())
{
const auto pk = obj.second->primaryKey();
if(pk)
pk_columns = pk->columnList();
pk_columns = obj.second->primaryKeyColumns();
}
for(const auto& field : obj.second->fields)
{
bool isPK = contains(pk_columns, field.name());
bool isFK = obj.second->constraint({field.name()}, sqlb::Constraint::ForeignKeyConstraintType) != nullptr;
bool isFK = obj.second->foreignKey({field.name()}) != nullptr;
addNode(schema, field.name(), "field", field.toString(" ", " "), item, field.type(), isPK ? "_key" : (isFK ? "_fk" : std::string{}));
}

View File

@@ -16,6 +16,8 @@
#include <algorithm>
Q_DECLARE_METATYPE(sqlb::ConstraintPtr)
Q_DECLARE_METATYPE(sqlb::StringVector)
Q_DECLARE_METATYPE(sqlb::IndexedColumnVector)
// Styled Item Delegate for non-editable columns
class NoEditDelegate: public QStyledItemDelegate {
@@ -133,29 +135,40 @@ EditTableDialog::EditTableDialog(DBBrowserDB& db, const sqlb::ObjectIdentifier&
// Check whether the double clicked item is in the columns column
if(item->column() == kConstraintColumns)
{
sqlb::ConstraintPtr constraint = ui->tableConstraints->item(item->row(), kConstraintColumns)->data(Qt::UserRole).value<sqlb::ConstraintPtr>();
sqlb::StringVector columns = ui->tableConstraints->item(item->row(), kConstraintColumns)->data(Qt::UserRole).value<sqlb::StringVector>();
if(columns.empty())
{
// When we've got no columns, try the other type of columns vector
sqlb::IndexedColumnVector indexed_columns = ui->tableConstraints->item(item->row(), kConstraintColumns)->data(Qt::UserRole).value<sqlb::IndexedColumnVector>();
std::transform(indexed_columns.begin(), indexed_columns.end(), std::back_inserter(columns), [](const auto& e) { return e.name(); });
}
sqlb::ConstraintPtr constraint = ui->tableConstraints->item(item->row(), kConstraintType)->data(Qt::UserRole).value<sqlb::ConstraintPtr>();
// Do not allow editing the columns list of a CHECK constraint because CHECK constraints are independent of column lists
if(constraint->type() == sqlb::Constraint::CheckConstraintType)
return;
// Show the select items popup dialog
SelectItemsPopup* dialog = new SelectItemsPopup(m_table.fieldNames(), item->data(Qt::UserRole).value<sqlb::ConstraintPtr>()->columnList(), this);
SelectItemsPopup* dialog = new SelectItemsPopup(m_table.fieldNames(), columns, this);
QRect item_rect = ui->tableConstraints->visualItemRect(item);
dialog->move(ui->tableConstraints->mapToGlobal(QPoint(ui->tableConstraints->x() + item_rect.x(),
ui->tableConstraints->y() + item_rect.y() + item_rect.height() / 2)));
dialog->show();
// When clicking the Apply button in the popup dialog, save the new columns list
connect(dialog, &SelectItemsPopup::accepted, this, [this, dialog, constraint]() {
connect(dialog, &SelectItemsPopup::accepted, this, [this, dialog, constraint, columns]() {
// Check if column selection changed at all
sqlb::StringVector new_columns = dialog->selectedItems();
if(constraint->columnList() != new_columns)
if(columns != new_columns)
{
// Remove the constraint with the old columns and add a new one with the new columns
m_table.removeConstraint(constraint);
constraint->setColumnList(new_columns);
m_table.addConstraint(constraint);
if(constraint->type() == sqlb::Constraint::PrimaryKeyConstraintType)
m_table.addConstraint(new_columns, std::dynamic_pointer_cast<sqlb::PrimaryKeyConstraint>(constraint));
else if(constraint->type() == sqlb::Constraint::UniqueConstraintType)
m_table.addConstraint(new_columns, std::dynamic_pointer_cast<sqlb::UniqueConstraint>(constraint));
else if(constraint->type() == sqlb::Constraint::ForeignKeyConstraintType)
m_table.addConstraint(new_columns, std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(constraint));
// Update the UI
populateFields();
@@ -214,6 +227,7 @@ void EditTableDialog::populateFields()
ui->treeWidget->clear();
const auto& fields = m_table.fields;
const auto pk = m_table.primaryKey();
const auto pkColumns = m_table.primaryKeyColumns();
for(const sqlb::Field& f : fields)
{
QTreeWidgetItem *tbitem = new QTreeWidgetItem(ui->treeWidget);
@@ -237,8 +251,8 @@ void EditTableDialog::populateFields()
ui->treeWidget->setItemWidget(tbitem, kType, typeBox);
tbitem->setCheckState(kNotNull, f.notnull() ? Qt::Checked : Qt::Unchecked);
tbitem->setCheckState(kPrimaryKey, pk && contains(pk->columnList(), f.name()) ? Qt::Checked : Qt::Unchecked);
tbitem->setCheckState(kAutoIncrement, pk && pk->autoIncrement() && contains(pk->columnList(), f.name()) ? Qt::Checked : Qt::Unchecked);
tbitem->setCheckState(kPrimaryKey, pk && contains(pkColumns, f.name()) ? Qt::Checked : Qt::Unchecked);
tbitem->setCheckState(kAutoIncrement, pk && pk->autoIncrement() && contains(pkColumns, f.name()) ? Qt::Checked : Qt::Unchecked);
tbitem->setCheckState(kUnique, f.unique() ? Qt::Checked : Qt::Unchecked);
// For the default value check if it is surrounded by parentheses and if that's the case
@@ -266,7 +280,7 @@ void EditTableDialog::populateFields()
connect(collationBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updateTypeAndCollation()));
ui->treeWidget->setItemWidget(tbitem, kCollation, collationBox);
auto fk = std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(m_table.constraint({f.name()}, sqlb::Constraint::ForeignKeyConstraintType));
auto fk = m_table.foreignKey({f.name()});
if(fk)
tbitem->setText(kForeignKey, QString::fromStdString(fk->toString()));
ui->treeWidget->addTopLevelItem(tbitem);
@@ -281,28 +295,27 @@ void EditTableDialog::populateConstraints()
// Disable the itemChanged signal or the table item will be updated while filling the treewidget
ui->tableConstraints->blockSignals(true);
const auto& constraints = m_table.allConstraints();
const auto& indexConstraints = m_table.indexConstraints();
const auto& foreignKeys = m_table.foreignKeys();
const auto& checkConstraints = m_table.checkConstraints();
ui->tableConstraints->setRowCount(static_cast<int>(constraints.size()));
int row = 0;
for(const auto& constraint : constraints)
{
const auto columns = constraint->columnList();
ui->tableConstraints->setRowCount(static_cast<int>(indexConstraints.size() + foreignKeys.size() + checkConstraints.size()));
auto setRow = [this](int row, const auto& columns, const sqlb::ConstraintPtr constraint, const std::string& sql_text) {
// Columns
QTableWidgetItem* column = new QTableWidgetItem(QString::fromStdString(sqlb::joinStringVector(columns, ",")));
column->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
column->setData(Qt::UserRole, QVariant::fromValue<sqlb::ConstraintPtr>(constraint)); // Remember address of constraint object. This is used for modifying it later
column->setData(Qt::UserRole, QVariant::fromValue(columns)); // Remember columns of constraint object. This is used for modifying it later
ui->tableConstraints->setItem(row, kConstraintColumns, column);
// Type
QComboBox* type = new QComboBox(this);
type->addItem(tr("Primary Key")); // NOTE: The order of the items here have to match the order in the sqlb::Constraint::ConstraintTypes enum!
type->addItem(tr("Unique"));
type->addItem(tr("Foreign Key"));
type->addItem(tr("Check"));
type->addItem(tr("Primary Key"), "pk");
type->addItem(tr("Unique"), "u");
type->addItem(tr("Foreign Key"), "fk");
type->addItem(tr("Check"), "c");
type->setCurrentIndex(constraint->type());
connect(type, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [this, type, constraint](int index) {
connect(type, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [this, type, columns, constraint](int index) {
// Handle change of constraint type. Effectively this means removing the old constraint and replacing it by an entirely new one.
// Only the column list and the name can be migrated to the new constraint.
@@ -319,20 +332,39 @@ void EditTableDialog::populateConstraints()
return;
}
// Create new constraint depending on selected type
sqlb::ConstraintPtr new_constraint = sqlb::Constraint::makeConstraint(static_cast<sqlb::Constraint::ConstraintTypes>(index));
new_constraint->setName(constraint->name());
new_constraint->setColumnList(constraint->columnList());
// Remove old constraint
m_table.removeConstraint(constraint);
// Replace old by new constraint
m_table.replaceConstraint(constraint, new_constraint);
// Add new constraint depending on selected type
sqlb::ConstraintPtr new_constraint;
if(type->itemData(index).toString() == "pk")
{
auto pk = std::make_shared<sqlb::PrimaryKeyConstraint>();
pk->setName(constraint->name());
m_table.addConstraint(columns, pk);
} else if(type->itemData(index).toString() == "u") {
auto u = std::make_shared<sqlb::UniqueConstraint>();
u->setName(constraint->name());
m_table.addConstraint(columns, u);
} else if(type->itemData(index).toString() == "fk") {
auto fk = std::make_shared<sqlb::ForeignKeyClause>();
fk->setName(constraint->name());
m_table.addConstraint(columns, fk);
} else if(type->itemData(index).toString() == "c") {
auto c = std::make_shared<sqlb::CheckConstraint>();
c->setName(constraint->name());
m_table.addConstraint(c);
}
// Update SQL and view
populateFields();
populateConstraints();
updateSqlText();
});
QTableWidgetItem* typeColumn = new QTableWidgetItem();
typeColumn->setData(Qt::UserRole, QVariant::fromValue<sqlb::ConstraintPtr>(constraint)); // Remember address of constraint object. This is used for modifying it later
ui->tableConstraints->setCellWidget(row, kConstraintType, type);
ui->tableConstraints->setItem(row, kConstraintType, typeColumn);
// Name
QTableWidgetItem* name = new QTableWidgetItem(QString::fromStdString(constraint->name()));
@@ -340,12 +372,18 @@ void EditTableDialog::populateConstraints()
ui->tableConstraints->setItem(row, kConstraintName, name);
// SQL
QTableWidgetItem* sql = new QTableWidgetItem(QString::fromStdString(constraint->toSql()));
QTableWidgetItem* sql = new QTableWidgetItem(QString::fromStdString(sql_text));
sql->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
ui->tableConstraints->setItem(row, kConstraintSql, sql);
};
row++;
}
int row = 0;
for(const auto& it : indexConstraints)
setRow(row++, it.first, it.second, it.second->toSql(it.first));
for(const auto& it : foreignKeys)
setRow(row++, it.first, it.second, it.second->toSql(it.first));
for(const auto& it : checkConstraints)
setRow(row++, sqlb::StringVector{}, it, it->toSql());
ui->tableConstraints->blockSignals(false);
}
@@ -407,7 +445,7 @@ void EditTableDialog::checkInput()
// update fk's that refer to table itself recursively
const auto& fields = m_table.fields;
for(const sqlb::Field& f : fields) {
auto fk = std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(m_table.constraint({f.name()}, sqlb::Constraint::ForeignKeyConstraintType));
auto fk = m_table.foreignKey({f.name()});
if(fk && oldTableName == fk->table())
fk->setTable(normTableName);
}
@@ -502,17 +540,17 @@ void EditTableDialog::fieldItemChanged(QTreeWidgetItem *item, int column)
if(!m_bNewTable)
{
const auto pk = m_table.primaryKey();
const auto pkColumns = m_table.primaryKeyColumns();
for(const auto& it : pdb.schemata[curTable.schema()].tables)
{
const sqlb::TablePtr& fkobj = it.second;
auto fks = fkobj->constraints({}, sqlb::Constraint::ForeignKeyConstraintType);
for(const sqlb::ConstraintPtr& fkptr : fks)
auto constraints = fkobj->foreignKeys();
for(const auto& fk : constraints)
{
auto fk = std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(fkptr);
if(fk->table() == m_table.name())
if(fk.second->table() == m_table.name())
{
if(contains(fk->columns(), field.name()) || (pk && contains(pk->columnList(), field.name())))
if(contains(fk.second->columns(), field.name()) || (pk && contains(pkColumns, field.name())))
{
QMessageBox::warning(this, qApp->applicationName(), tr("This column is referenced in a foreign key in table %1 and thus "
"its name cannot be changed.")
@@ -558,18 +596,12 @@ void EditTableDialog::fieldItemChanged(QTreeWidgetItem *item, int column)
{
// There already is a primary key for this table. So edit that one as there always can only be one primary key anyway.
if(item->checkState(column) == Qt::Checked)
{
pk->addToColumnList(field.name());
} else {
pk->removeFromColumnList(field.name());
// If this is now a primary key constraint without any columns, remove it entirely
if(pk->columnList().empty())
m_table.removeConstraints({}, sqlb::Constraint::PrimaryKeyConstraintType);
}
m_table.addKeyToConstraint(pk, field.name());
else
m_table.removeKeyFromConstraint(pk, field.name());
} else if(item->checkState(column) == Qt::Checked) {
// There is no primary key in the table yet. This means we need to add a default one.
m_table.addConstraint(sqlb::ConstraintPtr(new sqlb::PrimaryKeyConstraint({field.name()})));
m_table.addConstraint({sqlb::IndexedColumn(field.name(), false)}, std::make_shared<sqlb::PrimaryKeyConstraint>());
}
if(item->checkState(column) == Qt::Checked)
@@ -754,7 +786,7 @@ void EditTableDialog::fieldItemChanged(QTreeWidgetItem *item, int column)
void EditTableDialog::constraintItemChanged(QTableWidgetItem* item)
{
// Find modified constraint
sqlb::ConstraintPtr constraint = ui->tableConstraints->item(item->row(), kConstraintColumns)->data(Qt::UserRole).value<sqlb::ConstraintPtr>();
sqlb::ConstraintPtr constraint = ui->tableConstraints->item(item->row(), kConstraintType)->data(Qt::UserRole).value<sqlb::ConstraintPtr>();
// Which column has been modified?
switch(item->column())
@@ -765,7 +797,7 @@ void EditTableDialog::constraintItemChanged(QTableWidgetItem* item)
}
// Update SQL
ui->tableConstraints->item(item->row(), kConstraintSql)->setText(QString::fromStdString(constraint->toSql()));
populateConstraints();
checkInput();
}
@@ -1052,7 +1084,7 @@ void EditTableDialog::removeConstraint()
// Find constraint to delete
int row = ui->tableConstraints->currentRow();
sqlb::ConstraintPtr constraint = ui->tableConstraints->item(row, kConstraintColumns)->data(Qt::UserRole).value<sqlb::ConstraintPtr>();
sqlb::ConstraintPtr constraint = ui->tableConstraints->item(row, kConstraintType)->data(Qt::UserRole).value<sqlb::ConstraintPtr>();
// Remove the constraint. If there is more than one constraint with this combination of columns and constraint type, only delete the first one.
m_table.removeConstraint(constraint);
@@ -1074,11 +1106,15 @@ void EditTableDialog::addConstraint(sqlb::Constraint::ConstraintTypes type)
"key instead."));
return;
}
}
// Create new constraint
sqlb::ConstraintPtr constraint = sqlb::Constraint::makeConstraint(type);
m_table.addConstraint(constraint);
// Create new constraint
m_table.addConstraint(sqlb::IndexedColumnVector{}, std::make_shared<sqlb::PrimaryKeyConstraint>());
} else if(type == sqlb::Constraint::UniqueConstraintType)
m_table.addConstraint(sqlb::IndexedColumnVector{}, std::make_shared<sqlb::UniqueConstraint>());
else if(type == sqlb::Constraint::ForeignKeyConstraintType)
m_table.addConstraint(sqlb::StringVector{}, std::make_shared<sqlb::ForeignKeyClause>());
else if(type == sqlb::Constraint::CheckConstraintType)
m_table.addConstraint(std::make_shared<sqlb::CheckConstraint>());
// Update SQL and view
populateFields();

View File

@@ -131,19 +131,18 @@ QWidget* ExtendedTableWidgetEditorDelegate::createEditor(QWidget* parent, const
emit dataAboutToBeEdited(index);
SqliteTableModel* m = qobject_cast<SqliteTableModel*>(const_cast<QAbstractItemModel*>(index.model()));
sqlb::ForeignKeyClause fk = m->getForeignKeyClause(static_cast<size_t>(index.column()-1));
auto fk = m->getForeignKeyClause(static_cast<size_t>(index.column()-1));
if(fk.isSet()) {
sqlb::ObjectIdentifier foreignTable = sqlb::ObjectIdentifier(m->currentTableName().schema(), fk.table());
if(fk) {
sqlb::ObjectIdentifier foreignTable = sqlb::ObjectIdentifier(m->currentTableName().schema(), fk->table());
std::string column;
// If no column name is set, assume the primary key is meant
if(fk.columns().empty()) {
if(fk->columns().empty()) {
sqlb::TablePtr obj = m->db().getTableByName(foreignTable);
column = obj->primaryKey()->columnList().front();
column = obj->primaryKeyColumns().front().name();
} else
column = fk.columns().at(0);
column = fk->columns().at(0);
sqlb::TablePtr currentTable = m->db().getTableByName(m->currentTableName());
QString query = QString("SELECT %1 FROM %2").arg(QString::fromStdString(sqlb::escapeIdentifier(column)), QString::fromStdString(foreignTable.toString()));
@@ -1057,11 +1056,11 @@ void ExtendedTableWidget::cellClicked(const QModelIndex& index)
if(qApp->keyboardModifiers().testFlag(Qt::ControlModifier) && qApp->keyboardModifiers().testFlag(Qt::ShiftModifier) && model())
{
SqliteTableModel* m = qobject_cast<SqliteTableModel*>(model());
sqlb::ForeignKeyClause fk = m->getForeignKeyClause(static_cast<size_t>(index.column()-1));
auto fk = m->getForeignKeyClause(static_cast<size_t>(index.column()-1));
if(fk.isSet())
emit foreignKeyClicked(sqlb::ObjectIdentifier(m->currentTableName().schema(), fk.table()),
fk.columns().size() ? fk.columns().at(0) : "",
if(fk)
emit foreignKeyClicked(sqlb::ObjectIdentifier(m->currentTableName().schema(), fk->table()),
fk->columns().size() ? fk->columns().at(0) : "",
m->data(index, Qt::EditRole).toByteArray());
else {
// If this column does not have a foreign-key, try to interpret it as a filename/URL and open it in external application.

View File

@@ -71,6 +71,7 @@ public:
QComboBox* tablesComboBox;
QComboBox* idsComboBox;
QLineEdit* clauseEdit; // for ON CASCADE and such
std::shared_ptr<sqlb::ForeignKeyClause> foreignKey; // This can be used for storing a pointer to the foreign key object currently edited
private:
QPushButton* m_btnReset;
@@ -138,12 +139,12 @@ void ForeignKeyEditorDelegate::setEditorData(QWidget* editor, const QModelIndex&
size_t column = static_cast<size_t>(index.row()); // weird? I know right
const sqlb::Field& field = m_table.fields.at(column);
auto fk = std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(m_table.constraint({field.name()}, sqlb::Constraint::ForeignKeyConstraintType));
if (fk) {
fkEditor->tablesComboBox->setCurrentText(QString::fromStdString(fk->table()));
fkEditor->clauseEdit->setText(QString::fromStdString(fk->constraint()));
if (!fk->columns().empty())
fkEditor->idsComboBox->setCurrentText(QString::fromStdString(fk->columns().front()));
fkEditor->foreignKey = m_table.foreignKey({field.name()});
if (fkEditor->foreignKey) {
fkEditor->tablesComboBox->setCurrentText(QString::fromStdString(fkEditor->foreignKey->table()));
fkEditor->clauseEdit->setText(QString::fromStdString(fkEditor->foreignKey->constraint()));
if (!fkEditor->foreignKey->columns().empty())
fkEditor->idsComboBox->setCurrentText(QString::fromStdString(fkEditor->foreignKey->columns().front()));
} else {
fkEditor->tablesComboBox->setCurrentIndex(-1);
}
@@ -156,28 +157,27 @@ void ForeignKeyEditorDelegate::setModelData(QWidget* editor, QAbstractItemModel*
size_t column = static_cast<size_t>(index.row());
const sqlb::Field& field = m_table.fields.at(column);
if (sql.isEmpty()) {
if (sql.isEmpty() && fkEditor->foreignKey) {
// Remove the foreign key
m_table.removeConstraints({field.name()}, sqlb::Constraint::ConstraintTypes::ForeignKeyConstraintType);
m_table.removeConstraint(fkEditor->foreignKey);
} else {
// Set the foreign key
sqlb::ForeignKeyClause* fk = new sqlb::ForeignKeyClause;
const QString table = fkEditor->tablesComboBox->currentText();
const std::string id = fkEditor->idsComboBox->currentText().toStdString();
const QString clause = fkEditor->clauseEdit->text();
fk->setTable(table.toStdString());
fk->setColumnList({ field.name() });
if (!id.empty())
fk->setColumns({id});
if (!clause.trimmed().isEmpty()) {
fk->setConstraint(clause.toStdString());
// Create a new foreign key object if required
if(!fkEditor->foreignKey)
{
fkEditor->foreignKey = std::make_shared<sqlb::ForeignKeyClause>();
m_table.addConstraint({field.name()}, fkEditor->foreignKey);
}
m_table.setConstraint(sqlb::ConstraintPtr(fk));
// Set data
const auto table = fkEditor->tablesComboBox->currentText().toStdString();
const auto id = fkEditor->idsComboBox->currentText().toStdString();
const auto clause = fkEditor->clauseEdit->text().trimmed().toStdString();
fkEditor->foreignKey->setTable(table);
if(id.empty())
fkEditor->foreignKey->setColumns({});
else
fkEditor->foreignKey->setColumns({id});
fkEditor->foreignKey->setConstraint(clause);
}
model->setData(index, sql);

View File

@@ -1502,7 +1502,7 @@ void TableBrowser::jumpToRow(const sqlb::ObjectIdentifier& table, std::string co
// If no column name is set, assume the primary key is meant
if(!column.size())
column = obj->primaryKey()->columnList().front();
column = obj->primaryKeyColumns().front().name();
// If column doesn't exist don't do anything
auto column_it = sqlb::findField(obj, column);

File diff suppressed because it is too large Load Diff

View File

@@ -54,10 +54,17 @@
namespace sqlb { namespace parser { class ParserDriver; } }
typedef void* yyscan_t;
// Colum definitions are a tuple of two elements: the Field object and a set of table constraints
using ColumndefData = std::tuple<sqlb::Field, sqlb::ConstraintVector>;
struct Constraints
{
std::multimap<sqlb::IndexedColumnVector, std::shared_ptr<sqlb::UniqueConstraint>> index;
std::multimap<sqlb::StringVector, std::shared_ptr<sqlb::ForeignKeyClause>> fk;
std::vector<std::shared_ptr<sqlb::CheckConstraint>> check;
};
#line 61 "sqlite3_parser.hpp"
// Colum definitions are a tuple of two elements: the Field object and a set of table constraints
using ColumndefData = std::tuple<sqlb::Field, Constraints>;
#line 68 "sqlite3_parser.hpp"
# include <cassert>
# include <cstdlib> // std::abort
@@ -198,7 +205,7 @@
#line 10 "sqlite3_parser.yy"
namespace sqlb { namespace parser {
#line 202 "sqlite3_parser.hpp"
#line 209 "sqlite3_parser.hpp"
@@ -420,20 +427,19 @@ namespace sqlb { namespace parser {
// columndef
char dummy1[sizeof (ColumndefData)];
// tableconstraint
// tableconstraint_list
// optional_tableconstraint_list
char dummy2[sizeof (Constraints)];
// optional_if_not_exists
// optional_unique
// optional_temporary
// optional_always_generated
char dummy2[sizeof (bool)];
char dummy3[sizeof (bool)];
// columnconstraint
// tableconstraint
char dummy3[sizeof (sqlb::ConstraintPtr)];
// columnconstraint_list
// tableconstraint_list
// optional_tableconstraint_list
char dummy4[sizeof (sqlb::ConstraintVector)];
char dummy4[sizeof (sqlb::ConstraintPtr)];
// createindex_stmt
char dummy5[sizeof (sqlb::IndexPtr)];
@@ -586,6 +592,9 @@ namespace sqlb { namespace parser {
// columndef_list
char dummy12[sizeof (std::vector<ColumndefData>)];
// columnconstraint_list
char dummy13[sizeof (std::vector<sqlb::ConstraintPtr>)];
};
/// The size of the largest semantic type.
@@ -988,6 +997,12 @@ namespace sqlb { namespace parser {
value.move< ColumndefData > (std::move (that.value));
break;
case symbol_kind::S_tableconstraint: // tableconstraint
case symbol_kind::S_tableconstraint_list: // tableconstraint_list
case symbol_kind::S_optional_tableconstraint_list: // optional_tableconstraint_list
value.move< Constraints > (std::move (that.value));
break;
case symbol_kind::S_optional_if_not_exists: // optional_if_not_exists
case symbol_kind::S_optional_unique: // optional_unique
case symbol_kind::S_optional_temporary: // optional_temporary
@@ -996,16 +1011,9 @@ namespace sqlb { namespace parser {
break;
case symbol_kind::S_columnconstraint: // columnconstraint
case symbol_kind::S_tableconstraint: // tableconstraint
value.move< sqlb::ConstraintPtr > (std::move (that.value));
break;
case symbol_kind::S_columnconstraint_list: // columnconstraint_list
case symbol_kind::S_tableconstraint_list: // tableconstraint_list
case symbol_kind::S_optional_tableconstraint_list: // optional_tableconstraint_list
value.move< sqlb::ConstraintVector > (std::move (that.value));
break;
case symbol_kind::S_createindex_stmt: // createindex_stmt
value.move< sqlb::IndexPtr > (std::move (that.value));
break;
@@ -1166,6 +1174,10 @@ namespace sqlb { namespace parser {
value.move< std::vector<ColumndefData> > (std::move (that.value));
break;
case symbol_kind::S_columnconstraint_list: // columnconstraint_list
value.move< std::vector<sqlb::ConstraintPtr> > (std::move (that.value));
break;
default:
break;
}
@@ -1203,6 +1215,20 @@ namespace sqlb { namespace parser {
{}
#endif
#if 201103L <= YY_CPLUSPLUS
basic_symbol (typename Base::kind_type t, Constraints&& v, location_type&& l)
: Base (t)
, value (std::move (v))
, location (std::move (l))
{}
#else
basic_symbol (typename Base::kind_type t, const Constraints& v, const location_type& l)
: Base (t)
, value (v)
, location (l)
{}
#endif
#if 201103L <= YY_CPLUSPLUS
basic_symbol (typename Base::kind_type t, bool&& v, location_type&& l)
: Base (t)
@@ -1231,20 +1257,6 @@ namespace sqlb { namespace parser {
{}
#endif
#if 201103L <= YY_CPLUSPLUS
basic_symbol (typename Base::kind_type t, sqlb::ConstraintVector&& v, location_type&& l)
: Base (t)
, value (std::move (v))
, location (std::move (l))
{}
#else
basic_symbol (typename Base::kind_type t, const sqlb::ConstraintVector& v, const location_type& l)
: Base (t)
, value (v)
, location (l)
{}
#endif
#if 201103L <= YY_CPLUSPLUS
basic_symbol (typename Base::kind_type t, sqlb::IndexPtr&& v, location_type&& l)
: Base (t)
@@ -1357,6 +1369,20 @@ namespace sqlb { namespace parser {
{}
#endif
#if 201103L <= YY_CPLUSPLUS
basic_symbol (typename Base::kind_type t, std::vector<sqlb::ConstraintPtr>&& v, location_type&& l)
: Base (t)
, value (std::move (v))
, location (std::move (l))
{}
#else
basic_symbol (typename Base::kind_type t, const std::vector<sqlb::ConstraintPtr>& v, const location_type& l)
: Base (t)
, value (v)
, location (l)
{}
#endif
/// Destroy the symbol.
~basic_symbol ()
{
@@ -1385,6 +1411,12 @@ switch (yykind)
value.template destroy< ColumndefData > ();
break;
case symbol_kind::S_tableconstraint: // tableconstraint
case symbol_kind::S_tableconstraint_list: // tableconstraint_list
case symbol_kind::S_optional_tableconstraint_list: // optional_tableconstraint_list
value.template destroy< Constraints > ();
break;
case symbol_kind::S_optional_if_not_exists: // optional_if_not_exists
case symbol_kind::S_optional_unique: // optional_unique
case symbol_kind::S_optional_temporary: // optional_temporary
@@ -1393,16 +1425,9 @@ switch (yykind)
break;
case symbol_kind::S_columnconstraint: // columnconstraint
case symbol_kind::S_tableconstraint: // tableconstraint
value.template destroy< sqlb::ConstraintPtr > ();
break;
case symbol_kind::S_columnconstraint_list: // columnconstraint_list
case symbol_kind::S_tableconstraint_list: // tableconstraint_list
case symbol_kind::S_optional_tableconstraint_list: // optional_tableconstraint_list
value.template destroy< sqlb::ConstraintVector > ();
break;
case symbol_kind::S_createindex_stmt: // createindex_stmt
value.template destroy< sqlb::IndexPtr > ();
break;
@@ -1563,6 +1588,10 @@ switch (yykind)
value.template destroy< std::vector<ColumndefData> > ();
break;
case symbol_kind::S_columnconstraint_list: // columnconstraint_list
value.template destroy< std::vector<sqlb::ConstraintPtr> > ();
break;
default:
break;
}
@@ -3924,6 +3953,12 @@ switch (yykind)
value.copy< ColumndefData > (YY_MOVE (that.value));
break;
case symbol_kind::S_tableconstraint: // tableconstraint
case symbol_kind::S_tableconstraint_list: // tableconstraint_list
case symbol_kind::S_optional_tableconstraint_list: // optional_tableconstraint_list
value.copy< Constraints > (YY_MOVE (that.value));
break;
case symbol_kind::S_optional_if_not_exists: // optional_if_not_exists
case symbol_kind::S_optional_unique: // optional_unique
case symbol_kind::S_optional_temporary: // optional_temporary
@@ -3932,16 +3967,9 @@ switch (yykind)
break;
case symbol_kind::S_columnconstraint: // columnconstraint
case symbol_kind::S_tableconstraint: // tableconstraint
value.copy< sqlb::ConstraintPtr > (YY_MOVE (that.value));
break;
case symbol_kind::S_columnconstraint_list: // columnconstraint_list
case symbol_kind::S_tableconstraint_list: // tableconstraint_list
case symbol_kind::S_optional_tableconstraint_list: // optional_tableconstraint_list
value.copy< sqlb::ConstraintVector > (YY_MOVE (that.value));
break;
case symbol_kind::S_createindex_stmt: // createindex_stmt
value.copy< sqlb::IndexPtr > (YY_MOVE (that.value));
break;
@@ -4102,6 +4130,10 @@ switch (yykind)
value.copy< std::vector<ColumndefData> > (YY_MOVE (that.value));
break;
case symbol_kind::S_columnconstraint_list: // columnconstraint_list
value.copy< std::vector<sqlb::ConstraintPtr> > (YY_MOVE (that.value));
break;
default:
break;
}
@@ -4137,6 +4169,12 @@ switch (yykind)
value.move< ColumndefData > (YY_MOVE (s.value));
break;
case symbol_kind::S_tableconstraint: // tableconstraint
case symbol_kind::S_tableconstraint_list: // tableconstraint_list
case symbol_kind::S_optional_tableconstraint_list: // optional_tableconstraint_list
value.move< Constraints > (YY_MOVE (s.value));
break;
case symbol_kind::S_optional_if_not_exists: // optional_if_not_exists
case symbol_kind::S_optional_unique: // optional_unique
case symbol_kind::S_optional_temporary: // optional_temporary
@@ -4145,16 +4183,9 @@ switch (yykind)
break;
case symbol_kind::S_columnconstraint: // columnconstraint
case symbol_kind::S_tableconstraint: // tableconstraint
value.move< sqlb::ConstraintPtr > (YY_MOVE (s.value));
break;
case symbol_kind::S_columnconstraint_list: // columnconstraint_list
case symbol_kind::S_tableconstraint_list: // tableconstraint_list
case symbol_kind::S_optional_tableconstraint_list: // optional_tableconstraint_list
value.move< sqlb::ConstraintVector > (YY_MOVE (s.value));
break;
case symbol_kind::S_createindex_stmt: // createindex_stmt
value.move< sqlb::IndexPtr > (YY_MOVE (s.value));
break;
@@ -4315,6 +4346,10 @@ switch (yykind)
value.move< std::vector<ColumndefData> > (YY_MOVE (s.value));
break;
case symbol_kind::S_columnconstraint_list: // columnconstraint_list
value.move< std::vector<sqlb::ConstraintPtr> > (YY_MOVE (s.value));
break;
default:
break;
}
@@ -4382,7 +4417,7 @@ switch (yykind)
#line 10 "sqlite3_parser.yy"
} } // sqlb::parser
#line 4386 "sqlite3_parser.hpp"
#line 4421 "sqlite3_parser.hpp"

View File

@@ -17,8 +17,15 @@
namespace sqlb { namespace parser { class ParserDriver; } }
typedef void* yyscan_t;
struct Constraints
{
std::multimap<sqlb::IndexedColumnVector, std::shared_ptr<sqlb::UniqueConstraint>> index;
std::multimap<sqlb::StringVector, std::shared_ptr<sqlb::ForeignKeyClause>> fk;
std::vector<std::shared_ptr<sqlb::CheckConstraint>> check;
};
// Colum definitions are a tuple of two elements: the Field object and a set of table constraints
using ColumndefData = std::tuple<sqlb::Field, sqlb::ConstraintVector>;
using ColumndefData = std::tuple<sqlb::Field, Constraints>;
}
// The parsing context
@@ -223,7 +230,7 @@
%type <std::string> optional_typename
%type <std::string> optional_storage_identifier
%type <bool> optional_always_generated
%type <sqlb::ConstraintVector> columnconstraint_list
%type <std::vector<sqlb::ConstraintPtr>> columnconstraint_list
%type <sqlb::ConstraintPtr> columnconstraint
%type <ColumndefData> columndef
%type <std::vector<ColumndefData>> columndef_list
@@ -232,9 +239,9 @@
%type <std::string> fk_clause_part
%type <std::string> fk_clause_part_list
%type <std::string> optional_fk_clause
%type <sqlb::ConstraintPtr> tableconstraint
%type <sqlb::ConstraintVector> tableconstraint_list
%type <sqlb::ConstraintVector> optional_tableconstraint_list
%type <Constraints> tableconstraint
%type <Constraints> tableconstraint_list
%type <Constraints> optional_tableconstraint_list
%type <std::bitset<sqlb::Table::NumOptions>> tableoption
%type <std::bitset<sqlb::Table::NumOptions>> tableoptions_list
%type <std::bitset<sqlb::Table::NumOptions>> optional_tableoptions_list
@@ -656,13 +663,13 @@ optional_always_generated:
columnconstraint:
optional_constraintname PRIMARY KEY optional_sort_order optional_conflictclause {
auto pk = std::make_shared<sqlb::PrimaryKeyConstraint>(sqlb::IndexedColumnVector{sqlb::IndexedColumn("", false, $4)});
auto pk = std::make_shared<sqlb::PrimaryKeyConstraint>();
pk->setName($1);
pk->setConflictAction($5);
$$ = pk;
}
| optional_constraintname PRIMARY KEY optional_sort_order optional_conflictclause AUTOINCREMENT {
auto pk = std::make_shared<sqlb::PrimaryKeyConstraint>(sqlb::IndexedColumnVector{sqlb::IndexedColumn("", false, $4)});
auto pk = std::make_shared<sqlb::PrimaryKeyConstraint>();
pk->setName($1);
pk->setConflictAction($5);
pk->setAutoIncrement(true);
@@ -737,7 +744,7 @@ columnconstraint_list:
columndef:
columnid optional_typename columnconstraint_list {
sqlb::Field f($1, $2);
sqlb::ConstraintVector table_constraints{};
Constraints table_constraints;
for(const auto& c : $3)
{
if(!c)
@@ -745,18 +752,14 @@ columndef:
switch(c->type())
{
// Primary key and foreign key constraints are converted to table constraints
// because we cannot store them as column constraints at the moment.
case sqlb::Constraint::PrimaryKeyConstraintType:
case sqlb::Constraint::ForeignKeyConstraintType:
{
// Primary key and foreign key constraints are converted to table constraints
// because we cannot store them as column constraints at the moment.
if(c->columnList().empty())
c->setColumnList({$1});
else
c->replaceInColumnList("", $1);
table_constraints.push_back(c);
table_constraints.index.insert(std::make_pair(sqlb::IndexedColumnVector{sqlb::IndexedColumn($1, false)}, std::dynamic_pointer_cast<sqlb::UniqueConstraint>(c)));
break;
case sqlb::Constraint::ForeignKeyConstraintType:
table_constraints.fk.insert(std::make_pair(sqlb::StringVector{$1}, std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(c)));
break;
}
case sqlb::Constraint::NotNullConstraintType:
f.setNotNull(std::dynamic_pointer_cast<sqlb::NotNullConstraint>(c));
break;
@@ -789,14 +792,12 @@ columndef:
}
break;
}
default:
break;
}
}
$$ = std::make_tuple(f, table_constraints);
}
| columnid optional_typename { $$ = std::make_tuple(sqlb::Field($1, $2), sqlb::ConstraintVector{}); }
| columnid optional_typename { $$ = std::make_tuple(sqlb::Field($1, $2), Constraints{}); }
;
columndef_list:
@@ -863,39 +864,41 @@ optional_fk_clause:
tableconstraint:
optional_constraintname PRIMARY KEY "(" indexed_column_list ")" optional_conflictclause {
auto pk = std::make_shared<sqlb::PrimaryKeyConstraint>($5);
auto pk = std::make_shared<sqlb::PrimaryKeyConstraint>();
pk->setName($1);
pk->setConflictAction($7);
$$ = pk;
$$.index.insert(std::make_pair($5, pk));
}
| optional_constraintname PRIMARY KEY "(" indexed_column_list AUTOINCREMENT ")" optional_conflictclause {
auto pk = std::make_shared<sqlb::PrimaryKeyConstraint>($5);
auto pk = std::make_shared<sqlb::PrimaryKeyConstraint>();
pk->setName($1);
pk->setConflictAction($8);
pk->setAutoIncrement(true);
$$ = pk;
$$.index.insert(std::make_pair($5, pk));
}
| optional_constraintname UNIQUE "(" indexed_column_list ")" optional_conflictclause {
auto u = std::make_shared<sqlb::UniqueConstraint>($4);
auto u = std::make_shared<sqlb::UniqueConstraint>();
u->setName($1);
u->setConflictAction($6);
$$ = u;
sqlb::StringVector columns;
$$.index.insert(std::make_pair($4, u));
}
| optional_constraintname CHECK "(" expr ")" {
$$ = std::make_shared<sqlb::CheckConstraint>($4);
$$->setName($1);
auto c = std::make_shared<sqlb::CheckConstraint>($4);
c->setName($1);
$$.check.push_back(c);
}
| optional_constraintname FOREIGN KEY "(" columnid_list ")" REFERENCES tableid optional_columnid_with_paren_list optional_fk_clause {
$$ = std::make_shared<sqlb::ForeignKeyClause>($8, $9, $10);
$$->setColumnList($5);
$$->setName($1);
auto f = std::make_shared<sqlb::ForeignKeyClause>($8, $9, $10);
f->setName($1);
$$.fk.insert(std::make_pair($5, f));
}
;
tableconstraint_list:
tableconstraint { $$ = {$1}; }
| tableconstraint_list "," tableconstraint { $$ = $1; $$.push_back($3); }
| tableconstraint_list tableconstraint { $$ = $1; $$.push_back($2); }
tableconstraint { $$ = $1; }
| tableconstraint_list "," tableconstraint { $$ = $1; $$.index.insert($3.index.begin(), $3.index.end()); $$.fk.insert($3.fk.begin(), $3.fk.end()); std::copy($3.check.begin(), $3.check.end(), std::back_inserter($$.check)); }
| tableconstraint_list tableconstraint { $$ = $1; $$.index.insert($2.index.begin(), $2.index.end()); $$.fk.insert($2.fk.begin(), $2.fk.end()); std::copy($2.check.begin(), $2.check.end(), std::back_inserter($$.check)); }
;
optional_tableconstraint_list:
@@ -912,17 +915,26 @@ createtable_stmt:
$$ = std::make_shared<sqlb::Table>($5);
$$->setWithoutRowidTable($10.test(sqlb::Table::WithoutRowid));
$$->setStrict($10.test(sqlb::Table::Strict));
$$->setConstraints($8);
for(const auto& i : $8.index)
$$->addConstraint(i.first, i.second);
for(const auto& i : $8.fk)
$$->addConstraint(i.first, i.second);
for(const auto& i : $8.check)
$$->addConstraint(i);
$$->setFullyParsed(true);
for(const auto& column : $7)
{
sqlb::Field f;
sqlb::ConstraintVector c;
Constraints c;
std::tie(f, c) = column;
$$->fields.push_back(f);
for(const auto& i : c)
for(const auto& i : c.index)
$$->addConstraint(i.first, i.second);
for(const auto& i : c.fk)
$$->addConstraint(i.first, i.second);
for(const auto& i : c.check)
$$->addConstraint(i);
}
}

View File

@@ -23,6 +23,13 @@ std::string joinStringVector(const StringVector& vec, const std::string& delim)
});
}
std::string joinStringVector(const IndexedColumnVector& vec, const std::string& delim)
{
return std::accumulate(vec.begin(), vec.end(), std::string(), [delim](const std::string& so_far, const IndexedColumn& c) {
return so_far.empty() ? c.toString("", " ") : so_far + delim + c.toString("", "");
});
}
bool Object::operator==(const Object& rhs) const
{
if(m_name != rhs.m_name)
@@ -35,43 +42,8 @@ bool Object::operator==(const Object& rhs) const
return true;
}
ConstraintPtr Constraint::makeConstraint(ConstraintTypes type)
{
switch(type)
{
case PrimaryKeyConstraintType:
return std::make_shared<PrimaryKeyConstraint>();
case UniqueConstraintType:
return std::make_shared<UniqueConstraint>();
case ForeignKeyConstraintType:
return std::make_shared<ForeignKeyClause>();
case CheckConstraintType:
return std::make_shared<CheckConstraint>();
default:
return nullptr;
}
}
void Constraint::replaceInColumnList(const std::string& from, const std::string& to)
{
std::replace(column_list.begin(), column_list.end(), from, to);
}
void Constraint::removeFromColumnList(const std::string& key)
{
column_list.erase(std::remove(column_list.begin(), column_list.end(), key), column_list.end());
}
bool ForeignKeyClause::isSet() const
{
return m_table.size();
}
std::string ForeignKeyClause::toString() const
{
if(!isSet())
return std::string();
std::string result = escapeIdentifier(m_table);
if(m_columns.size())
@@ -83,71 +55,17 @@ std::string ForeignKeyClause::toString() const
return result;
}
std::string ForeignKeyClause::toSql() const
std::string ForeignKeyClause::toSql(const StringVector& columns) const
{
std::string result;
if(!m_name.empty())
result = "CONSTRAINT " + escapeIdentifier(m_name) + " ";
result += "FOREIGN KEY(" + joinStringVector(escapeIdentifier(column_list), ",") + ") REFERENCES " + this->toString();
result += "FOREIGN KEY(" + joinStringVector(escapeIdentifier(columns), ",") + ") REFERENCES " + this->toString();
return result;
}
UniqueConstraint::UniqueConstraint(const IndexedColumnVector& columns) :
m_columns(columns)
{
// Extract column names and give them to the column list in the base class
for(const auto& c : columns)
column_list.push_back(c.name());
}
UniqueConstraint::UniqueConstraint(const StringVector& columns) :
Constraint(columns)
{
setColumnList(columns);
}
void UniqueConstraint::setColumnList(const StringVector& list)
{
Constraint::setColumnList(list);
// Create our own column list without sort orders etc
m_columns.clear();
std::transform(list.begin(), list.end(), std::back_inserter(m_columns), [](const auto& c) { return IndexedColumn(c, false); });
}
void UniqueConstraint::addToColumnList(const std::string& key)
{
Constraint::addToColumnList(key);
// Also add to our own column list
m_columns.push_back(IndexedColumn(key, false));
}
void UniqueConstraint::replaceInColumnList(const std::string& from, const std::string& to)
{
Constraint::replaceInColumnList(from, to);
for(auto& c : m_columns)
{
if(c.name() == from)
c.setName(to);
}
}
void UniqueConstraint::removeFromColumnList(const std::string& key)
{
Constraint::removeFromColumnList(key);
m_columns.erase(std::remove_if(m_columns.begin(), m_columns.end(), [key](const IndexedColumn& c) {
if(c.name() == key)
return true;
else
return false;
}), m_columns.end());
}
std::string UniqueConstraint::toSql() const
std::string UniqueConstraint::toSql(const IndexedColumnVector& columns) const
{
std::string result;
if(!m_name.empty())
@@ -155,12 +73,8 @@ std::string UniqueConstraint::toSql() const
result += "UNIQUE";
if(m_columns.size())
{
std::vector<std::string> u_columns;
std::transform(m_columns.begin(), m_columns.end(), std::back_inserter(u_columns), [](const auto& c) { return c.toString("", " "); });
result += "(" + joinStringVector(u_columns, ",") + ")";
}
if(columns.size())
result += "(" + joinStringVector(columns, ",") + ")";
if(!m_conflictAction.empty())
result += " ON CONFLICT " + m_conflictAction;
@@ -182,27 +96,13 @@ std::string NotNullConstraint::toSql() const
return result;
}
PrimaryKeyConstraint::PrimaryKeyConstraint(const IndexedColumnVector& columns) :
UniqueConstraint(columns),
m_auto_increment(false)
{
}
PrimaryKeyConstraint::PrimaryKeyConstraint(const StringVector& columns) :
UniqueConstraint(columns),
m_auto_increment(false)
{
}
std::string PrimaryKeyConstraint::toSql() const
std::string PrimaryKeyConstraint::toSql(const IndexedColumnVector& columns) const
{
std::string result;
if(!m_name.empty())
result = "CONSTRAINT " + escapeIdentifier(m_name) + " ";
std::vector<std::string> pk_columns;
std::transform(m_columns.begin(), m_columns.end(), std::back_inserter(pk_columns), [](const auto& c) { return c.toString("", " "); });
result += "PRIMARY KEY(" + joinStringVector(pk_columns, ",") + (m_auto_increment ? " AUTOINCREMENT" : "") + ")";
result += "PRIMARY KEY(" + joinStringVector(columns, ",") + (m_auto_increment ? " AUTOINCREMENT" : "") + ")";
if(!m_conflictAction.empty())
result += " ON CONFLICT " + m_conflictAction;
@@ -280,7 +180,7 @@ std::string Field::toString(const std::string& indent, const std::string& sep) c
if(m_check)
str += " " + m_check->toSql();
if(m_unique)
str += " " + m_unique->toSql();
str += " " + m_unique->toSql({});
if(m_collation)
str += " " + m_collation->toSql();
if(m_generated)
@@ -371,34 +271,23 @@ Table& Table::operator=(const Table& rhs)
// Clear the fields and the constraints first in order to avoid duplicates and/or old data in the next step
fields.clear();
m_constraints.clear();
m_indexConstraints.clear();
m_foreignKeys.clear();
m_checkConstraints.clear();
// Make copies of the fields and the constraints. This is necessary in order to avoid any unwanted changes to the application's main database
// schema representation just by modifying a reference to the fields or constraints and thinking it operates on a copy.
std::copy(rhs.fields.begin(), rhs.fields.end(), std::back_inserter(fields));
std::transform(rhs.m_constraints.begin(), rhs.m_constraints.end(), std::back_inserter(m_constraints), [](ConstraintPtr e) -> ConstraintPtr {
switch(e->type())
{
case Constraint::PrimaryKeyConstraintType:
return std::make_shared<PrimaryKeyConstraint>(*std::dynamic_pointer_cast<sqlb::PrimaryKeyConstraint>(e));
case Constraint::UniqueConstraintType:
return std::make_shared<UniqueConstraint>(*std::dynamic_pointer_cast<sqlb::UniqueConstraint>(e));
case Constraint::ForeignKeyConstraintType:
return std::make_shared<ForeignKeyClause>(*std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(e));
case Constraint::CheckConstraintType:
return std::make_shared<CheckConstraint>(*std::dynamic_pointer_cast<sqlb::CheckConstraint>(e));
case Constraint::GeneratedColumnConstraintType:
return std::make_shared<GeneratedColumnConstraint>(*std::dynamic_pointer_cast<sqlb::GeneratedColumnConstraint>(e));
case Constraint::NotNullConstraintType:
return std::make_shared<NotNullConstraint>(*std::dynamic_pointer_cast<sqlb::NotNullConstraint>(e));
case Constraint::DefaultConstraintType:
return std::make_shared<DefaultConstraint>(*std::dynamic_pointer_cast<sqlb::DefaultConstraint>(e));
case Constraint::CollateConstraintType:
return std::make_shared<CollateConstraint>(*std::dynamic_pointer_cast<sqlb::CollateConstraint>(e));
default:
return nullptr;
}
});
for(const auto& e : rhs.m_indexConstraints)
{
if(e.second->type() == Constraint::PrimaryKeyConstraintType)
m_indexConstraints.insert(std::make_pair(e.first, std::make_shared<PrimaryKeyConstraint>(*std::dynamic_pointer_cast<PrimaryKeyConstraint>(e.second))));
else
m_indexConstraints.insert(std::make_pair(e.first, std::make_shared<UniqueConstraint>(*e.second)));
}
for(const auto& e : rhs.m_foreignKeys)
m_foreignKeys.insert(std::make_pair(e.first, std::make_shared<ForeignKeyClause>(*e.second)));
std::transform(rhs.m_checkConstraints.begin(), rhs.m_checkConstraints.end(), std::back_inserter(m_checkConstraints), [](const auto& e) { return std::make_shared<CheckConstraint>(*e); });
return *this;
}
@@ -414,7 +303,11 @@ bool Table::operator==(const Table& rhs) const
return false;
if(fields != rhs.fields)
return false;
if(m_constraints != rhs.m_constraints)
if(m_indexConstraints != rhs.m_indexConstraints)
return false;
if(m_foreignKeys != rhs.m_foreignKeys)
return false;
if(m_checkConstraints != rhs.m_checkConstraints)
return false;
return true;
@@ -438,8 +331,12 @@ StringVector Table::rowidColumns() const
{
// For WITHOUT ROWID tables this function returns the names of the primary key column. For ordinary tables with a rowid column, it returns "_rowid_"
if(withoutRowidTable())
return const_cast<Table*>(this)->primaryKey()->columnList();
else
{
auto columns = primaryKeyColumns();
StringVector result;
std::transform(columns.begin(), columns.end(), std::back_inserter(result), [](const auto& e) { return e.name(); });
return result;
} else
return {"_rowid_"};
}
@@ -475,15 +372,12 @@ std::string Table::sql(const std::string& schema, bool ifNotExists) const
sql += joinStringVector(fieldList(), ",\n");
// Constraints
for(const auto& it : m_constraints)
{
// Ignore all constraints without any fields, except for check constraints which don't rely on a field vector
if(!it->columnList().empty() || it->type() == Constraint::CheckConstraintType)
{
sql += ",\n\t";
sql += it->toSql();
}
}
for(const auto& it : m_indexConstraints)
sql += ",\n\t" + it.second->toSql(it.first);
for(const auto& it : m_foreignKeys)
sql += ",\n\t" + it.second->toSql(it.first);
for(const auto& it : m_checkConstraints)
sql += ",\n\t" + it->toSql();
sql += "\n)";
@@ -516,107 +410,123 @@ std::string Table::sql(const std::string& schema, bool ifNotExists) const
return sql + ";";
}
void Table::addConstraint(ConstraintPtr constraint)
void Table::addConstraint(const StringVector& columns, std::shared_ptr<PrimaryKeyConstraint> constraint)
{
m_constraints.push_back(constraint);
IndexedColumnVector c;
std::transform(columns.begin(), columns.end(), std::back_inserter(c), [](const auto& e) { return IndexedColumn(e, false); });
m_indexConstraints.insert(std::make_pair(c, constraint));
}
void Table::setConstraint(ConstraintPtr constraint)
void Table::addConstraint(const StringVector& columns, std::shared_ptr<UniqueConstraint> constraint)
{
// Delete any old constraints of this type for these fields
removeConstraints(constraint->columnList(), constraint->type());
IndexedColumnVector c;
std::transform(columns.begin(), columns.end(), std::back_inserter(c), [](const auto& e) { return IndexedColumn(e, false); });
m_indexConstraints.insert(std::make_pair(c, constraint));
}
// Add the new constraint to the table, effectively overwriting all old constraints for that fields/type combination
addConstraint(constraint);
void Table::addConstraint(const IndexedColumnVector& columns, std::shared_ptr<ForeignKeyClause> constraint)
{
StringVector c;
std::transform(columns.begin(), columns.end(), std::back_inserter(c), [](const auto& e) { return e.name(); });
m_foreignKeys.insert(std::make_pair(c, constraint));
}
void Table::removeConstraint(ConstraintPtr constraint)
{
for(auto it = m_constraints.begin();it!=m_constraints.end();++it)
auto c = std::find_if(m_indexConstraints.begin(), m_indexConstraints.end(), [constraint](const auto& e) { return e.second == constraint; });
if(c != m_indexConstraints.end())
{
if((*it)->toSql() == constraint->toSql())
m_indexConstraints.erase(c);
return;
}
auto fk = std::find_if(m_foreignKeys.begin(), m_foreignKeys.end(), [constraint](const auto& e) { return e.second == constraint; });
if(fk != m_foreignKeys.end())
{
m_foreignKeys.erase(fk);
return;
}
auto check = std::find_if(m_checkConstraints.begin(), m_checkConstraints.end(), [constraint](const auto& e) { return e == constraint; });
if(check != m_checkConstraints.end())
m_checkConstraints.erase(check);
}
void Table::addKeyToConstraint(ConstraintPtr constraint, const std::string& key)
{
const auto find_and_add = [constraint, key](auto& container, auto& it, const auto& create) {
if(it->second == constraint)
{
m_constraints.erase(it);
// Add key to the column list
auto new_columns = it->first;
new_columns.push_back(create(key));
// Only remove the first constraint matching these criteria
return;
container.insert(std::make_pair(new_columns, it->second));
it = container.erase(it);
}
}
};
// Search for matching constraint
for(auto it=m_indexConstraints.begin();it!=m_indexConstraints.end();++it)
find_and_add(m_indexConstraints, it, [](const std::string& name) { return IndexedColumn(name, false); });
for(auto it=m_foreignKeys.begin();it!=m_foreignKeys.end();++it)
find_and_add(m_foreignKeys, it, [](const std::string& name) { return name; });
}
void Table::removeConstraints(const StringVector& vStrFields, Constraint::ConstraintTypes type)
void Table::removeKeyFromConstraint(ConstraintPtr constraint, const std::string& key)
{
for(auto it = m_constraints.begin();it!=m_constraints.end();)
{
if((*it)->columnList() == vStrFields && (*it)->type() == type)
it = m_constraints.erase(it);
else
++it;
}
}
auto match_and_remove = [constraint, key](auto& container, auto& it) {
if(it->second == constraint)
{
// Remove key from the column list
std::remove_const_t<decltype(it->first)> new_columns;
std::copy_if(it->first.begin(), it->first.end(), std::back_inserter(new_columns), [key](const auto& c) { return c != key; });
ConstraintPtr Table::constraint(const StringVector& vStrFields, Constraint::ConstraintTypes type) const
{
auto list = constraints(vStrFields, type);
if(list.size())
return list.at(0);
else
return ConstraintPtr(nullptr);
}
// If the column list is empty now, remove the entire constraint. Otherwise save the updated column list
if(new_columns.empty())
{
it = container.erase(it);
} else {
container.insert(std::make_pair(new_columns, it->second));
it = container.erase(it);
}
}
};
std::vector<ConstraintPtr> Table::constraints(const StringVector& vStrFields, Constraint::ConstraintTypes type) const
{
std::vector<ConstraintPtr> clist;
std::copy_if(m_constraints.begin(), m_constraints.end(), std::back_inserter(clist), [vStrFields, type](const auto& c) {
return (type == Constraint::NoType || c->type() == type) && (vStrFields.empty() || c->columnList() == vStrFields);
});
return clist;
}
for(auto it=m_indexConstraints.begin();it!=m_indexConstraints.end();++it)
match_and_remove(m_indexConstraints, it);
void Table::setConstraints(const ConstraintVector& constraints)
{
m_constraints = constraints;
}
void Table::replaceConstraint(ConstraintPtr from, ConstraintPtr to)
{
auto it = std::find(m_constraints.begin(), m_constraints.end(), from);
if(it == m_constraints.end())
return;
m_constraints.erase(it); // Erase old constraint
m_constraints.push_back(to); // Insert new constraint
}
std::shared_ptr<PrimaryKeyConstraint> Table::primaryKey()
{
const auto c = constraint({}, Constraint::PrimaryKeyConstraintType);
if(c)
return std::dynamic_pointer_cast<PrimaryKeyConstraint>(c);
else
return nullptr;
for(auto it=m_foreignKeys.begin();it!=m_foreignKeys.end();++it)
match_and_remove(m_foreignKeys, it);
}
void Table::removeKeyFromAllConstraints(const std::string& key)
{
// Update all constraints
for(auto it=m_constraints.begin();it!=m_constraints.end();)
{
auto match_and_remove = [key](auto& container, auto& it) {
// Check if they contain the old key name
if(contains((*it)->columnList(), key))
if(contains(it->first, key))
{
// If so, remove it from the column list
(*it)->removeFromColumnList(key);
std::remove_const_t<decltype(it->first)> new_columns;
std::copy_if(it->first.begin(), it->first.end(), std::back_inserter(new_columns), [key](const auto& c) { return c != key; });
// If the column list is empty now, remove the entire constraint. Otherwise save the updated column list
if((*it)->columnList().empty())
it = m_constraints.erase(it);
else
++it;
} else {
++it;
if(new_columns.empty())
{
it = container.erase(it);
} else {
container.insert(std::make_pair(new_columns, it->second));
it = container.erase(it);
}
}
}
};
for(auto it=m_indexConstraints.begin();it!=m_indexConstraints.end();++it)
match_and_remove(m_indexConstraints, it);
for(auto it=m_foreignKeys.begin();it!=m_foreignKeys.end();++it)
match_and_remove(m_foreignKeys, it);
}
void Table::renameKeyInAllConstraints(const std::string& key, const std::string& to)
@@ -625,14 +535,58 @@ void Table::renameKeyInAllConstraints(const std::string& key, const std::string&
if(key == to)
return;
// Find all occurrences of the key and change it to the new one
for(auto& it : m_constraints)
const auto match_and_rename = [key, to](auto& container, auto& it, const auto& rename) {
// Check if they contain the old key name
if(contains(it->first, key))
{
// If so, update it in the column list
std::remove_const_t<decltype(it->first)> new_columns;
std::transform(it->first.begin(), it->first.end(), std::back_inserter(new_columns), [rename](auto c) {
return rename(c);
});
container.insert(std::make_pair(new_columns, it->second));
it = container.erase(it);
}
};
// Update all constraints
for(auto it=m_indexConstraints.begin();it!=m_indexConstraints.end();++it)
{
if(contains(it->columnList(), key))
it->replaceInColumnList(key, to);
match_and_rename(m_indexConstraints, it, [key, to](IndexedColumn c) {
if(c == key)
c.setName(to);
return c;
});
}
for(auto it=m_foreignKeys.begin();it!=m_foreignKeys.end();++it)
match_and_rename(m_foreignKeys, it, [key, to](const std::string& c) { return c == key ? to : c; });
}
std::shared_ptr<PrimaryKeyConstraint> Table::primaryKey() const
{
const auto it = std::find_if(m_indexConstraints.begin(), m_indexConstraints.end(), [](const auto& e) { return e.second->type() == Constraint::PrimaryKeyConstraintType; });
if(it != m_indexConstraints.end())
return std::dynamic_pointer_cast<PrimaryKeyConstraint>(it->second);
return nullptr;
}
IndexedColumnVector Table::primaryKeyColumns() const
{
const auto it = std::find_if(m_indexConstraints.begin(), m_indexConstraints.end(), [](const auto& e) { return e.second->type() == Constraint::PrimaryKeyConstraintType; });
if(it != m_indexConstraints.end())
return it->first;
return {};
}
std::shared_ptr<ForeignKeyClause> Table::foreignKey(const StringVector& columns) const
{
const auto it = std::find_if(m_foreignKeys.begin(), m_foreignKeys.end(), [columns](const auto& e) { return e.first == columns; });
if(it != m_foreignKeys.end())
return it->second;
return nullptr;
}
std::string IndexedColumn::toString(const std::string& indent, const std::string& sep) const

View File

@@ -51,9 +51,6 @@ namespace sqlb {
using StringVector = std::vector<std::string>;
StringVector escapeIdentifier(StringVector ids);
std::string joinStringVector(const StringVector& vec, const std::string& delim);
class Object;
class Table;
class Index;
@@ -70,7 +67,10 @@ using TriggerPtr = std::shared_ptr<Trigger>;
using ConstraintPtr = std::shared_ptr<Constraint>;
using FieldVector = std::vector<Field>;
using IndexedColumnVector = std::vector<IndexedColumn>;
using ConstraintVector = std::vector<ConstraintPtr>;
StringVector escapeIdentifier(StringVector ids);
std::string joinStringVector(const StringVector& vec, const std::string& delim);
std::string joinStringVector(const IndexedColumnVector& vec, const std::string& delim);
class Object
{
@@ -116,34 +116,16 @@ public:
NotNullConstraintType,
DefaultConstraintType,
CollateConstraintType,
NoType = 999,
};
explicit Constraint(const StringVector& columns = {}, const std::string& name = std::string())
: column_list(columns),
m_name(name)
{
}
virtual ~Constraint() = default;
static ConstraintPtr makeConstraint(ConstraintTypes type);
virtual ConstraintTypes type() const = 0;
void setName(const std::string& name) { m_name = name; }
const std::string& name() const { return m_name; }
StringVector columnList() const { return column_list; }
virtual void setColumnList(const StringVector& list) { column_list = list; }
virtual void addToColumnList(const std::string& key) { column_list.push_back(key); }
virtual void replaceInColumnList(const std::string& from, const std::string& to);
virtual void removeFromColumnList(const std::string& key);
virtual std::string toSql() const = 0;
protected:
StringVector column_list;
std::string m_name;
};
@@ -157,7 +139,6 @@ public:
{
}
bool isSet() const;
std::string toString() const;
void setTable(const std::string& table) { m_table = table; }
@@ -169,7 +150,7 @@ public:
void setConstraint(const std::string& constraint) { m_constraint = constraint; }
const std::string& constraint() const { return m_constraint; }
std::string toSql() const override;
std::string toSql(const StringVector& columns) const;
ConstraintTypes type() const override { return ForeignKeyConstraintType; }
@@ -182,26 +163,14 @@ private:
class UniqueConstraint : public Constraint
{
public:
explicit UniqueConstraint(const IndexedColumnVector& columns = {});
explicit UniqueConstraint(const StringVector& columns);
void setConflictAction(const std::string& conflict) { m_conflictAction = conflict; }
const std::string& conflictAction() const { return m_conflictAction; }
// We override these because we maintain our own copy of the column_list variable in m_columns.
// This needs to be done because in a unique constraint we can add expressions, sort order, etc. to the
// list of columns.
void setColumnList(const StringVector& list) override;
void addToColumnList(const std::string& key) override;
void replaceInColumnList(const std::string& from, const std::string& to) override;
void removeFromColumnList(const std::string& key) override;
std::string toSql() const override;
virtual std::string toSql(const IndexedColumnVector& columns) const;
ConstraintTypes type() const override { return UniqueConstraintType; }
protected:
IndexedColumnVector m_columns;
std::string m_conflictAction;
};
@@ -212,7 +181,7 @@ public:
void setConflictAction(const std::string& conflict) { m_conflictAction = conflict; }
const std::string& conflictAction() const { return m_conflictAction; }
std::string toSql() const override;
std::string toSql() const;
ConstraintTypes type() const override { return NotNullConstraintType; }
@@ -227,13 +196,14 @@ class PrimaryKeyConstraint : public UniqueConstraint
// and both need to maintain a copy of the column list with sort order information etc.
public:
explicit PrimaryKeyConstraint(const IndexedColumnVector& columns = {});
explicit PrimaryKeyConstraint(const StringVector& columns);
PrimaryKeyConstraint()
: m_auto_increment(false)
{}
void setAutoIncrement(bool ai) { m_auto_increment = ai; }
bool autoIncrement() const { return m_auto_increment; }
std::string toSql() const override;
std::string toSql(const IndexedColumnVector& columns) const override;
ConstraintTypes type() const override { return PrimaryKeyConstraintType; }
@@ -252,7 +222,7 @@ public:
void setExpression(const std::string& expr) { m_expression = expr; }
const std::string& expression() const { return m_expression; }
std::string toSql() const override;
std::string toSql() const;
ConstraintTypes type() const override { return CheckConstraintType; }
@@ -271,7 +241,7 @@ public:
void setValue(const std::string& value) { m_value = value; }
const std::string& value() const { return m_value; }
std::string toSql() const override;
std::string toSql() const;
ConstraintTypes type() const override { return DefaultConstraintType; }
@@ -290,7 +260,7 @@ public:
void setCollation(const std::string& collation) { m_collation = collation; }
const std::string& collation() const { return m_collation; }
std::string toSql() const override;
std::string toSql() const;
ConstraintTypes type() const override { return CollateConstraintType; }
@@ -313,7 +283,7 @@ public:
void setStorage(const std::string& storage) { m_storage = storage; }
std::string storage() const { return m_storage.empty() ? "VIRTUAL" : m_storage; }
std::string toSql() const override;
std::string toSql() const;
ConstraintTypes type() const override { return GeneratedColumnConstraintType; }
@@ -416,7 +386,6 @@ public:
using field_type = Field;
using field_iterator = FieldVector::iterator;
enum Options
{
WithoutRowid,
@@ -444,19 +413,29 @@ public:
const std::string& virtualUsing() const { return m_virtual; }
bool isVirtual() const { return !m_virtual.empty(); }
void addConstraint(ConstraintPtr constraint);
void setConstraint(ConstraintPtr constraint);
void addConstraint(const IndexedColumnVector& columns, std::shared_ptr<PrimaryKeyConstraint> constraint) { m_indexConstraints.insert(std::make_pair(columns, constraint)); }
void addConstraint(const IndexedColumnVector& columns, std::shared_ptr<UniqueConstraint> constraint) { m_indexConstraints.insert(std::make_pair(columns, constraint)); }
void addConstraint(const StringVector& columns, std::shared_ptr<ForeignKeyClause> constraint) { m_foreignKeys.insert(std::make_pair(columns, constraint)); }
void addConstraint(std::shared_ptr<CheckConstraint> constraint) { m_checkConstraints.push_back(constraint); }
void removeConstraint(ConstraintPtr constraint);
void removeConstraints(const StringVector& vStrFields = StringVector(), Constraint::ConstraintTypes type = Constraint::NoType);
ConstraintPtr constraint(const StringVector& vStrFields = StringVector(), Constraint::ConstraintTypes type = Constraint::NoType) const; //! Only returns the first constraint, if any
std::vector<ConstraintPtr> constraints(const StringVector& vStrFields = StringVector(), Constraint::ConstraintTypes type = Constraint::NoType) const;
ConstraintVector allConstraints() const { return m_constraints; }
void setConstraints(const ConstraintVector& constraints);
void replaceConstraint(ConstraintPtr from, ConstraintPtr to);
std::shared_ptr<PrimaryKeyConstraint> primaryKey();
void addKeyToConstraint(ConstraintPtr constraint, const std::string& key);
void removeKeyFromConstraint(ConstraintPtr constraint, const std::string& key);
void removeKeyFromAllConstraints(const std::string& key);
void renameKeyInAllConstraints(const std::string& key, const std::string& to);
// These three functions are helper functions which we need for some templated code. As soon as we
// switch to C++17 we should be able to rewrite that code so we can get rid of these.
void addConstraint(const StringVector& columns, std::shared_ptr<PrimaryKeyConstraint> constraint);
void addConstraint(const StringVector& columns, std::shared_ptr<UniqueConstraint> constraint);
void addConstraint(const IndexedColumnVector& columns, std::shared_ptr<ForeignKeyClause> constraint);
std::shared_ptr<PrimaryKeyConstraint> primaryKey() const;
IndexedColumnVector primaryKeyColumns() const;
std::multimap<IndexedColumnVector, std::shared_ptr<UniqueConstraint>> indexConstraints() const { return m_indexConstraints; }
std::multimap<StringVector, std::shared_ptr<ForeignKeyClause>> foreignKeys() const { return m_foreignKeys; }
std::shared_ptr<ForeignKeyClause> foreignKey(const StringVector& columns) const; //! Only returns the first foreign key, if any
std::vector<std::shared_ptr<CheckConstraint>> checkConstraints() const { return m_checkConstraints; }
/**
* @brief parseSQL Parses the create Table statement in sSQL.
* @param sSQL The create table statement.
@@ -468,7 +447,9 @@ private:
StringVector fieldList() const;
private:
ConstraintVector m_constraints;
std::multimap<IndexedColumnVector, std::shared_ptr<UniqueConstraint>> m_indexConstraints; // This stores both UNIQUE and PRIMARY KEY constraints
std::multimap<StringVector, std::shared_ptr<ForeignKeyClause>> m_foreignKeys;
std::vector<std::shared_ptr<CheckConstraint>> m_checkConstraints;
std::string m_virtual;
std::bitset<NumOptions> m_options;
};
@@ -488,6 +469,11 @@ public:
{
}
bool operator==(const std::string& name) const { return !m_isExpression && m_name == name; }
bool operator!=(const std::string& name) const { return !m_isExpression && m_name != name; }
bool operator==(const IndexedColumn& other) const { return m_name == other.m_name && m_isExpression == other.m_isExpression && m_order == other.m_order; }
bool operator<(const IndexedColumn& other) const { return m_name < other.m_name; }
void setName(const std::string& name) { m_name = name; }
const std::string& name() const { return m_name; }

View File

@@ -1337,6 +1337,8 @@ std::string DBBrowserDB::emptyInsertStmt(const std::string& schemaName, const sq
{
std::string stmt = "INSERT INTO " + sqlb::escapeIdentifier(schemaName) + "." + sqlb::escapeIdentifier(t.name());
const auto pk = t.primaryKeyColumns();
sqlb::StringVector vals;
sqlb::StringVector fields;
for(const sqlb::Field& f : t.fields)
@@ -1345,8 +1347,7 @@ std::string DBBrowserDB::emptyInsertStmt(const std::string& schemaName, const sq
if(f.generated())
continue;
sqlb::ConstraintPtr pk = t.constraint({f.name()}, sqlb::Constraint::PrimaryKeyConstraintType);
if(pk)
if(pk.size() == 1 && pk.at(0) == f.name())
{
fields.push_back(f.name());

View File

@@ -223,14 +223,14 @@ QVariant SqliteTableModel::headerData(int section, Qt::Orientation orientation,
return QString::fromStdString(plainHeader);
case Qt::FontRole: {
bool is_pk = false;
bool is_fk = getForeignKeyClause(column-1).isSet();
bool is_fk = getForeignKeyClause(column-1) != nullptr;
if (contains(m_query.rowIdColumns(), m_headers.at(column))) {
is_pk = true;
} else if (m_table_of_query) {
auto field = sqlb::findField(m_table_of_query, m_headers.at(column));
const auto pk = m_table_of_query->primaryKey();
is_pk = field != m_table_of_query->fields.end() && pk && contains(pk->columnList(), field->name());
const auto pk = m_table_of_query->primaryKeyColumns();
is_pk = field != m_table_of_query->fields.end() && contains(pk, field->name());
}
QFont font;
@@ -398,11 +398,11 @@ QVariant SqliteTableModel::data(const QModelIndex &index, int role) const
// Regular case (not null, not binary and no matching conditional format)
return m_regBgColour;
} else if(role == Qt::ToolTipRole) {
sqlb::ForeignKeyClause fk = getForeignKeyClause(column-1);
if(fk.isSet())
auto fk = getForeignKeyClause(column-1);
if(fk)
return tr("References %1(%2)\nHold %3Shift and click to jump there").arg(
QString::fromStdString(fk.table()),
QString::fromStdString(sqlb::joinStringVector(fk.columns(), ",")),
QString::fromStdString(fk->table()),
QString::fromStdString(sqlb::joinStringVector(fk->columns(), ",")),
QKeySequence(Qt::CTRL).toString(QKeySequence::NativeText));
} else if (role == Qt::TextAlignmentRole) {
// Align horizontally according to conditional format or default (left for text and right for numbers)
@@ -429,14 +429,12 @@ QVariant SqliteTableModel::data(const QModelIndex &index, int role) const
return QVariant();
}
sqlb::ForeignKeyClause SqliteTableModel::getForeignKeyClause(size_t column) const
std::shared_ptr<sqlb::ForeignKeyClause> SqliteTableModel::getForeignKeyClause(size_t column) const
{
static const sqlb::ForeignKeyClause empty_foreign_key_clause;
// No foreign keys when not browsing a table. This usually happens when executing custom SQL statements
// and browsing the result set instead of browsing an entire table.
if(m_query.table().isEmpty())
return empty_foreign_key_clause;
return nullptr;
// Check if database object is a table. If it isn't stop here and don't return a foreign key.
// This happens for views which don't have foreign keys (though we might want to think about
@@ -447,12 +445,10 @@ sqlb::ForeignKeyClause SqliteTableModel::getForeignKeyClause(size_t column) cons
// Note that the rowid column has number -1 here, it can safely be excluded since there will never be a
// foreign key on that column.
sqlb::ConstraintPtr ptr = m_table_of_query->constraint({m_table_of_query->fields.at(column).name()}, sqlb::Constraint::ForeignKeyConstraintType);
if(ptr)
return *(std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(ptr));
return m_table_of_query->foreignKey({m_table_of_query->fields.at(column).name()});
}
return empty_foreign_key_clause;
return nullptr;
}
bool SqliteTableModel::setData(const QModelIndex& index, const QVariant& value, int role)
@@ -490,8 +486,8 @@ bool SqliteTableModel::setTypedData(const QModelIndex& index, bool isBlob, const
if(m_table_of_query)
{
auto field = sqlb::findField(m_table_of_query, m_headers.at(column));
const auto pk = m_table_of_query->primaryKey();
if(pk && contains(pk->columnList(), field->name()) && field->isInteger())
const auto pk = m_table_of_query->primaryKeyColumns();
if(contains(pk, field->name()) && field->isInteger())
newValue = "0";
}
}
@@ -709,9 +705,9 @@ QModelIndex SqliteTableModel::dittoRecord(int old_row)
size_t firstEditedColumn = 0;
int new_row = rowCount() - 1;
const auto pk = m_table_of_query->primaryKey();
const auto pk = m_table_of_query->primaryKeyColumns();
for (size_t col = 0; col < m_table_of_query->fields.size(); ++col) {
if(!pk || !contains(pk->columnList(), m_table_of_query->fields.at(col).name())) {
if(pk.empty() || !contains(pk, m_table_of_query->fields.at(col).name())) {
if (!firstEditedColumn)
firstEditedColumn = col + 1;

View File

@@ -104,7 +104,7 @@ public:
bool hasPseudoPk() const;
std::vector<std::string> pseudoPk() const { return m_query.rowIdColumns(); }
sqlb::ForeignKeyClause getForeignKeyClause(size_t column) const;
std::shared_ptr<sqlb::ForeignKeyClause> getForeignKeyClause(size_t column) const;
// This returns true if the model and, if set, the index can be edited. Not specifying the index parameter asks whether the model can
// be edited in general (i.e. inserting and deleting rows as well as updating some cells). Specifying the index parameter asks whether

View File

@@ -30,7 +30,7 @@ void TestTable::sqlOutput()
tt.fields.push_back(f);
tt.fields.emplace_back("car", "text");
tt.fields.push_back(fkm);
tt.addConstraint(ConstraintPtr(new PrimaryKeyConstraint({f.name(), fkm.name()})));
tt.addConstraint({f.name(), fkm.name()}, std::make_shared<PrimaryKeyConstraint>());
QCOMPARE(tt.sql(), "CREATE TABLE \"testtable\" (\n"
"\t\"id\"\tinteger,\n"
@@ -48,7 +48,7 @@ void TestTable::sqlGraveAccentOutput()
tt.fields.push_back(f);
tt.fields.emplace_back("car", "text");
tt.fields.push_back(fkm);
tt.addConstraint(ConstraintPtr(new PrimaryKeyConstraint({f.name(), fkm.name()})));
tt.addConstraint({f.name(), fkm.name()}, std::make_shared<PrimaryKeyConstraint>());
sqlb::setIdentifierQuoting(sqlb::GraveAccents);
QCOMPARE(tt.sql(), "CREATE TABLE `testtable` (\n"
@@ -70,7 +70,7 @@ void TestTable::sqlSquareBracketsOutput()
tt.fields.push_back(f);
tt.fields.emplace_back("car", "text");
tt.fields.push_back(fkm);
tt.addConstraint(ConstraintPtr(new PrimaryKeyConstraint({f.name(), fkm.name()})));
tt.addConstraint({f.name(), fkm.name()}, std::make_shared<PrimaryKeyConstraint>());
sqlb::setIdentifierQuoting(sqlb::SquareBrackets);
QCOMPARE(tt.sql(), "CREATE TABLE [testtable] (\n"
@@ -91,9 +91,9 @@ void TestTable::autoincrement()
tt.fields.push_back(f);
tt.fields.emplace_back("car", "text");
tt.fields.push_back(fkm);
PrimaryKeyConstraint pk({f.name()});
PrimaryKeyConstraint pk;
pk.setAutoIncrement(true);
tt.addConstraint(ConstraintPtr(new PrimaryKeyConstraint(pk)));
tt.addConstraint({f.name()}, std::make_shared<PrimaryKeyConstraint>(pk));
QCOMPARE(tt.sql(), "CREATE TABLE \"testtable\" (\n"
"\t\"id\"\tinteger,\n"
@@ -111,9 +111,9 @@ void TestTable::notnull()
tt.fields.push_back(f);
tt.fields.emplace_back("car", "text", true);
tt.fields.push_back(fkm);
PrimaryKeyConstraint pk({f.name()});
PrimaryKeyConstraint pk;
pk.setAutoIncrement(true);
tt.addConstraint(ConstraintPtr(new PrimaryKeyConstraint(pk)));
tt.addConstraint({f.name()}, std::make_shared<PrimaryKeyConstraint>(pk));
QCOMPARE(tt.sql(), "CREATE TABLE \"testtable\" (\n"
"\t\"id\"\tinteger,\n"
@@ -130,7 +130,7 @@ void TestTable::withoutRowid()
tt.fields.push_back(f);
tt.fields.emplace_back("b", "integer");
tt.setWithoutRowidTable(true);
tt.addConstraint(ConstraintPtr(new PrimaryKeyConstraint({f.name()})));
tt.addConstraint({f.name()}, std::make_shared<PrimaryKeyConstraint>());
QCOMPARE(tt.sql(), "CREATE TABLE \"testtable\" (\n"
"\t\"a\"\tinteger,\n"
@@ -160,7 +160,7 @@ void TestTable::strictAndWithoutRowid()
tt.fields.emplace_back("b", "integer");
tt.setStrict(true);
tt.setWithoutRowidTable(true);
tt.addConstraint(ConstraintPtr(new PrimaryKeyConstraint({f.name()})));
tt.addConstraint({f.name()}, std::make_shared<PrimaryKeyConstraint>());
QCOMPARE(tt.sql(), "CREATE TABLE \"testtable\" (\n"
"\t\"a\"\tinteger,\n"
@@ -174,9 +174,8 @@ void TestTable::foreignKeys()
Table tt("testtable");
Field f("a", "integer");
tt.fields.push_back(f);
sqlb::ConstraintPtr fk = sqlb::ConstraintPtr(new sqlb::ForeignKeyClause("b", sqlb::StringVector{"c"}));
fk->setColumnList({f.name()});
tt.addConstraint(fk);
auto fk = std::make_shared<sqlb::ForeignKeyClause>("b", sqlb::StringVector{"c"});
tt.addConstraint({f.name()}, fk);
QCOMPARE(tt.sql(), "CREATE TABLE \"testtable\" (\n"
"\t\"a\"\tinteger,\n"
@@ -194,7 +193,7 @@ void TestTable::uniqueConstraint()
tt.fields.push_back(f1);
tt.fields.push_back(f2);
tt.fields.push_back(f3);
tt.addConstraint(sqlb::ConstraintPtr(new sqlb::UniqueConstraint({f2.name(), f3.name()})));
tt.addConstraint({f2.name(), f3.name()}, std::make_shared<sqlb::UniqueConstraint>());
QCOMPARE(tt.sql(), "CREATE TABLE \"testtable\" (\n"
"\t\"a\"\tinteger UNIQUE,\n"
@@ -225,9 +224,10 @@ void TestTable::parseSQL()
QCOMPARE(tab.fields.at(2).type(), "VARCHAR(255)");
auto pk = tab.primaryKey();
auto pkColumns = tab.primaryKeyColumns();
QVERIFY(pk->autoIncrement());
QCOMPARE(pk->columnList().size(), 1);
QCOMPARE(pk->columnList().at(0), tab.fields.at(0).name());
QCOMPARE(pkColumns.size(), 1);
QCOMPARE(pkColumns.at(0).name(), tab.fields.at(0).name());
QVERIFY(tab.fields.at(1).notnull());
QCOMPARE(tab.fields.at(1).defaultValue(), "'xxxx'");
QCOMPARE(tab.fields.at(1).check(), "");
@@ -262,9 +262,9 @@ void TestTable::parseSQLdefaultexpr()
QCOMPARE(tab.fields.at(3).defaultValue(), "");
QCOMPARE(tab.fields.at(3).check(), "");
auto pk = tab.primaryKey();
QCOMPARE(pk->columnList().size(), 1);
QCOMPARE(pk->columnList().at(0), tab.fields.at(0).name());
auto pk = tab.primaryKeyColumns();
QCOMPARE(pk.size(), 1);
QCOMPARE(pk.at(0).name(), tab.fields.at(0).name());
}
void TestTable::parseSQLMultiPk()
@@ -285,10 +285,10 @@ void TestTable::parseSQLMultiPk()
QCOMPARE(tab.fields.at(0).type(), "integer");
QCOMPARE(tab.fields.at(1).type(), "integer");
auto pk = tab.primaryKey();
QCOMPARE(pk->columnList().size(), 2);
QCOMPARE(pk->columnList().at(0), tab.fields.at(0).name());
QCOMPARE(pk->columnList().at(1), tab.fields.at(1).name());
auto pk = tab.primaryKeyColumns();
QCOMPARE(pk.size(), 2);
QCOMPARE(pk.at(0).name(), tab.fields.at(0).name());
QCOMPARE(pk.at(1).name(), tab.fields.at(1).name());
}
void TestTable::parseSQLForeignKey()
@@ -356,7 +356,7 @@ void TestTable::parseSQLWithoutRowid()
Table tab(*Table::parseSQL(sSQL));
QCOMPARE(tab.primaryKey()->columnList(), {"a"});
QCOMPARE(tab.primaryKeyColumns().at(0).name(), "a");
QCOMPARE(tab.rowidColumns(), {"a"});
QCOMPARE(tab.withoutRowidTable(), true);
QCOMPARE(tab.isStrict(), false);
@@ -430,10 +430,10 @@ void TestTable::parseSQLForeignKeys()
QCOMPARE(tab.name(), "foreign_key_test");
QCOMPARE(tab.fields.at(0).name(), "a");
QCOMPARE(tab.fields.at(0).type(), "int");
QCOMPARE(std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(tab.constraint({tab.fields.at(0).name()}, sqlb::Constraint::ForeignKeyConstraintType))->table(), "x");
QCOMPARE(tab.foreignKey({tab.fields.at(0).name()})->table(), "x");
QCOMPARE(tab.fields.at(1).name(), "b");
QCOMPARE(tab.fields.at(1).type(), "int");
QCOMPARE(std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(tab.constraint({tab.fields.at(1).name()}, sqlb::Constraint::ForeignKeyConstraintType))->toString(), "\"w\"(\"y\",\"z\") on delete set null");
QCOMPARE(tab.foreignKey({tab.fields.at(1).name()})->toString(), "\"w\"(\"y\",\"z\") on delete set null");
}
void TestTable::parseSQLCheckConstraint()
@@ -515,7 +515,9 @@ void TestTable::rowValues()
Table tab(*Table::parseSQL(sql));
QCOMPARE(tab.name(), "test");
QCOMPARE(std::dynamic_pointer_cast<sqlb::CheckConstraint>(tab.constraint({}, sqlb::Constraint::CheckConstraintType))->expression(), "(\"a\", \"b\") = (1, 2)");
auto c = tab.checkConstraints();
QCOMPARE(c.size(), 1);
QCOMPARE(c.at(0)->expression(), "(\"a\", \"b\") = (1, 2)");
}
void TestTable::complexExpressions()
@@ -594,9 +596,9 @@ void TestTable::complexExpression()
QCOMPARE(tab.fields.at(0).type(), "INTEGER");
QCOMPARE(tab.fields.at(0).defaultValue(), "(hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-' || '4' || substr(hex(randomblob(2)), 2) || '-' || substr('AB89', 1 + (abs(random()) % 4), 1) || substr(hex(randomblob(2)), 2) || '-' || hex(randomblob(6)))");
auto c = tab.constraints({}, sqlb::Constraint::CheckConstraintType);
auto c = tab.checkConstraints();
QCOMPARE(c.size(), 1);
QCOMPARE(std::dynamic_pointer_cast<sqlb::CheckConstraint>(c.at(0))->expression(), "(\"a\" = 'S' AND \"b\" IS NOT NULL) OR (\"a\" IN ('A', 'P'))");
QCOMPARE(c.at(0)->expression(), "(\"a\" = 'S' AND \"b\" IS NOT NULL) OR (\"a\" IN ('A', 'P'))");
}
void TestTable::parseIdentifierWithDollar()