From 44eb2d4f9922fd1a854d959602c6a2455eab0dc5 Mon Sep 17 00:00:00 2001 From: Martin Kleusberg Date: Sun, 3 Sep 2017 21:36:06 +0200 Subject: [PATCH] Add better handling of multiple schemata in the Database Structure tab Commit 532fcd3f6b713e89f7739f25de09c353c61bbe99 added support for multiple database schemata to the backend code. While doing this, it removed support for showing temporary database objects in the user interface. This functionally is partially reimplemented by this commit. With this commit temporary database objects are shown in the Database Structure tab and in the Db Structure dock. Unlike before however, they are visually separated from 'normal' database objects. Also this commit tries to make use of the new schema handling code wherever possible to also separate temporary objects programatically from the normal ones. This wasn't done in earlier versions and effectively was a source of all sorts of errors. This commit still lacks support for temporary tables in the foreign key editor and in the Browse Data tab. Also a substantial amount of testing is still required. --- src/DbStructureModel.cpp | 179 ++++++++++++++++++++++----------------- src/DbStructureModel.h | 12 ++- src/EditIndexDialog.cpp | 46 +++++++--- src/EditIndexDialog.h | 4 +- src/EditTableDialog.cpp | 47 +++++----- src/EditTableDialog.h | 4 +- src/ExportDataDialog.cpp | 30 +++++-- src/ExportDataDialog.h | 5 +- src/MainWindow.cpp | 64 ++++++++------ src/sqlitetypes.cpp | 2 +- src/sqlitetypes.h | 30 +++++++ 11 files changed, 267 insertions(+), 156 deletions(-) diff --git a/src/DbStructureModel.cpp b/src/DbStructureModel.cpp index a2e6a9ca..68e3da93 100644 --- a/src/DbStructureModel.cpp +++ b/src/DbStructureModel.cpp @@ -14,7 +14,7 @@ DbStructureModel::DbStructureModel(DBBrowserDB& db, QObject* parent) { // Create root item and use its columns to store the header strings QStringList header; - header << tr("Name") << tr("Object") << tr("Type") << tr("Schema"); + header << tr("Name") << tr("Object") << tr("Type") << tr("Schema") << tr("Database"); rootItem = new QTreeWidgetItem(header); } @@ -60,7 +60,7 @@ Qt::ItemFlags DbStructureModel::flags(const QModelIndex &index) const Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled; // Only enable dragging for entire table objects - QString type = data(index.sibling(index.row(), 1), Qt::DisplayRole).toString(); + QString type = data(index.sibling(index.row(), ColumnObjectType), Qt::DisplayRole).toString(); if(type == "table" || type == "view" || type == "index" || type == "trigger") flags |= Qt::ItemIsDragEnabled; @@ -138,77 +138,22 @@ void DbStructureModel::reloadData() // In the root node there are two nodes: 'browsables' and 'all'. The first node contains a list of a all browsable objects, i.e. views and tables. // The seconds node contains four sub-nodes (tables, indices, views and triggers), each containing a list of objects of that type. // This way we only have to have and only have to update one model and can use it in all sorts of places, just by setting a different root node. - QMap typeToParentItem; - QTreeWidgetItem* itemBrowsables = new QTreeWidgetItem(rootItem); - itemBrowsables->setIcon(0, QIcon(QString(":/icons/view"))); - itemBrowsables->setText(0, tr("Browsables (%1)").arg(m_db.schemata["main"].values("table").count() + m_db.schemata["main"].values("view").count())); - typeToParentItem.insert("browsable", itemBrowsables); + itemBrowsables->setIcon(ColumnName, QIcon(QString(":/icons/view"))); + itemBrowsables->setText(ColumnName, tr("Browsables")); QTreeWidgetItem* itemAll = new QTreeWidgetItem(rootItem); - itemAll->setIcon(0, QIcon(QString(":/icons/view"))); - itemAll->setText(0, tr("All")); + itemAll->setIcon(ColumnName, QIcon(QString(":/icons/database"))); + itemAll->setText(ColumnName, tr("All")); + buildTree(itemAll, itemBrowsables, "main"); - QTreeWidgetItem* itemTables = new QTreeWidgetItem(itemAll); - itemTables->setIcon(0, QIcon(QString(":/icons/table"))); - itemTables->setText(0, tr("Tables (%1)").arg(m_db.schemata["main"].values("table").count())); - typeToParentItem.insert("table", itemTables); - - QTreeWidgetItem* itemIndices = new QTreeWidgetItem(itemAll); - itemIndices->setIcon(0, QIcon(QString(":/icons/index"))); - itemIndices->setText(0, tr("Indices (%1)").arg(m_db.schemata["main"].values("index").count())); - typeToParentItem.insert("index", itemIndices); - - QTreeWidgetItem* itemViews = new QTreeWidgetItem(itemAll); - itemViews->setIcon(0, QIcon(QString(":/icons/view"))); - itemViews->setText(0, tr("Views (%1)").arg(m_db.schemata["main"].values("view").count())); - typeToParentItem.insert("view", itemViews); - - QTreeWidgetItem* itemTriggers = new QTreeWidgetItem(itemAll); - itemTriggers->setIcon(0, QIcon(QString(":/icons/trigger"))); - itemTriggers->setText(0, tr("Triggers (%1)").arg(m_db.schemata["main"].values("trigger").count())); - typeToParentItem.insert("trigger", itemTriggers); - - // Get all database objects and sort them by their name - QMultiMap dbobjs; - for(auto it=m_db.schemata["main"].constBegin(); it != m_db.schemata["main"].constEnd(); ++it) - dbobjs.insert((*it)->name(), (*it)); - - // Add the actual table objects - for(auto it=dbobjs.constBegin();it!=dbobjs.constEnd();++it) + // Add the temporary database as a node if it isn't empty + if(!m_db.schemata["temp"].isEmpty()) { - // Object node - QTreeWidgetItem* item = addNode(typeToParentItem.value(sqlb::Object::typeToString((*it)->type())), *it); - - // If it is a table or view add the field nodes, add an extra node for the browsable section - if((*it)->type() == sqlb::Object::Types::Table || (*it)->type() == sqlb::Object::Types::View) - addNode(typeToParentItem.value("browsable"), *it); - - // Add field nodes if there are any - sqlb::FieldInfoList fieldList = (*it)->fieldInformation(); - if(!fieldList.empty()) - { - QStringList pk_columns; - if((*it)->type() == sqlb::Object::Types::Table) - { - sqlb::FieldVector pk = (*it).dynamicCast()->primaryKey(); - foreach(sqlb::FieldPtr pk_col, pk) - pk_columns.push_back(pk_col->name()); - - } - foreach(const sqlb::FieldInfo& field, fieldList) - { - QTreeWidgetItem *fldItem = new QTreeWidgetItem(item); - fldItem->setText(0, field.name); - fldItem->setText(1, "field"); - fldItem->setText(2, field.type); - fldItem->setText(3, field.sql); - if(pk_columns.contains(field.name)) - fldItem->setIcon(0, QIcon(":/icons/field_key")); - else - fldItem->setIcon(0, QIcon(":/icons/field")); - } - } + QTreeWidgetItem* itemTemp = new QTreeWidgetItem(itemAll); + itemTemp->setIcon(ColumnName, QIcon(QString(":/icons/database"))); + itemTemp->setText(ColumnName, tr("Temporary")); + buildTree(itemTemp, itemBrowsables, "temp"); } // Refresh the view @@ -229,19 +174,20 @@ QMimeData* DbStructureModel::mimeData(const QModelIndexList& indices) const foreach(QModelIndex index, indices) { // Only export data for valid indices and only for the SQL column, i.e. only once per row - if(index.isValid() && index.column() == 3) + if(index.isValid() && index.column() == ColumnSQL) { // Add the SQL code used to create the object d = d.append(data(index, Qt::DisplayRole).toString() + ";\n"); // If it is a table also add the content - if(data(index.sibling(index.row(), 1), Qt::DisplayRole).toString() == "table") + if(data(index.sibling(index.row(), ColumnObjectType), Qt::DisplayRole).toString() == "table") { SqliteTableModel tableModel(m_db); - tableModel.setTable(sqlb::ObjectIdentifier("main", data(index.sibling(index.row(), 0), Qt::DisplayRole).toString())); + tableModel.setTable(sqlb::ObjectIdentifier(data(index.sibling(index.row(), ColumnSchema), Qt::DisplayRole).toString(), + data(index.sibling(index.row(), ColumnName), Qt::DisplayRole).toString())); for(int i=0; i < tableModel.rowCount(); ++i) { - QString insertStatement = "INSERT INTO " + sqlb::escapeIdentifier(data(index.sibling(index.row(), 0), Qt::DisplayRole).toString()) + " VALUES("; + QString insertStatement = "INSERT INTO " + sqlb::escapeIdentifier(data(index.sibling(index.row(), ColumnName), Qt::DisplayRole).toString()) + " VALUES("; for(int j=1; j < tableModel.columnCount(); ++j) insertStatement += QString("'%1',").arg(tableModel.data(tableModel.index(i, j)).toString()); insertStatement.chop(1); @@ -285,15 +231,94 @@ bool DbStructureModel::dropMimeData(const QMimeData* data, Qt::DropAction action } } -QTreeWidgetItem* DbStructureModel::addNode(QTreeWidgetItem* parent, const sqlb::ObjectPtr& object) +void DbStructureModel::buildTree(QTreeWidgetItem* parent, QTreeWidgetItem* browsables, const QString& schema) +{ + // Build a map from object type to tree node to simplify finding the correct tree node later + QMap typeToParentItem; + + // Get object map for the given schema + objectMap objmap = m_db.schemata[schema]; + + // Prepare tree + QTreeWidgetItem* itemTables = new QTreeWidgetItem(parent); + itemTables->setIcon(ColumnName, QIcon(QString(":/icons/table"))); + itemTables->setText(ColumnName, tr("Tables (%1)").arg(objmap.values("table").count())); + typeToParentItem.insert("table", itemTables); + + QTreeWidgetItem* itemIndices = new QTreeWidgetItem(parent); + itemIndices->setIcon(ColumnName, QIcon(QString(":/icons/index"))); + itemIndices->setText(ColumnName, tr("Indices (%1)").arg(objmap.values("index").count())); + typeToParentItem.insert("index", itemIndices); + + QTreeWidgetItem* itemViews = new QTreeWidgetItem(parent); + itemViews->setIcon(ColumnName, QIcon(QString(":/icons/view"))); + itemViews->setText(ColumnName, tr("Views (%1)").arg(objmap.values("view").count())); + typeToParentItem.insert("view", itemViews); + + QTreeWidgetItem* itemTriggers = new QTreeWidgetItem(parent); + itemTriggers->setIcon(ColumnName, QIcon(QString(":/icons/trigger"))); + itemTriggers->setText(ColumnName, tr("Triggers (%1)").arg(objmap.values("trigger").count())); + typeToParentItem.insert("trigger", itemTriggers); + + // Get all database objects and sort them by their name + QMultiMap dbobjs; + for(auto it=objmap.constBegin(); it != objmap.constEnd(); ++it) + dbobjs.insert((*it)->name(), (*it)); + + // Add the database objects to the tree nodes + for(auto it=dbobjs.constBegin();it!=dbobjs.constEnd();++it) + { + // Object node + QTreeWidgetItem* item = addNode(typeToParentItem.value(sqlb::Object::typeToString((*it)->type())), *it, schema); + + // If it is a table or view add the field nodes, add an extra node for the browsable section + if((*it)->type() == sqlb::Object::Types::Table || (*it)->type() == sqlb::Object::Types::View) + { + // TODO We're currently only adding objects in the main schema to the list of browsable objects because browsing non-main objects + // isn't really supported in the main window yet. As soon as this is implemented the following if statement can be removed. + if(schema == "main") + addNode(browsables, *it, schema); + } + + // Add field nodes if there are any + sqlb::FieldInfoList fieldList = (*it)->fieldInformation(); + if(!fieldList.empty()) + { + QStringList pk_columns; + if((*it)->type() == sqlb::Object::Types::Table) + { + sqlb::FieldVector pk = (*it).dynamicCast()->primaryKey(); + foreach(sqlb::FieldPtr pk_col, pk) + pk_columns.push_back(pk_col->name()); + + } + foreach(const sqlb::FieldInfo& field, fieldList) + { + QTreeWidgetItem *fldItem = new QTreeWidgetItem(item); + fldItem->setText(ColumnName, field.name); + fldItem->setText(ColumnObjectType, "field"); + fldItem->setText(ColumnDataType, field.type); + fldItem->setText(ColumnSQL, field.sql); + fldItem->setText(ColumnSchema, schema); + if(pk_columns.contains(field.name)) + fldItem->setIcon(ColumnName, QIcon(":/icons/field_key")); + else + fldItem->setIcon(ColumnName, QIcon(":/icons/field")); + } + } + } +} + +QTreeWidgetItem* DbStructureModel::addNode(QTreeWidgetItem* parent, const sqlb::ObjectPtr& object, const QString& schema) { QString type = sqlb::Object::typeToString(object->type()); QTreeWidgetItem *item = new QTreeWidgetItem(parent); - item->setIcon(0, QIcon(QString(":/icons/%1").arg(type))); - item->setText(0, object->name()); - item->setText(1, type); - item->setText(3, object->originalSql()); + item->setIcon(ColumnName, QIcon(QString(":/icons/%1").arg(type))); + item->setText(ColumnName, object->name()); + item->setText(ColumnObjectType, type); + item->setText(ColumnSQL, object->originalSql()); + item->setText(ColumnSchema, schema); return item; } diff --git a/src/DbStructureModel.h b/src/DbStructureModel.h index 784ccdb0..f1bb764a 100644 --- a/src/DbStructureModel.h +++ b/src/DbStructureModel.h @@ -29,11 +29,21 @@ public: QMimeData* mimeData(const QModelIndexList& indices) const; bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); + enum Columns + { + ColumnName, + ColumnObjectType, + ColumnDataType, + ColumnSQL, + ColumnSchema, + }; + private: QTreeWidgetItem* rootItem; DBBrowserDB& m_db; - QTreeWidgetItem* addNode(QTreeWidgetItem* parent, const sqlb::ObjectPtr& object); + void buildTree(QTreeWidgetItem* parent, QTreeWidgetItem* browsables, const QString& schema); + QTreeWidgetItem* addNode(QTreeWidgetItem* parent, const sqlb::ObjectPtr& object, const QString& schema); }; #endif diff --git a/src/EditIndexDialog.cpp b/src/EditIndexDialog.cpp index 88971cae..b3ea640d 100644 --- a/src/EditIndexDialog.cpp +++ b/src/EditIndexDialog.cpp @@ -5,11 +5,11 @@ #include #include -EditIndexDialog::EditIndexDialog(DBBrowserDB& db, const QString& indexName, bool createIndex, QWidget* parent) +EditIndexDialog::EditIndexDialog(DBBrowserDB& db, const sqlb::ObjectIdentifier& indexName, bool createIndex, QWidget* parent) : QDialog(parent), pdb(db), curIndex(indexName), - index(indexName), + index(indexName.name()), newIndex(createIndex), ui(new Ui::EditIndexDialog), m_sRestorePointName(pdb.generateSavepointName("editindex")) @@ -18,13 +18,31 @@ EditIndexDialog::EditIndexDialog(DBBrowserDB& db, const QString& indexName, bool ui->setupUi(this); // Get list of tables, sort it alphabetically and fill the combobox - objectMap dbobjs; - QList tables = pdb.schemata["main"].values("table"); - for(auto it=tables.constBegin();it!=tables.constEnd();++it) - dbobjs.insert((*it)->name(), (*it)); + QMap dbobjs; // Map from display name to full object identifier + if(newIndex) // If this is a new index, offer all tables of all database schemata + { + for(auto it=pdb.schemata.constBegin();it!=pdb.schemata.constEnd();++it) + { + QList tables = it->values("table"); + for(auto jt=tables.constBegin();jt!=tables.constEnd();++jt) + { + // Only show the schema name for non-main schemata + sqlb::ObjectIdentifier obj(it.key(), (*jt)->name()); + dbobjs.insert(obj.toDisplayString(), obj); + } + } + } else { // If this is an existing index, only offer tables of the current database schema + QList tables = pdb.schemata[curIndex.schema()].values("table"); + for(auto it=tables.constBegin();it!=tables.constEnd();++it) + { + // Only show the schema name for non-main schemata + sqlb::ObjectIdentifier obj(curIndex.schema(), (*it)->name()); + dbobjs.insert(obj.toDisplayString(), obj); + } + } ui->comboTableName->blockSignals(true); for(auto it=dbobjs.constBegin();it!=dbobjs.constEnd();++it) - ui->comboTableName->addItem(QIcon(QString(":icons/table")), (*it)->name()); + ui->comboTableName->addItem(QIcon(QString(":icons/table")), it.key(), it.value().toVariant()); ui->comboTableName->blockSignals(false); QHeaderView *tableHeaderView = ui->tableIndexColumns->horizontalHeader(); @@ -34,7 +52,7 @@ EditIndexDialog::EditIndexDialog(DBBrowserDB& db, const QString& indexName, bool if(!newIndex) { // Load the current layout and fill in the dialog fields - index = *(pdb.getObjectByName(sqlb::ObjectIdentifier("main", curIndex)).dynamicCast()); + index = *(pdb.getObjectByName(curIndex).dynamicCast()); ui->editIndexName->blockSignals(true); ui->editIndexName->setText(index.name()); @@ -76,7 +94,7 @@ void EditIndexDialog::tableChanged(const QString& new_table, bool initialLoad) // Set the table name and clear all index columns if(!initialLoad) { - index.setTable(new_table); + index.setTable(sqlb::ObjectIdentifier(ui->comboTableName->currentData()).name()); index.clearColumns(); } @@ -94,7 +112,7 @@ void EditIndexDialog::tableChanged(const QString& new_table, bool initialLoad) void EditIndexDialog::updateColumnLists() { // Fill the table column list - sqlb::FieldInfoList tableFields = pdb.getObjectByName(sqlb::ObjectIdentifier("main", index.table())).dynamicCast()->fieldInformation(); + sqlb::FieldInfoList tableFields = pdb.getObjectByName(sqlb::ObjectIdentifier(ui->comboTableName->currentData())).dynamicCast()->fieldInformation(); ui->tableTableColumns->setRowCount(tableFields.size()); int tableRows = 0; for(int i=0;iapplicationName(), tr("Deleting the old index failed:\n%1").arg(pdb.lastError())); return; } } - // Create the new index - if(pdb.executeSQL(index.sql())) + // Create the new index in the schema of the selected table + if(pdb.executeSQL(index.sql(sqlb::ObjectIdentifier(ui->comboTableName->currentData()).schema()))) QDialog::accept(); else QMessageBox::warning(this, QApplication::applicationName(), tr("Creating the index failed:\n%1").arg(pdb.lastError())); @@ -261,7 +279,7 @@ void EditIndexDialog::reject() void EditIndexDialog::updateSqlText() { - ui->sqlTextEdit->setText(index.sql()); + ui->sqlTextEdit->setText(index.sql(sqlb::ObjectIdentifier(ui->comboTableName->currentData()).schema())); } void EditIndexDialog::moveColumnUp() diff --git a/src/EditIndexDialog.h b/src/EditIndexDialog.h index ba967b90..ad6d28b3 100644 --- a/src/EditIndexDialog.h +++ b/src/EditIndexDialog.h @@ -17,7 +17,7 @@ class EditIndexDialog : public QDialog Q_OBJECT public: - explicit EditIndexDialog(DBBrowserDB& db, const QString& indexName, bool createIndex, QWidget* parent = 0); + explicit EditIndexDialog(DBBrowserDB& db, const sqlb::ObjectIdentifier& indexName, bool createIndex, QWidget* parent = 0); ~EditIndexDialog(); private slots: @@ -34,7 +34,7 @@ private slots: private: DBBrowserDB& pdb; - QString curIndex; + sqlb::ObjectIdentifier curIndex; sqlb::Index index; bool newIndex; Ui::EditIndexDialog* ui; diff --git a/src/EditTableDialog.cpp b/src/EditTableDialog.cpp index 71f77994..4e26d4c0 100644 --- a/src/EditTableDialog.cpp +++ b/src/EditTableDialog.cpp @@ -11,12 +11,12 @@ #include #include -EditTableDialog::EditTableDialog(DBBrowserDB& db, const QString& tableName, bool createTable, QWidget* parent) +EditTableDialog::EditTableDialog(DBBrowserDB& db, const sqlb::ObjectIdentifier& tableName, bool createTable, QWidget* parent) : QDialog(parent), ui(new Ui::EditTableDialog), pdb(db), curTable(tableName), - m_table(tableName), + m_table(tableName.name()), m_bNewTable(createTable), m_sRestorePointName(pdb.generateSavepointName("edittable")) { @@ -33,7 +33,7 @@ EditTableDialog::EditTableDialog(DBBrowserDB& db, const QString& tableName, bool if(m_bNewTable == false) { // Existing table, so load and set the current layout - m_table = *(pdb.getObjectByName(sqlb::ObjectIdentifier("main", curTable)).dynamicCast()); + m_table = *(pdb.getObjectByName(curTable).dynamicCast()); ui->labelEditWarning->setVisible(!m_table.fullyParsed()); // Set without rowid and temporary checkboxex. No need to trigger any events here as we're only loading a table exactly as it is stored by SQLite, so no need @@ -52,7 +52,7 @@ EditTableDialog::EditTableDialog(DBBrowserDB& db, const QString& tableName, bool pdb.setSavepoint(m_sRestorePointName); // Update UI - ui->editTableName->setText(curTable); + ui->editTableName->setText(curTable.name()); updateColumnWidth(); checkInput(); @@ -161,9 +161,9 @@ void EditTableDialog::accept() // Editing of old table // Rename table if necessary - if(ui->editTableName->text() != curTable) + if(ui->editTableName->text() != curTable.name()) { - if(!pdb.renameTable("main", curTable, ui->editTableName->text())) + if(!pdb.renameTable(curTable.schema(), curTable.name(), ui->editTableName->text())) { QMessageBox::warning(this, QApplication::applicationName(), pdb.lastError()); return; @@ -210,7 +210,7 @@ void EditTableDialog::checkInput() if (oldTableName == fk->table()) { fk->setTable(normTableName); if(!fksEnabled) - pdb.renameColumn(sqlb::ObjectIdentifier("main", curTable), m_table, f->name(), f, 0); + pdb.renameColumn(curTable, m_table, f->name(), f, 0); } } } @@ -239,7 +239,7 @@ void EditTableDialog::updateTypes() m_table.fields().at(index)->setType(type); if(!m_bNewTable) - pdb.renameColumn(sqlb::ObjectIdentifier("main", curTable), m_table, column, m_table.fields().at(index)); + pdb.renameColumn(curTable, m_table, column, m_table.fields().at(index)); checkInput(); } } @@ -279,7 +279,7 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column) if(!m_bNewTable) { sqlb::FieldVector pk = m_table.primaryKey(); - foreach(const sqlb::ObjectPtr& fkobj, pdb.schemata["main"].values("table")) + foreach(const sqlb::ObjectPtr& fkobj, pdb.schemata[curTable.schema()].values("table")) { QList fks = fkobj.dynamicCast()->constraints(sqlb::FieldVector(), sqlb::Constraint::ForeignKeyConstraintType); foreach(sqlb::ConstraintPtr fkptr, fks) @@ -352,10 +352,9 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column) // we need to check for this case and cancel here. Maybe we can think of some way to modify the INSERT INTO ... SELECT statement // to at least replace all troublesome NULL values by the default value SqliteTableModel m(pdb, this); - m.setQuery(QString("SELECT COUNT(%1) FROM %2.%3 WHERE %4 IS NULL;") - .arg(sqlb::escapeIdentifier(pdb.getObjectByName(sqlb::ObjectIdentifier("main", curTable)).dynamicCast()->rowidColumn())) - .arg(sqlb::escapeIdentifier("main")) - .arg(sqlb::escapeIdentifier(curTable)) + m.setQuery(QString("SELECT COUNT(%1) FROM %2 WHERE %3 IS NULL;") + .arg(sqlb::escapeIdentifier(pdb.getObjectByName(curTable).dynamicCast()->rowidColumn())) + .arg(curTable.toString()) .arg(sqlb::escapeIdentifier(field->name()))); if(m.data(m.index(0, 0)).toInt() > 0) { @@ -381,7 +380,7 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column) { SqliteTableModel m(pdb, this); m.setQuery(QString("SELECT COUNT(*) FROM %1 WHERE %2 <> CAST(%3 AS INTEGER);") - .arg(sqlb::escapeIdentifier(curTable)) + .arg(curTable.toString()) .arg(sqlb::escapeIdentifier(field->name())) .arg(sqlb::escapeIdentifier(field->name()))); if(m.data(m.index(0, 0)).toInt() > 0) @@ -424,9 +423,9 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column) { // Because our renameColumn() function fails when setting a column to unique when it already contains the same values SqliteTableModel m(pdb, this); - m.setQuery(QString("SELECT COUNT(%2) FROM %1;").arg(sqlb::escapeIdentifier(curTable)).arg(sqlb::escapeIdentifier(field->name()))); + m.setQuery(QString("SELECT COUNT(%2) FROM %1;").arg(curTable.toString()).arg(sqlb::escapeIdentifier(field->name()))); int rowcount = m.data(m.index(0, 0)).toInt(); - m.setQuery(QString("SELECT COUNT(DISTINCT %2) FROM %1;").arg(sqlb::escapeIdentifier(curTable)).arg(sqlb::escapeIdentifier(field->name()))); + m.setQuery(QString("SELECT COUNT(DISTINCT %2) FROM %1;").arg(curTable.toString()).arg(sqlb::escapeIdentifier(field->name()))); int uniquecount = m.data(m.index(0, 0)).toInt(); if(rowcount != uniquecount) { @@ -490,7 +489,7 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column) if(callRenameColumn) { - if(!pdb.renameColumn(sqlb::ObjectIdentifier("main", curTable), m_table, oldFieldName, field)) + if(!pdb.renameColumn(curTable, m_table, oldFieldName, field)) QMessageBox::warning(this, qApp->applicationName(), tr("Modifying this column failed. Error returned from database:\n%1").arg(pdb.lastError())); } } @@ -549,7 +548,7 @@ void EditTableDialog::addField() // Actually add the new column to the table if we're editing an existing table if(!m_bNewTable) - pdb.addColumn(sqlb::ObjectIdentifier("main", curTable), f); + pdb.addColumn(curTable, f); checkInput(); } @@ -577,12 +576,12 @@ void EditTableDialog::removeField() QString msg = tr("Are you sure you want to delete the field '%1'?\nAll data currently stored in this field will be lost.").arg(ui->treeWidget->currentItem()->text(0)); if(QMessageBox::warning(this, QApplication::applicationName(), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { - if(!pdb.renameColumn(sqlb::ObjectIdentifier("main", curTable), m_table, ui->treeWidget->currentItem()->text(0), sqlb::FieldPtr())) + if(!pdb.renameColumn(curTable, m_table, ui->treeWidget->currentItem()->text(0), sqlb::FieldPtr())) { QMessageBox::warning(0, QApplication::applicationName(), pdb.lastError()); } else { //relayout - m_table = *(pdb.getObjectByName(sqlb::ObjectIdentifier("main", curTable)).dynamicCast()); + m_table = *(pdb.getObjectByName(curTable).dynamicCast()); populateFields(); } } @@ -655,7 +654,7 @@ void EditTableDialog::moveCurrentField(bool down) // Move the actual column if(!pdb.renameColumn( - sqlb::ObjectIdentifier("main", curTable), + curTable, m_table, ui->treeWidget->currentItem()->text(0), m_table.fields().at(ui->treeWidget->indexOfTopLevelItem(ui->treeWidget->currentItem())), @@ -665,7 +664,7 @@ void EditTableDialog::moveCurrentField(bool down) QMessageBox::warning(0, QApplication::applicationName(), pdb.lastError()); } else { // Reload table SQL - m_table = *(pdb.getObjectByName(sqlb::ObjectIdentifier("main", curTable)).dynamicCast()); + m_table = *(pdb.getObjectByName(curTable).dynamicCast()); populateFields(); // Select old item at new position @@ -711,7 +710,7 @@ void EditTableDialog::setWithoutRowid(bool without_rowid) // Update table if we're editing an existing table if(!m_bNewTable) { - if(!pdb.renameColumn(sqlb::ObjectIdentifier("main", curTable), m_table, QString(), sqlb::FieldPtr(), 0)) + if(!pdb.renameColumn(curTable, m_table, QString(), sqlb::FieldPtr(), 0)) { QMessageBox::warning(this, QApplication::applicationName(), tr("Setting the rowid column for the table failed. Error message:\n%1").arg(pdb.lastError())); @@ -730,7 +729,7 @@ void EditTableDialog::setTemporary(bool is_temp) // Update table if we're editing an existing table if(!m_bNewTable) { - if(!pdb.renameColumn(sqlb::ObjectIdentifier("main", curTable), m_table, QString(), sqlb::FieldPtr(), 0)) + if(!pdb.renameColumn(curTable, m_table, QString(), sqlb::FieldPtr(), 0)) { QMessageBox::warning(this, QApplication::applicationName(), tr("Setting the temporary flag for the table failed. Error message:\n%1").arg(pdb.lastError())); diff --git a/src/EditTableDialog.h b/src/EditTableDialog.h index 64ebeaeb..1f4b08f3 100644 --- a/src/EditTableDialog.h +++ b/src/EditTableDialog.h @@ -18,7 +18,7 @@ class EditTableDialog : public QDialog Q_OBJECT public: - explicit EditTableDialog(DBBrowserDB& pdb, const QString& tableName, bool createTable, QWidget* parent = 0); + explicit EditTableDialog(DBBrowserDB& pdb, const sqlb::ObjectIdentifier& tableName, bool createTable, QWidget* parent = 0); ~EditTableDialog(); protected: @@ -61,7 +61,7 @@ private: Ui::EditTableDialog* ui; DBBrowserDB& pdb; ForeignKeyEditorDelegate* m_fkEditorDelegate; - QString curTable; + sqlb::ObjectIdentifier curTable; sqlb::Table m_table; QStringList types; QStringList fields; diff --git a/src/ExportDataDialog.cpp b/src/ExportDataDialog.cpp index 6545c23c..7276e950 100644 --- a/src/ExportDataDialog.cpp +++ b/src/ExportDataDialog.cpp @@ -12,7 +12,7 @@ #include #include -ExportDataDialog::ExportDataDialog(DBBrowserDB& db, ExportFormats format, QWidget* parent, const QString& query, const QString& selection) +ExportDataDialog::ExportDataDialog(DBBrowserDB& db, ExportFormats format, QWidget* parent, const QString& query, const sqlb::ObjectIdentifier& selection) : QDialog(parent), ui(new Ui::ExportDataDialog), pdb(db), @@ -42,9 +42,17 @@ ExportDataDialog::ExportDataDialog(DBBrowserDB& db, ExportFormats format, QWidge if(query.isEmpty()) { // Get list of tables to export - objectMap objects = pdb.getBrowsableObjects("main"); - foreach(const sqlb::ObjectPtr& obj, objects) - ui->listTables->addItem(new QListWidgetItem(QIcon(QString(":icons/%1").arg(sqlb::Object::typeToString(obj->type()))), obj->name())); + for(auto it=pdb.schemata.constBegin();it!=pdb.schemata.constEnd();++it) + { + QList tables = it->values("table") + it->values("view"); + for(auto jt=tables.constBegin();jt!=tables.constEnd();++jt) + { + sqlb::ObjectIdentifier obj(it.key(), (*jt)->name()); + QListWidgetItem* item = new QListWidgetItem(QIcon(QString(":icons/%1").arg(sqlb::Object::typeToString((*jt)->type()))), obj.toDisplayString()); + item->setData(Qt::UserRole, obj.toVariant()); + ui->listTables->addItem(item); + } + } // Sort list of tables and select the table specified in the selection parameter or alternatively the first one ui->listTables->model()->sort(0); @@ -52,9 +60,14 @@ ExportDataDialog::ExportDataDialog(DBBrowserDB& db, ExportFormats format, QWidge { ui->listTables->setCurrentItem(ui->listTables->item(0)); } else { - QList items = ui->listTables->findItems(selection, Qt::MatchExactly); - if(!items.isEmpty()) - ui->listTables->setCurrentItem(items.first()); + for(int i=0;ilistTables->count();i++) + { + if(sqlb::ObjectIdentifier(ui->listTables->item(i)->data(Qt::UserRole)) == selection) + { + ui->listTables->setCurrentRow(i); + break; + } + } } } else { // Hide table combo box @@ -320,8 +333,7 @@ void ExportDataDialog::accept() { // if we are called from execute sql tab, query is already set // and we only export 1 select - QString sQuery = QString("SELECT * FROM %1;").arg(sqlb::escapeIdentifier(selectedItems.at(i)->text())); - + QString sQuery = QString("SELECT * FROM %1;").arg(sqlb::ObjectIdentifier(selectedItems.at(i)->data(Qt::UserRole)).toString()); exportQuery(sQuery, filenames.at(i)); } } diff --git a/src/ExportDataDialog.h b/src/ExportDataDialog.h index 65ccafe7..4971a826 100644 --- a/src/ExportDataDialog.h +++ b/src/ExportDataDialog.h @@ -3,6 +3,8 @@ #include +#include "sqlitetypes.h" + class DBBrowserDB; namespace Ui { @@ -20,7 +22,8 @@ public: ExportFormatJson, }; - explicit ExportDataDialog(DBBrowserDB& db, ExportFormats format, QWidget* parent = 0, const QString& query = "", const QString& selection = ""); + explicit ExportDataDialog(DBBrowserDB& db, ExportFormats format, QWidget* parent = 0, + const QString& query = "", const sqlb::ObjectIdentifier& selection = sqlb::ObjectIdentifier()); ~ExportDataDialog(); private slots: diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 7c0fb46a..ece791a7 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -95,13 +95,15 @@ void MainWindow::init() // Set up DB structure tab dbStructureModel = new DbStructureModel(db, this); ui->dbTreeWidget->setModel(dbStructureModel); - ui->dbTreeWidget->setColumnHidden(1, true); - ui->dbTreeWidget->setColumnWidth(0, 300); + ui->dbTreeWidget->setColumnWidth(DbStructureModel::ColumnName, 300); + ui->dbTreeWidget->setColumnHidden(DbStructureModel::ColumnObjectType, true); + ui->dbTreeWidget->setColumnHidden(DbStructureModel::ColumnSchema, true); // Set up DB schema dock ui->treeSchemaDock->setModel(dbStructureModel); - ui->treeSchemaDock->setColumnHidden(1, true); - ui->treeSchemaDock->setColumnWidth(0, 300); + ui->treeSchemaDock->setColumnWidth(DbStructureModel::ColumnName, 300); + ui->treeSchemaDock->setColumnHidden(DbStructureModel::ColumnObjectType, true); + ui->treeSchemaDock->setColumnHidden(DbStructureModel::ColumnSchema, true); // Set up the table combo box in the Browse Data tab ui->comboBrowseTable->setModel(dbStructureModel); @@ -746,7 +748,7 @@ void MainWindow::createTable() return; } - EditTableDialog dialog(db, "", true, this); + EditTableDialog dialog(db, sqlb::ObjectIdentifier(), true, this); if(dialog.exec()) { populateTable(); @@ -760,7 +762,7 @@ void MainWindow::createIndex() return; } - EditIndexDialog dialog(db, "", true, this); + EditIndexDialog dialog(db, sqlb::ObjectIdentifier(), true, this); if(dialog.exec()) populateTable(); } @@ -774,16 +776,17 @@ void MainWindow::compact() void MainWindow::deleteObject() { // Get name and type of object to delete - QString table = ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), 0), Qt::EditRole).toString(); - QString type = ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), 1), Qt::EditRole).toString(); + sqlb::ObjectIdentifier name(ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), DbStructureModel::ColumnSchema), Qt::EditRole).toString(), + ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), DbStructureModel::ColumnName), Qt::EditRole).toString()); + QString type = ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), DbStructureModel::ColumnObjectType), Qt::EditRole).toString(); // Ask user if he really wants to delete that table - if(QMessageBox::warning(this, QApplication::applicationName(), tr("Are you sure you want to delete the %1 '%2'?\nAll data associated with the %1 will be lost.").arg(type).arg(table), + if(QMessageBox::warning(this, QApplication::applicationName(), tr("Are you sure you want to delete the %1 '%2'?\nAll data associated with the %1 will be lost.").arg(type).arg(name.name()), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { // Delete the table - QString statement = QString("DROP %1 %2;").arg(type.toUpper()).arg(sqlb::escapeIdentifier(table)); - if(!db.executeSQL( statement)) + QString statement = QString("DROP %1 %2;").arg(type.toUpper()).arg(name.toString()); + if(!db.executeSQL(statement)) { QString error = tr("Error: could not delete the %1. Message from database engine:\n%2").arg(type).arg(db.lastError()); QMessageBox::warning(this, QApplication::applicationName(), error); @@ -800,8 +803,9 @@ void MainWindow::editObject() return; // Get name and type of the object to edit - QString name = ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), 0), Qt::EditRole).toString(); - QString type = ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), 1), Qt::EditRole).toString(); + sqlb::ObjectIdentifier name(ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), DbStructureModel::ColumnSchema), Qt::EditRole).toString(), + ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), DbStructureModel::ColumnName), Qt::EditRole).toString()); + QString type = ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), DbStructureModel::ColumnObjectType), Qt::EditRole).toString(); if(type == "table") { @@ -1158,14 +1162,19 @@ void MainWindow::importTableFromCSV() void MainWindow::exportTableToCSV() { // Get the current table name if we are in the Browse Data tab - QString current_table; - if(ui->mainTab->currentIndex() == StructureTab) { - QString type = ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), 1)).toString(); + sqlb::ObjectIdentifier current_table; + if(ui->mainTab->currentIndex() == StructureTab) + { + QString type = ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), DbStructureModel::ColumnObjectType)).toString(); if(type == "table" || type == "view") - current_table = ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), 0)).toString(); + { + QString schema = ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), DbStructureModel::ColumnSchema)).toString(); + QString name = ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), DbStructureModel::ColumnName)).toString(); + current_table = sqlb::ObjectIdentifier(schema, name); + } + } else if(ui->mainTab->currentIndex() == BrowseTab) { + current_table = sqlb::ObjectIdentifier("main", ui->comboBrowseTable->currentText()); } - else if(ui->mainTab->currentIndex() == BrowseTab) - current_table = ui->comboBrowseTable->currentText(); // Open dialog ExportDataDialog dialog(db, ExportDataDialog::ExportFormatCsv, this, "", current_table); @@ -1175,14 +1184,19 @@ void MainWindow::exportTableToCSV() void MainWindow::exportTableToJson() { // Get the current table name if we are in the Browse Data tab - QString current_table; - if(ui->mainTab->currentIndex() == StructureTab) { - QString type = ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), 1)).toString(); + sqlb::ObjectIdentifier current_table; + if(ui->mainTab->currentIndex() == StructureTab) + { + QString type = ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), DbStructureModel::ColumnObjectType)).toString(); if(type == "table" || type == "view") - current_table = ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), 0)).toString(); + { + QString schema = ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), DbStructureModel::ColumnSchema)).toString(); + QString name = ui->dbTreeWidget->model()->data(ui->dbTreeWidget->currentIndex().sibling(ui->dbTreeWidget->currentIndex().row(), DbStructureModel::ColumnName)).toString(); + current_table = sqlb::ObjectIdentifier(schema, name); + } + } else if(ui->mainTab->currentIndex() == BrowseTab) { + current_table = sqlb::ObjectIdentifier("main", ui->comboBrowseTable->currentText()); } - else if(ui->mainTab->currentIndex() == BrowseTab) - current_table = ui->comboBrowseTable->currentText(); // Open dialog ExportDataDialog dialog(db, ExportDataDialog::ExportFormatJson, this, "", current_table); diff --git a/src/sqlitetypes.cpp b/src/sqlitetypes.cpp index 4344fe81..8b1fce14 100644 --- a/src/sqlitetypes.cpp +++ b/src/sqlitetypes.cpp @@ -1148,7 +1148,7 @@ QString Index::sql(const QString& schema, bool ifNotExists) const .arg(m_unique ? QString("UNIQUE ") : QString("")) .arg(ifNotExists ? QString(" IF NOT EXISTS") : QString("")) .arg(ObjectIdentifier(schema, m_name).toString(true)) - .arg(ObjectIdentifier(schema, m_table).toString(true)); + .arg(sqlb::escapeIdentifier(m_table)); // Add column list sql += columnSqlList().join(",\n"); diff --git a/src/sqlitetypes.h b/src/sqlitetypes.h index c8954b5b..aa4da038 100644 --- a/src/sqlitetypes.h +++ b/src/sqlitetypes.h @@ -7,6 +7,7 @@ #include #include #include +#include namespace sqlb { @@ -36,6 +37,18 @@ public: { } + ObjectIdentifier(QVariant variant) + { + QStringList str = variant.toStringList(); + m_schema = str.first(); + m_name = str.last(); + } + + bool operator==(const ObjectIdentifier& rhs) const + { + return (rhs.m_schema == m_schema && rhs.m_name == m_name); + } + const QString& schema() const { return m_schema; } const QString& name() const { return m_name; } void setSchema(const QString& schema) { m_schema = schema; } @@ -49,6 +62,7 @@ public: bool isEmpty() const { return m_name.isEmpty(); } + // This returns a string which can be used in SQL statements QString toString(bool shortName = false) const { if(shortName && m_schema == "main") @@ -57,6 +71,22 @@ public: return QString("%1.%2").arg(sqlb::escapeIdentifier(m_schema)).arg(sqlb::escapeIdentifier(m_name)); } + // This returns a string which can be used in the user interface + QString toDisplayString() const + { + if(m_schema == "main") + return m_name; + else + return QString("%1.%2").arg(m_schema).arg(m_name); + } + + QVariant toVariant() const + { + QStringList result; + result << m_schema << m_name; + return QVariant(result); + } + private: QString m_schema; QString m_name;