mirror of
https://github.com/sqlitebrowser/sqlitebrowser.git
synced 2026-01-17 01:09:36 -06:00
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:
@@ -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())));
|
||||
|
||||
|
||||
@@ -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{}));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user