diff --git a/src/DbStructureModel.cpp b/src/DbStructureModel.cpp index ae6fe49f..a2e6a9ca 100644 --- a/src/DbStructureModel.cpp +++ b/src/DbStructureModel.cpp @@ -142,7 +142,7 @@ void DbStructureModel::reloadData() QTreeWidgetItem* itemBrowsables = new QTreeWidgetItem(rootItem); itemBrowsables->setIcon(0, QIcon(QString(":/icons/view"))); - itemBrowsables->setText(0, tr("Browsables (%1)").arg(m_db.objMap.values("table").count() + m_db.objMap.values("view").count())); + 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); QTreeWidgetItem* itemAll = new QTreeWidgetItem(rootItem); @@ -151,27 +151,27 @@ void DbStructureModel::reloadData() QTreeWidgetItem* itemTables = new QTreeWidgetItem(itemAll); itemTables->setIcon(0, QIcon(QString(":/icons/table"))); - itemTables->setText(0, tr("Tables (%1)").arg(m_db.objMap.values("table").count())); + 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.objMap.values("index").count())); + 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.objMap.values("view").count())); + 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.objMap.values("trigger").count())); + 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.objMap.constBegin(); it != m_db.objMap.constEnd(); ++it) + 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 @@ -238,7 +238,7 @@ QMimeData* DbStructureModel::mimeData(const QModelIndexList& indices) const if(data(index.sibling(index.row(), 1), Qt::DisplayRole).toString() == "table") { SqliteTableModel tableModel(m_db); - tableModel.setTable(data(index.sibling(index.row(), 0), Qt::DisplayRole).toString()); + tableModel.setTable(sqlb::ObjectIdentifier("main", data(index.sibling(index.row(), 0), 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("; diff --git a/src/EditIndexDialog.cpp b/src/EditIndexDialog.cpp index 7e1a1514..88971cae 100644 --- a/src/EditIndexDialog.cpp +++ b/src/EditIndexDialog.cpp @@ -19,7 +19,7 @@ EditIndexDialog::EditIndexDialog(DBBrowserDB& db, const QString& indexName, bool // Get list of tables, sort it alphabetically and fill the combobox objectMap dbobjs; - QList tables = pdb.objMap.values("table"); + QList tables = pdb.schemata["main"].values("table"); for(auto it=tables.constBegin();it!=tables.constEnd();++it) dbobjs.insert((*it)->name(), (*it)); ui->comboTableName->blockSignals(true); @@ -34,7 +34,7 @@ EditIndexDialog::EditIndexDialog(DBBrowserDB& db, const QString& indexName, bool if(!newIndex) { // Load the current layout and fill in the dialog fields - index = *(pdb.getObjectByName(curIndex).dynamicCast()); + index = *(pdb.getObjectByName(sqlb::ObjectIdentifier("main", curIndex)).dynamicCast()); ui->editIndexName->blockSignals(true); ui->editIndexName->setText(index.name()); @@ -94,7 +94,7 @@ void EditIndexDialog::tableChanged(const QString& new_table, bool initialLoad) void EditIndexDialog::updateColumnLists() { // Fill the table column list - sqlb::FieldInfoList tableFields = pdb.getObjectByName(index.table()).dynamicCast()->fieldInformation(); + sqlb::FieldInfoList tableFields = pdb.getObjectByName(sqlb::ObjectIdentifier("main", index.table())).dynamicCast()->fieldInformation(); ui->tableTableColumns->setRowCount(tableFields.size()); int tableRows = 0; for(int i=0;i()); + m_table = *(pdb.getObjectByName(sqlb::ObjectIdentifier("main", 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 @@ -163,7 +163,7 @@ void EditTableDialog::accept() // Rename table if necessary if(ui->editTableName->text() != curTable) { - if(!pdb.renameTable(curTable, ui->editTableName->text())) + if(!pdb.renameTable("main", curTable, 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(curTable, m_table, f->name(), f, 0); + pdb.renameColumn(sqlb::ObjectIdentifier("main", 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(curTable, m_table, column, m_table.fields().at(index)); + pdb.renameColumn(sqlb::ObjectIdentifier("main", 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.objMap.values("table")) + foreach(const sqlb::ObjectPtr& fkobj, pdb.schemata["main"].values("table")) { QList fks = fkobj.dynamicCast()->constraints(sqlb::FieldVector(), sqlb::Constraint::ForeignKeyConstraintType); foreach(sqlb::ConstraintPtr fkptr, fks) @@ -352,8 +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 WHERE %3 IS NULL;") - .arg(sqlb::escapeIdentifier(pdb.getObjectByName(curTable).dynamicCast()->rowidColumn())) + 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)) .arg(sqlb::escapeIdentifier(field->name()))); if(m.data(m.index(0, 0)).toInt() > 0) @@ -489,7 +490,7 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column) if(callRenameColumn) { - if(!pdb.renameColumn(curTable, m_table, oldFieldName, field)) + if(!pdb.renameColumn(sqlb::ObjectIdentifier("main", curTable), m_table, oldFieldName, field)) QMessageBox::warning(this, qApp->applicationName(), tr("Modifying this column failed. Error returned from database:\n%1").arg(pdb.lastError())); } } @@ -548,7 +549,7 @@ void EditTableDialog::addField() // Actually add the new column to the table if we're editing an existing table if(!m_bNewTable) - pdb.addColumn(curTable, f); + pdb.addColumn(sqlb::ObjectIdentifier("main", curTable), f); checkInput(); } @@ -576,12 +577,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(curTable, m_table, ui->treeWidget->currentItem()->text(0), sqlb::FieldPtr())) + if(!pdb.renameColumn(sqlb::ObjectIdentifier("main", curTable), m_table, ui->treeWidget->currentItem()->text(0), sqlb::FieldPtr())) { QMessageBox::warning(0, QApplication::applicationName(), pdb.lastError()); } else { //relayout - m_table = *(pdb.getObjectByName(curTable).dynamicCast()); + m_table = *(pdb.getObjectByName(sqlb::ObjectIdentifier("main", curTable)).dynamicCast()); populateFields(); } } @@ -654,7 +655,7 @@ void EditTableDialog::moveCurrentField(bool down) // Move the actual column if(!pdb.renameColumn( - curTable, + sqlb::ObjectIdentifier("main", curTable), m_table, ui->treeWidget->currentItem()->text(0), m_table.fields().at(ui->treeWidget->indexOfTopLevelItem(ui->treeWidget->currentItem())), @@ -664,7 +665,7 @@ void EditTableDialog::moveCurrentField(bool down) QMessageBox::warning(0, QApplication::applicationName(), pdb.lastError()); } else { // Reload table SQL - m_table = *(pdb.getObjectByName(curTable).dynamicCast()); + m_table = *(pdb.getObjectByName(sqlb::ObjectIdentifier("main", curTable)).dynamicCast()); populateFields(); // Select old item at new position @@ -710,7 +711,7 @@ void EditTableDialog::setWithoutRowid(bool without_rowid) // Update table if we're editing an existing table if(!m_bNewTable) { - if(!pdb.renameColumn(curTable, m_table, QString(), sqlb::FieldPtr(), 0)) + if(!pdb.renameColumn(sqlb::ObjectIdentifier("main", 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())); @@ -729,7 +730,7 @@ void EditTableDialog::setTemporary(bool is_temp) // Update table if we're editing an existing table if(!m_bNewTable) { - if(!pdb.renameColumn(curTable, m_table, QString(), sqlb::FieldPtr(), 0)) + if(!pdb.renameColumn(sqlb::ObjectIdentifier("main", 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/ExportDataDialog.cpp b/src/ExportDataDialog.cpp index 93b6499f..6545c23c 100644 --- a/src/ExportDataDialog.cpp +++ b/src/ExportDataDialog.cpp @@ -42,7 +42,7 @@ ExportDataDialog::ExportDataDialog(DBBrowserDB& db, ExportFormats format, QWidge if(query.isEmpty()) { // Get list of tables to export - objectMap objects = pdb.getBrowsableObjects(); + 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())); diff --git a/src/ExportSqlDialog.cpp b/src/ExportSqlDialog.cpp index 3db109b7..858b4fe4 100644 --- a/src/ExportSqlDialog.cpp +++ b/src/ExportSqlDialog.cpp @@ -34,10 +34,9 @@ ExportSqlDialog::ExportSqlDialog(DBBrowserDB* db, QWidget* parent, const QString ui->comboOldSchema->setCurrentIndex(settings.value(sSettingsOldSchema, 0).toInt()); // Get list of tables to export - objectMap objects = pdb->getBrowsableObjects(); - for(auto it=objects.constBegin();it!=objects.constEnd();++it) { + objectMap objects = pdb->getBrowsableObjects("main"); + for(auto it=objects.constBegin();it!=objects.constEnd();++it) ui->listTables->addItem(new QListWidgetItem(QIcon(QString(":icons/%1").arg((*it)->type())), (*it)->name())); - } // Sort list of tables and select the table specified in the // selection parameter or all tables if table not specified diff --git a/src/ForeignKeyEditorDelegate.cpp b/src/ForeignKeyEditorDelegate.cpp index e8eb654a..78e8988d 100644 --- a/src/ForeignKeyEditorDelegate.cpp +++ b/src/ForeignKeyEditorDelegate.cpp @@ -81,7 +81,7 @@ ForeignKeyEditorDelegate::ForeignKeyEditorDelegate(const DBBrowserDB& db, sqlb:: , m_db(db) , m_table(table) { - const auto objects = m_db.getBrowsableObjects(); + const auto objects = m_db.getBrowsableObjects("main"); for (auto& obj : objects) { if (obj->type() == sqlb::Object::Types::Table) { QString tableName = obj->name(); diff --git a/src/ImportCsvDialog.cpp b/src/ImportCsvDialog.cpp index 32beb5b3..51f1b4f6 100644 --- a/src/ImportCsvDialog.cpp +++ b/src/ImportCsvDialog.cpp @@ -394,7 +394,7 @@ void ImportCsvDialog::importCsv(const QString& fileName, const QString &name) // Are we importing into an existing table? bool importToExistingTable = false; - const sqlb::ObjectPtr obj = pdb->getObjectByName(tableName); + const sqlb::ObjectPtr obj = pdb->getObjectByName(sqlb::ObjectIdentifier("main", tableName)); if(obj && obj->type() == sqlb::Object::Types::Table) { if((size_t)obj.dynamicCast()->fields().size() != csv.columns()) @@ -425,14 +425,14 @@ void ImportCsvDialog::importCsv(const QString& fileName, const QString &name) QStringList nullValues; if(!importToExistingTable) { - if(!pdb->createTable(tableName, fieldList)) + if(!pdb->createTable(sqlb::ObjectIdentifier("main", tableName), fieldList)) return rollback(this, pdb, progress, restorepointName, 0, tr("Creating the table failed: %1").arg(pdb->lastError())); } else { // Importing into an existing table. So find out something about it's structure. // Prepare the values for each table column that are to be inserted if the field in the CSV file is empty. Depending on the data type // and the constraints of a field, we need to handle this case differently. - sqlb::TablePtr tbl = pdb->getObjectByName(tableName).dynamicCast(); + sqlb::TablePtr tbl = pdb->getObjectByName(sqlb::ObjectIdentifier("main", tableName)).dynamicCast(); if(tbl) { foreach(const sqlb::FieldPtr& f, tbl->fields()) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index ca9750aa..7c0fb46a 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -388,7 +388,7 @@ void MainWindow::populateStructure() return; // Update table and column names for syntax highlighting - objectMap tab = db.getBrowsableObjects(); + objectMap tab = db.getBrowsableObjects("main"); SqlUiLexer::TablesAndColumnsMap tablesToColumnsMap; for(auto it=tab.constBegin();it!=tab.constEnd();++it) { @@ -454,7 +454,7 @@ void MainWindow::populateTable() // No stored settings found. // Set table name and apply default display format settings - m_browseTableModel->setTable(tablename, 0, Qt::AscendingOrder); + m_browseTableModel->setTable(sqlb::ObjectIdentifier("main", tablename), 0, Qt::AscendingOrder); // There aren't any information stored for this table yet, so use some default values @@ -485,7 +485,7 @@ void MainWindow::populateTable() // Load display formats and set them along with the table name QVector v; bool only_defaults = true; - const sqlb::FieldInfoList& tablefields = db.getObjectByName(tablename)->fieldInformation(); + const sqlb::FieldInfoList& tablefields = db.getObjectByName(sqlb::ObjectIdentifier("main", tablename))->fieldInformation(); for(int i=0; isetTable(tablename, storedData.sortOrderIndex, storedData.sortOrderMode); + m_browseTableModel->setTable(sqlb::ObjectIdentifier("main", tablename), storedData.sortOrderIndex, storedData.sortOrderMode); else - m_browseTableModel->setTable(tablename, storedData.sortOrderIndex, storedData.sortOrderMode, v); + m_browseTableModel->setTable(sqlb::ObjectIdentifier("main", tablename), storedData.sortOrderIndex, storedData.sortOrderMode, v); // There is information stored for this table, so extract it and apply it @@ -531,7 +531,7 @@ void MainWindow::populateTable() } // Show/hide menu options depending on whether this is a table or a view - if(db.getObjectByName(ui->comboBrowseTable->currentText())->type() == sqlb::Object::Table) + if(db.getObjectByName(sqlb::ObjectIdentifier("main", ui->comboBrowseTable->currentText()))->type() == sqlb::Object::Table) { // Table ui->actionUnlockViewEditing->setVisible(false); @@ -852,7 +852,7 @@ void MainWindow::doubleClickTable(const QModelIndex& index) // * Don't allow editing of other objects than tables (on the browse table) * bool isEditingAllowed = (m_currentTabTableModel == m_browseTableModel) && - (db.getObjectByName(ui->comboBrowseTable->currentText())->type() == sqlb::Object::Types::Table); + (db.getObjectByName(sqlb::ObjectIdentifier("main", ui->comboBrowseTable->currentText()))->type() == sqlb::Object::Types::Table); // Enable or disable the Apply, Null, & Import buttons in the Edit Cell // dock depending on the value of the "isEditingAllowed" bool above @@ -876,7 +876,7 @@ void MainWindow::dataTableSelectionChanged(const QModelIndex& index) } bool editingAllowed = (m_currentTabTableModel == m_browseTableModel) && - (db.getObjectByName(ui->comboBrowseTable->currentText())->type() == sqlb::Object::Types::Table); + (db.getObjectByName(sqlb::ObjectIdentifier("main", ui->comboBrowseTable->currentText()))->type() == sqlb::Object::Types::Table); // Don't allow editing of other objects than tables editDock->setReadOnly(!editingAllowed); @@ -2263,7 +2263,7 @@ void MainWindow::copyCurrentCreateStatement() void MainWindow::jumpToRow(const QString& table, QString column, const QByteArray& value) { // First check if table exists - sqlb::TablePtr obj = db.getObjectByName(table).dynamicCast(); + sqlb::TablePtr obj = db.getObjectByName(sqlb::ObjectIdentifier("main", table)).dynamicCast(); if(!obj) return; @@ -2300,7 +2300,7 @@ void MainWindow::showDataColumnPopupMenu(const QPoint& pos) void MainWindow::showRecordPopupMenu(const QPoint& pos) { const QString sCurrentTable = ui->comboBrowseTable->currentText(); - if(!(db.getObjectByName(sCurrentTable)->type() == sqlb::Object::Types::Table && !db.readOnly())) + if(!(db.getObjectByName(sqlb::ObjectIdentifier("main", sCurrentTable))->type() == sqlb::Object::Types::Table && !db.readOnly())) return; int row = ui->dataTable->verticalHeader()->logicalIndexAt(pos); @@ -2325,7 +2325,7 @@ void MainWindow::editDataColumnDisplayFormat() // column is always the rowid column. Ultimately, get the column name from the column object QString current_table = ui->comboBrowseTable->currentText(); int field_number = sender()->property("clicked_column").toInt(); - QString field_name = db.getObjectByName(current_table).dynamicCast()->fields().at(field_number-1)->name(); + QString field_name = db.getObjectByName(sqlb::ObjectIdentifier("main", current_table)).dynamicCast()->fields().at(field_number-1)->name(); // Get the current display format of the field QString current_displayformat = browseTableSettings[current_table].displayFormats[field_number]; @@ -2441,7 +2441,7 @@ void MainWindow::unlockViewEditing(bool unlock, QString pk) QString currentTable = ui->comboBrowseTable->currentText(); // If this isn't a view just unlock editing and return - if(db.getObjectByName(currentTable)->type() != sqlb::Object::View) + if(db.getObjectByName(sqlb::ObjectIdentifier("main", currentTable))->type() != sqlb::Object::View) { m_browseTableModel->setPseudoPk(QString()); enableEditing(true, true); diff --git a/src/SqlExecutionArea.cpp b/src/SqlExecutionArea.cpp index 73f2d531..45c9ebb5 100644 --- a/src/SqlExecutionArea.cpp +++ b/src/SqlExecutionArea.cpp @@ -90,7 +90,7 @@ void SqlExecutionArea::saveAsView() name = QInputDialog::getText(this, qApp->applicationName(), tr("Please specify the view name")).trimmed(); if(name.isEmpty()) return; - if(db.getObjectByName(name) != nullptr) + if(db.getObjectByName(sqlb::ObjectIdentifier("main", name)) != nullptr) QMessageBox::warning(this, qApp->applicationName(), tr("There is already an object with that name. Please choose a different name.")); else break; diff --git a/src/VacuumDialog.cpp b/src/VacuumDialog.cpp index 9e6e0042..b6512fe6 100644 --- a/src/VacuumDialog.cpp +++ b/src/VacuumDialog.cpp @@ -15,9 +15,9 @@ VacuumDialog::VacuumDialog(DBBrowserDB* _db, QWidget* parent) : // Show warning if DB is dirty ui->labelSavepointWarning->setVisible(db->getDirty()); - // Populate list of objects to compact - QList objects = db->objMap.values("table"); - objects.append(db->objMap.values("index")); + // Populate list of objects to compact. We just support vacuuming the main schema here. + QList objects = db->schemata["main"].values("table"); + objects.append(db->schemata["main"].values("index")); for(QList::const_iterator i=objects.constBegin();i!=objects.constEnd();++i) { QTreeWidgetItem* item = new QTreeWidgetItem(ui->treeSelectedObjects); diff --git a/src/sqlitedb.cpp b/src/sqlitedb.cpp index f0bd38a8..7fcc9f30 100644 --- a/src/sqlitedb.cpp +++ b/src/sqlitedb.cpp @@ -202,6 +202,9 @@ bool DBBrowserDB::attach(const QString& filename, QString attach_as) } #endif + // Update schema to load database schema of the newly attached database + updateSchema(); + return true; } @@ -440,7 +443,7 @@ bool DBBrowserDB::close() sqlite3_close(_db); } _db = 0; - objMap.clear(); + schemata.clear(); savepointList.clear(); emit dbChanged(getDirty()); emit structureUpdated(); @@ -464,6 +467,7 @@ bool DBBrowserDB::dump(const QString& filename, QApplication::setOverrideCursor(Qt::WaitCursor); size_t numRecordsTotal = 0, numRecordsCurrent = 0; + objectMap objMap = schemata["main"]; // We only always export the main database, not the attached databases QList tables = objMap.values("table"); QMutableListIterator it(tables); while(it.hasNext()) @@ -477,7 +481,7 @@ bool DBBrowserDB::dump(const QString& filename, } else { // Otherwise get the number of records in this table SqliteTableModel tableModel(*this); - tableModel.setTable(it.value()->name()); + tableModel.setTable(sqlb::ObjectIdentifier("main", it.value()->name())); numRecordsTotal += tableModel.totalRowCount(); } } @@ -507,7 +511,7 @@ bool DBBrowserDB::dump(const QString& filename, stream << QString("DROP TABLE IF EXISTS %1;\n").arg(sqlb::escapeIdentifier((*it)->name())); if((*it)->fullyParsed()) - stream << (*it)->sql(true) << "\n"; + stream << (*it)->sql("main", true) << "\n"; else stream << (*it)->originalSql() << ";\n"; } @@ -625,7 +629,7 @@ bool DBBrowserDB::dump(const QString& filename, .arg(sqlb::escapeIdentifier((*it)->name())); if((*it)->fullyParsed()) - stream << (*it)->sql(true) << "\n"; + stream << (*it)->sql("main", true) << "\n"; else stream << (*it)->originalSql() << ";\n"; } @@ -782,11 +786,11 @@ bool DBBrowserDB::executeMultiSQL(const QString& statement, bool dirty, bool log return true; } -bool DBBrowserDB::getRow(const QString& sTableName, const QString& rowid, QList& rowdata) +bool DBBrowserDB::getRow(const sqlb::ObjectIdentifier& table, const QString& rowid, QList& rowdata) { QString sQuery = QString("SELECT * FROM %1 WHERE %2='%3';") - .arg(sqlb::escapeIdentifier(sTableName)) - .arg(sqlb::escapeIdentifier(getObjectByName(sTableName).dynamicCast()->rowidColumn())) + .arg(table.toString()) + .arg(sqlb::escapeIdentifier(getObjectByName(table).dynamicCast()->rowidColumn())) .arg(rowid); QByteArray utf8Query = sQuery.toUtf8(); @@ -818,9 +822,9 @@ bool DBBrowserDB::getRow(const QString& sTableName, const QString& rowid, QList< return ret; } -QString DBBrowserDB::max(const sqlb::Table& t, sqlb::FieldPtr field) const +QString DBBrowserDB::max(const sqlb::ObjectIdentifier& tableName, sqlb::FieldPtr field) const { - QString sQuery = QString("SELECT MAX(CAST(%2 AS INTEGER)) FROM %1;").arg(sqlb::escapeIdentifier(t.name())).arg(sqlb::escapeIdentifier(field->name())); + QString sQuery = QString("SELECT MAX(CAST(%2 AS INTEGER)) FROM %1;").arg(tableName.toString()).arg(sqlb::escapeIdentifier(field->name())); QByteArray utf8Query = sQuery.toUtf8(); sqlite3_stmt *stmt; QString ret = "0"; @@ -839,9 +843,9 @@ QString DBBrowserDB::max(const sqlb::Table& t, sqlb::FieldPtr field) const return ret; } -QString DBBrowserDB::emptyInsertStmt(const sqlb::Table& t, const QString& pk_value) const +QString DBBrowserDB::emptyInsertStmt(const QString& schemaName, const sqlb::Table& t, const QString& pk_value) const { - QString stmt = QString("INSERT INTO %1").arg(sqlb::escapeIdentifier(t.name())); + QString stmt = QString("INSERT INTO %1.%2").arg(sqlb::escapeIdentifier(schemaName)).arg(sqlb::escapeIdentifier(t.name())); QStringList vals; QStringList fields; @@ -858,7 +862,7 @@ QString DBBrowserDB::emptyInsertStmt(const sqlb::Table& t, const QString& pk_val } else { if(f->notnull()) { - QString maxval = this->max(t, f); + QString maxval = this->max(sqlb::ObjectIdentifier(schemaName, t.name()), f); vals << QString::number(maxval.toLongLong() + 1); } else { vals << "NULL"; @@ -898,11 +902,11 @@ QString DBBrowserDB::emptyInsertStmt(const sqlb::Table& t, const QString& pk_val return stmt; } -QString DBBrowserDB::addRecord(const QString& sTableName) +QString DBBrowserDB::addRecord(const sqlb::ObjectIdentifier& tablename) { if (!isOpen()) return QString(); - sqlb::TablePtr table = getObjectByName(sTableName).dynamicCast(); + sqlb::TablePtr table = getObjectByName(tablename).dynamicCast(); // For tables without rowid we have to set the primary key by ourselves. We do so by querying for the largest value in the PK column // and adding one to it. @@ -910,10 +914,10 @@ QString DBBrowserDB::addRecord(const QString& sTableName) QString pk_value; if(table->isWithoutRowidTable()) { - pk_value = QString::number(max(*table, table->fields().at(table->findField(table->rowidColumn()))).toLongLong() + 1); - sInsertstmt = emptyInsertStmt(*table, pk_value); + pk_value = QString::number(max(tablename, table->fields().at(table->findField(table->rowidColumn()))).toLongLong() + 1); + sInsertstmt = emptyInsertStmt(tablename.schema(), *table, pk_value); } else { - sInsertstmt = emptyInsertStmt(*table); + sInsertstmt = emptyInsertStmt(tablename.schema(), *table); } if(!executeSQL(sInsertstmt)) @@ -928,7 +932,7 @@ QString DBBrowserDB::addRecord(const QString& sTableName) } } -bool DBBrowserDB::deleteRecords(const QString& table, const QStringList& rowids) +bool DBBrowserDB::deleteRecords(const sqlb::ObjectIdentifier& table, const QStringList& rowids) { if (!isOpen()) return false; @@ -937,7 +941,7 @@ bool DBBrowserDB::deleteRecords(const QString& table, const QStringList& rowids) quoted_rowids.append("'" + rowid + "'"); QString statement = QString("DELETE FROM %1 WHERE %2 IN (%3);") - .arg(sqlb::escapeIdentifier(table)) + .arg(table.toString()) .arg(sqlb::escapeIdentifier(getObjectByName(table).dynamicCast()->rowidColumn())) .arg(quoted_rowids.join(", ")); if(executeSQL(statement)) @@ -949,7 +953,7 @@ bool DBBrowserDB::deleteRecords(const QString& table, const QStringList& rowids) } } -bool DBBrowserDB::updateRecord(const QString& table, const QString& column, const QString& rowid, const QByteArray& value, bool itsBlob, const QString& pseudo_pk) +bool DBBrowserDB::updateRecord(const sqlb::ObjectIdentifier& table, const QString& column, const QString& rowid, const QByteArray& value, bool itsBlob, const QString& pseudo_pk) { if (!isOpen()) return false; @@ -972,7 +976,7 @@ bool DBBrowserDB::updateRecord(const QString& table, const QString& column, cons } QString sql = QString("UPDATE %1 SET %2=? WHERE %3='%4';") - .arg(sqlb::escapeIdentifier(table)) + .arg(table.toString()) .arg(sqlb::escapeIdentifier(column)) .arg(pk) .arg(rowid); @@ -1015,26 +1019,26 @@ bool DBBrowserDB::updateRecord(const QString& table, const QString& column, cons } } -bool DBBrowserDB::createTable(const QString& name, const sqlb::FieldVector& structure) +bool DBBrowserDB::createTable(const sqlb::ObjectIdentifier& name, const sqlb::FieldVector& structure) { // Build SQL statement - sqlb::Table table(name); + sqlb::Table table(name.name()); for(int i=0;itoString()); + QString sql = QString("ALTER TABLE %1 ADD COLUMN %2").arg(tablename.toString()).arg(field->toString()); // Execute it and update the schema return executeSQL(sql); } -bool DBBrowserDB::renameColumn(const QString& tablename, const sqlb::Table& table, const QString& name, sqlb::FieldPtr to, int move) +bool DBBrowserDB::renameColumn(const sqlb::ObjectIdentifier& tablename, const sqlb::Table& table, const QString& name, sqlb::FieldPtr to, int move) { /* * USE CASES: @@ -1121,7 +1125,7 @@ bool DBBrowserDB::renameColumn(const QString& tablename, const sqlb::Table& tabl // Create the new table NoStructureUpdateChecks nup(*this); - if(!executeSQL(newSchema.sql(), true, true)) + if(!executeSQL(newSchema.sql(tablename.schema()), true, true)) { QString error(tr("renameColumn: creating new table failed. DB says: %1").arg(lastErrorMessage)); revertToSavepoint(savepointName); @@ -1130,7 +1134,10 @@ bool DBBrowserDB::renameColumn(const QString& tablename, const sqlb::Table& tabl } // Copy the data from the old table to the new one - if(!executeSQL(QString("INSERT INTO sqlitebrowser_rename_column_new_table SELECT %1 FROM %2;").arg(select_cols).arg(sqlb::escapeIdentifier(tablename)))) + if(!executeSQL(QString("INSERT INTO %1.sqlitebrowser_rename_column_new_table SELECT %2 FROM %3;") + .arg(tablename.schema()) + .arg(select_cols) + .arg(tablename.toString()))) { QString error(tr("renameColumn: copying data to new table failed. DB says:\n%1").arg(lastErrorMessage)); revertToSavepoint(savepointName); @@ -1140,10 +1147,10 @@ bool DBBrowserDB::renameColumn(const QString& tablename, const sqlb::Table& tabl // Save all indices, triggers and views associated with this table because SQLite deletes them when we drop the table in the next step QStringList otherObjectsSql; - for(auto it=objMap.constBegin();it!=objMap.constEnd();++it) + for(auto it=schemata[tablename.schema()].constBegin();it!=schemata[tablename.schema()].constEnd();++it) { // If this object references the table and it's not the table itself save it's SQL string - if((*it)->baseTable() == tablename && (*it)->type() != sqlb::Object::Types::Table) + if((*it)->baseTable() == tablename.name() && (*it)->type() != sqlb::Object::Types::Table) { // If this is an index, update the fields first. This highly increases the chance that the SQL statement won't throw an // error later on when we try to recreate it. @@ -1186,7 +1193,7 @@ bool DBBrowserDB::renameColumn(const QString& tablename, const sqlb::Table& tabl setPragma("defer_foreign_keys", "1"); // Delete the old table - if(!executeSQL(QString("DROP TABLE %1;").arg(sqlb::escapeIdentifier(tablename)), true, true)) + if(!executeSQL(QString("DROP TABLE %1;").arg(tablename.toString()), true, true)) { QString error(tr("renameColumn: deleting old table failed. DB says: %1").arg(lastErrorMessage)); revertToSavepoint(savepointName); @@ -1195,7 +1202,7 @@ bool DBBrowserDB::renameColumn(const QString& tablename, const sqlb::Table& tabl } // Rename the temporary table - if(!renameTable("sqlitebrowser_rename_column_new_table", tablename)) + if(!renameTable(tablename.schema(), "sqlitebrowser_rename_column_new_table", tablename.name())) { revertToSavepoint(savepointName); return false; @@ -1231,9 +1238,12 @@ bool DBBrowserDB::renameColumn(const QString& tablename, const sqlb::Table& tabl return true; } -bool DBBrowserDB::renameTable(const QString& from_table, const QString& to_table) +bool DBBrowserDB::renameTable(const QString& schema, const QString& from_table, const QString& to_table) { - QString sql = QString("ALTER TABLE %1 RENAME TO %2").arg(sqlb::escapeIdentifier(from_table)).arg(sqlb::escapeIdentifier(to_table)); + QString sql = QString("ALTER TABLE %1.%2 RENAME TO %3") + .arg(sqlb::escapeIdentifier(schema)) + .arg(sqlb::escapeIdentifier(from_table)) + .arg(sqlb::escapeIdentifier(to_table)); if(!executeSQL(sql)) { QString error = tr("Error renaming table '%1' to '%2'." @@ -1246,11 +1256,11 @@ bool DBBrowserDB::renameTable(const QString& from_table, const QString& to_table } } -objectMap DBBrowserDB::getBrowsableObjects() const +objectMap DBBrowserDB::getBrowsableObjects(const QString& schema) const { objectMap res; - for(auto it=objMap.constBegin();it!=objMap.constEnd();++it) + for(auto it=schemata[schema].constBegin();it!=schemata[schema].constEnd();++it) { if(it.key() == "table" || it.key() == "view") res.insert(it.key(), it.value()); @@ -1259,11 +1269,11 @@ objectMap DBBrowserDB::getBrowsableObjects() const return res; } -const sqlb::ObjectPtr DBBrowserDB::getObjectByName(const QString& name) const +const sqlb::ObjectPtr DBBrowserDB::getObjectByName(const sqlb::ObjectIdentifier& name) const { - for(auto it=objMap.constBegin();it!=objMap.constEnd();++it) + for(auto it=schemata[name.schema()].constBegin();it!=schemata[name.schema()].constEnd();++it) { - if((*it)->name() == name) + if((*it)->name() == name.name()) return *it; } return sqlb::ObjectPtr(nullptr); @@ -1292,81 +1302,104 @@ void DBBrowserDB::logSQL(QString statement, int msgtype) emit sqlExecuted(statement, msgtype); } -void DBBrowserDB::updateSchema( ) +void DBBrowserDB::updateSchema() { - objMap.clear(); + schemata.clear(); // Exit here is no DB is opened if(!isOpen()) return; - QString statement = "SELECT type,name,sql,tbl_name,'0' AS temp FROM sqlite_master UNION SELECT type,name,sql,tbl_name,'1' AS temp FROM sqlite_temp_master;"; - QByteArray utf8Statement = statement.toUtf8(); - logSQL(statement, kLogMsg_App); - - sqlite3_stmt* vm; - const char* tail; - int err = sqlite3_prepare_v2(_db, utf8Statement, utf8Statement.length(), &vm, &tail); - if(err == SQLITE_OK) + // Get a list of all databases. This list always includes the main and the temp database but can include more items if there are attached databases + QString db_statement = "PRAGMA database_list;"; + QByteArray db_utf8Statement = db_statement.toUtf8(); + logSQL(db_statement, kLogMsg_App); + sqlite3_stmt* db_vm; + const char* db_tail; + if(sqlite3_prepare_v2(_db, db_utf8Statement, db_utf8Statement.length(), &db_vm, &db_tail) == SQLITE_OK) { - while(sqlite3_step(vm) == SQLITE_ROW) + // Loop through all the databases + while(sqlite3_step(db_vm) == SQLITE_ROW) { - QString val_type = QString::fromUtf8((const char*)sqlite3_column_text(vm, 0)); - QString val_name = QString::fromUtf8((const char*)sqlite3_column_text(vm, 1)); - QString val_sql = QString::fromUtf8((const char*)sqlite3_column_text(vm, 2)); - QString val_tblname = QString::fromUtf8((const char*)sqlite3_column_text(vm, 3)); - QString val_temp = QString::fromUtf8((const char*)sqlite3_column_text(vm, 4)); - val_sql.replace("\r", ""); + // Get the schema name which is in column 1 (counting starts with 0). 0 contains an ID and 2 the file path. + QString schema_name = QString::fromUtf8((const char*)sqlite3_column_text(db_vm, 1)); - sqlb::Object::Types type; - if(val_type == "table") - type = sqlb::Object::Types::Table; - else if(val_type == "index") - type = sqlb::Object::Types::Index; - else if(val_type == "trigger") - type = sqlb::Object::Types::Trigger; - else if(val_type == "view") - type = sqlb::Object::Types::View; + // Get a list of all the tables for the current database schema. We need to do this differently for normal databases and the temporary schema + // because SQLite doesn't understand the "temp.sqlite_master" notation. + QString statement; + if(schema_name == "temp") + statement = QString("SELECT type,name,sql,tbl_name FROM sqlite_temp_master;"); else - continue; + statement = QString("SELECT type,name,sql,tbl_name FROM %1.sqlite_master;").arg(sqlb::escapeIdentifier(schema_name)); + QByteArray utf8Statement = statement.toUtf8(); + logSQL(statement, kLogMsg_App); - if(!val_sql.isEmpty()) + sqlite3_stmt* vm; + const char* tail; + int err = sqlite3_prepare_v2(_db, utf8Statement, utf8Statement.length(), &vm, &tail); + if(err == SQLITE_OK) { - sqlb::ObjectPtr object = sqlb::Object::parseSQL(type, val_sql); - if(val_temp == "1") - object->setTemporary(true); - - // If parsing wasn't successful set the object name manually, so that at least the name is going to be correct - if(!object->fullyParsed()) - object->setName(val_name); - - // For virtual tables and views query the column list using the SQLite pragma because for both we can't yet rely on our grammar parser - if((type == sqlb::Object::Types::Table && object.dynamicCast()->isVirtual()) || type == sqlb::Object::Types::View) + while(sqlite3_step(vm) == SQLITE_ROW) { - auto columns = queryColumnInformation(val_name); + QString val_type = QString::fromUtf8((const char*)sqlite3_column_text(vm, 0)); + QString val_name = QString::fromUtf8((const char*)sqlite3_column_text(vm, 1)); + QString val_sql = QString::fromUtf8((const char*)sqlite3_column_text(vm, 2)); + QString val_tblname = QString::fromUtf8((const char*)sqlite3_column_text(vm, 3)); + val_sql.replace("\r", ""); - if(type == sqlb::Object::Types::Table) + sqlb::Object::Types type; + if(val_type == "table") + type = sqlb::Object::Types::Table; + else if(val_type == "index") + type = sqlb::Object::Types::Index; + else if(val_type == "trigger") + type = sqlb::Object::Types::Trigger; + else if(val_type == "view") + type = sqlb::Object::Types::View; + else + continue; + + if(!val_sql.isEmpty()) { - sqlb::TablePtr tab = object.dynamicCast(); - foreach(const auto& column, columns) - tab->addField(sqlb::FieldPtr(new sqlb::Field(column.first, column.second))); - } else { - sqlb::ViewPtr view = object.dynamicCast(); - foreach(const auto& column, columns) - view->addField(sqlb::FieldPtr(new sqlb::Field(column.first, column.second))); - } - } else if(type == sqlb::Object::Types::Trigger) { - // For triggers set the name of the table the trigger operates on here because we don't have a parser for trigger statements yet. - sqlb::TriggerPtr trg = object.dynamicCast(); - trg->setTable(val_tblname); - } + sqlb::ObjectPtr object = sqlb::Object::parseSQL(type, val_sql); + if(schema_name == "temp") + object->setTemporary(true); - objMap.insert(val_type, object); + // If parsing wasn't successful set the object name manually, so that at least the name is going to be correct + if(!object->fullyParsed()) + object->setName(val_name); + + // For virtual tables and views query the column list using the SQLite pragma because for both we can't yet rely on our grammar parser + if((type == sqlb::Object::Types::Table && object.dynamicCast()->isVirtual()) || type == sqlb::Object::Types::View) + { + auto columns = queryColumnInformation(schema_name, val_name); + + if(type == sqlb::Object::Types::Table) + { + sqlb::TablePtr tab = object.dynamicCast(); + foreach(const auto& column, columns) + tab->addField(sqlb::FieldPtr(new sqlb::Field(column.first, column.second))); + } else { + sqlb::ViewPtr view = object.dynamicCast(); + foreach(const auto& column, columns) + view->addField(sqlb::FieldPtr(new sqlb::Field(column.first, column.second))); + } + } else if(type == sqlb::Object::Types::Trigger) { + // For triggers set the name of the table the trigger operates on here because we don't have a parser for trigger statements yet. + sqlb::TriggerPtr trg = object.dynamicCast(); + trg->setTable(val_tblname); + } + + schemata[schema_name].insert(val_type, object); + } + } + sqlite3_finalize(vm); + } else { + qWarning() << tr("could not get list of db objects: %1, %2").arg(err).arg(sqlite3_errmsg(_db)); } } - sqlite3_finalize(vm); } else { - qWarning() << tr("could not get list of db objects: %1, %2").arg(err).arg(sqlite3_errmsg(_db)); + qWarning() << tr("could not get list of databases: %1").arg(sqlite3_errmsg(_db)); } emit structureUpdated(); @@ -1475,10 +1508,10 @@ bool DBBrowserDB::loadExtension(const QString& filename) } } -QVector> DBBrowserDB::queryColumnInformation(const QString& object_name) +QVector> DBBrowserDB::queryColumnInformation(const QString& schema_name, const QString& object_name) { QVector> result; - QString statement = QString("PRAGMA TABLE_INFO(%1);").arg(sqlb::escapeIdentifier(object_name)); + QString statement = QString("PRAGMA %1.TABLE_INFO(%2);").arg(sqlb::escapeIdentifier(schema_name)).arg(sqlb::escapeIdentifier(object_name)); logSQL(statement, kLogMsg_App); sqlite3_stmt* vm; diff --git a/src/sqlitedb.h b/src/sqlitedb.h index 9d8071d1..b6f48e3b 100644 --- a/src/sqlitedb.h +++ b/src/sqlitedb.h @@ -16,7 +16,8 @@ enum kLogMsg_App }; -typedef QMultiMap objectMap; +typedef QMultiMap objectMap; // Maps from object type (table, index, view, trigger) to a pointer to the object representation +typedef QMap schemaMap; // Maps from the schema name (main, temp, attached schemas) to the object map for that schema class DBBrowserDB : public QObject { @@ -42,50 +43,53 @@ public: /** * @brief getRow Executes a sqlite statement to get the rowdata(columns) * for the given rowid. + * @param schemaName Name of the database schema. * @param sTableName Table to query. * @param rowid The rowid to fetch. * @param rowdata A list of QByteArray containing the row data. * @return true if statement execution was ok, else false. */ - bool getRow(const QString& sTableName, const QString& rowid, QList& rowdata); + bool getRow(const sqlb::ObjectIdentifier& table, const QString& rowid, QList& rowdata); /** * @brief max Queries the table t for the max value of field. - * @param t Table to query + * @param tableName Table to query * @param field Field to get the max value * @return the max value of the field or 0 on error */ - QString max(const sqlb::Table& t, sqlb::FieldPtr field) const; + QString max(const sqlb::ObjectIdentifier& tableName, sqlb::FieldPtr field) const; void updateSchema(); - QString addRecord(const QString& sTableName); + QString addRecord(const sqlb::ObjectIdentifier& tablename); /** * @brief Creates an empty insert statement. + * @param schemaName The name of the database schema in which to find the table * @param pk_value This optional parameter can be used to manually set a specific value for the primary key column * @return An sqlite conform INSERT INTO statement with empty values. (NULL,'',0) */ - QString emptyInsertStmt(const sqlb::Table& t, const QString& pk_value = QString()) const; - bool deleteRecords(const QString& table, const QStringList& rowids); - bool updateRecord(const QString& table, const QString& column, const QString& rowid, const QByteArray& value, bool itsBlob, const QString& pseudo_pk = QString()); + QString emptyInsertStmt(const QString& schemaName, const sqlb::Table& t, const QString& pk_value = QString()) const; + bool deleteRecords(const sqlb::ObjectIdentifier& table, const QStringList& rowids); + bool updateRecord(const sqlb::ObjectIdentifier& table, const QString& column, const QString& rowid, const QByteArray& value, bool itsBlob, const QString& pseudo_pk = QString()); - bool createTable(const QString& name, const sqlb::FieldVector& structure); - bool renameTable(const QString& from_table, const QString& to_table); - bool addColumn(const QString& table, const sqlb::FieldPtr& field); + bool createTable(const sqlb::ObjectIdentifier& name, const sqlb::FieldVector& structure); + bool renameTable(const QString& schema, const QString& from_table, const QString& to_table); + bool addColumn(const sqlb::ObjectIdentifier& tablename, const sqlb::FieldPtr& field); /** * @brief renameColumn Can be used to rename, modify or drop an existing column of a given table - * @param table_name Specifies the name of the table to edit + * @param schema Specifies the name of the schema, i.e. the database name, of the table + * @param tablename Specifies the name of the table to edit * @param table Specifies the table to edit. The table constraints are used from this but not the columns * @param name Name of the column to edit * @param to The new field definition with changed name, type or the like. If Null-Pointer is given the column is dropped. * @param move Set this to a value != 0 to move the new column to a different position * @return true if renaming was successful, false if not. In the latter case also lastErrorMessage is set */ - bool renameColumn(const QString& tablename, const sqlb::Table& table, const QString& name, sqlb::FieldPtr to, int move = 0); + bool renameColumn(const sqlb::ObjectIdentifier& tablename, const sqlb::Table& table, const QString& name, sqlb::FieldPtr to, int move = 0); - objectMap getBrowsableObjects() const; - const sqlb::ObjectPtr getObjectByName(const QString& name) const; + objectMap getBrowsableObjects(const QString& schema) const; + const sqlb::ObjectPtr getObjectByName(const sqlb::ObjectIdentifier& name) const; bool isOpen() const; bool encrypted() const { return isEncrypted; } bool readOnly() const { return isReadOnly; } @@ -100,13 +104,13 @@ public: bool loadExtension(const QString& filename); - QVector> queryColumnInformation(const QString& object_name); + QVector> queryColumnInformation(const QString& schema_name, const QString& object_name); QString generateSavepointName(const QString& identifier = QString()) const; sqlite3 * _db; - objectMap objMap; + schemaMap schemata; signals: void sqlExecuted(QString sql, int msgtype); diff --git a/src/sqlitetablemodel.cpp b/src/sqlitetablemodel.cpp index 30ce27f2..f2f468a3 100644 --- a/src/sqlitetablemodel.cpp +++ b/src/sqlitetablemodel.cpp @@ -40,7 +40,7 @@ void SqliteTableModel::setChunkSize(size_t chunksize) m_chunkSize = chunksize; } -void SqliteTableModel::setTable(const QString& table, int sortColumn, Qt::SortOrder sortOrder, const QVector& display_format) +void SqliteTableModel::setTable(const sqlb::ObjectIdentifier& table, int sortColumn, Qt::SortOrder sortOrder, const QVector& display_format) { // Unset all previous settings. When setting a table all information on the previously browsed data set is removed first. reset(); @@ -85,7 +85,7 @@ void SqliteTableModel::setTable(const QString& table, int sortColumn, Qt::SortOr // NOTE: It would be nice to eventually get rid of this piece here. As soon as the grammar parser is good enough... if(!allOk) { - QString sColumnQuery = QString::fromUtf8("SELECT * FROM %1;").arg(sqlb::escapeIdentifier(table)); + QString sColumnQuery = QString::fromUtf8("SELECT * FROM %1;").arg(table.toString()); m_sRowidColumn = "rowid"; m_headers.push_back("rowid"); m_headers.append(getColumns(sColumnQuery, m_vDataTypes)); @@ -409,7 +409,7 @@ void SqliteTableModel::sort(int column, Qt::SortOrder order) m_sSortOrder = (order == Qt::AscendingOrder ? "ASC" : "DESC"); // Set the new query (but only if a table has already been set - if(m_sTable != "") + if(!m_sTable.isEmpty()) buildQuery(); } @@ -597,7 +597,7 @@ void SqliteTableModel::buildQuery() QString sql = QString("SELECT %1,%2 FROM %3 ") .arg(sqlb::escapeIdentifier(m_headers.at(0))) .arg(selector) - .arg(sqlb::escapeIdentifier(m_sTable)) + .arg(m_sTable.toString()) + where + QString("ORDER BY %1 %2") .arg(sqlb::escapeIdentifier(m_headers.at(m_iSortColumn))) diff --git a/src/sqlitetablemodel.h b/src/sqlitetablemodel.h index 445f58a0..cc188b2c 100644 --- a/src/sqlitetablemodel.h +++ b/src/sqlitetablemodel.h @@ -5,8 +5,9 @@ #include #include +#include "sqlitetypes.h" + class DBBrowserDB; -namespace sqlb { class ForeignKeyClause; } class SqliteTableModel : public QAbstractTableModel { @@ -38,7 +39,7 @@ public: void setQuery(const QString& sQuery, bool dontClearHeaders = false); QString query() const { return m_sQuery; } - void setTable(const QString& table, int sortColumn = 0, Qt::SortOrder sortOrder = Qt::AscendingOrder, const QVector &display_format = QVector()); + void setTable(const sqlb::ObjectIdentifier& table, int sortColumn = 0, Qt::SortOrder sortOrder = Qt::AscendingOrder, const QVector &display_format = QVector()); void setChunkSize(size_t chunksize); void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); @@ -92,7 +93,7 @@ private: DataType m_data; QString m_sQuery; - QString m_sTable; + sqlb::ObjectIdentifier m_sTable; QString m_sRowidColumn; QString m_pseudoPk; int m_iSortColumn; @@ -116,4 +117,4 @@ private: QString m_encoding; }; -#endif // SQLITETABLEMODEL_H +#endif diff --git a/src/sqlitetypes.cpp b/src/sqlitetypes.cpp index 6bca8f0c..4344fe81 100644 --- a/src/sqlitetypes.cpp +++ b/src/sqlitetypes.cpp @@ -427,17 +427,17 @@ ObjectPtr Table::parseSQL(const QString &sSQL) return TablePtr(new Table("")); } -QString Table::sql(bool ifNotExists) const +QString Table::sql(const QString& schema, bool ifNotExists) const { // Special handling for virtual tables: just build an easy create statement and copy the using part in there if(isVirtual()) - return QString("CREATE VIRTUAL TABLE %1 USING %2;").arg(escapeIdentifier(m_name)).arg(m_virtual); + return QString("CREATE VIRTUAL TABLE %1 USING %2;").arg(ObjectIdentifier(schema, m_name).toString(true)).arg(m_virtual); // This is a normal table, not a virtual one QString sql = QString("CREATE %1TABLE%2 %3 (\n") .arg(m_temporary ? QString("TEMPORARY ") : QString("")) .arg(ifNotExists ? QString(" IF NOT EXISTS") : QString("")) - .arg(escapeIdentifier(m_name)); + .arg(ObjectIdentifier(schema, m_name).toString(true)); sql += fieldList().join(",\n"); @@ -1141,14 +1141,14 @@ QStringList Index::columnSqlList() const return sl; } -QString Index::sql(bool ifNotExists) const +QString Index::sql(const QString& schema, bool ifNotExists) const { // Start CREATE (UNIQUE) INDEX statement QString sql = QString("CREATE %1INDEX%2 %3 ON %4 (\n") .arg(m_unique ? QString("UNIQUE ") : QString("")) .arg(ifNotExists ? QString(" IF NOT EXISTS") : QString("")) - .arg(escapeIdentifier(m_name)) - .arg(escapeIdentifier(m_table)); + .arg(ObjectIdentifier(schema, m_name).toString(true)) + .arg(ObjectIdentifier(schema, m_table).toString(true)); // Add column list sql += columnSqlList().join(",\n"); diff --git a/src/sqlitetypes.h b/src/sqlitetypes.h index 5e32d6d1..c8954b5b 100644 --- a/src/sqlitetypes.h +++ b/src/sqlitetypes.h @@ -21,6 +21,47 @@ uint qHash(const QVector& key, uint seed = 0) QString escapeIdentifier(QString id); +class ObjectIdentifier +{ +public: + ObjectIdentifier(const QString& schema, const QString& name) + : m_schema(schema), + m_name(name) + { + } + + ObjectIdentifier() + : m_schema("main"), + m_name(QString()) + { + } + + const QString& schema() const { return m_schema; } + const QString& name() const { return m_name; } + void setSchema(const QString& schema) { m_schema = schema; } + void setName(const QString& name) { m_name = name; } + + void clear() + { + m_schema = "main"; + m_name.clear(); + } + + bool isEmpty() const { return m_name.isEmpty(); } + + QString toString(bool shortName = false) const + { + if(shortName && m_schema == "main") + return sqlb::escapeIdentifier(m_name); + else + return QString("%1.%2").arg(sqlb::escapeIdentifier(m_schema)).arg(sqlb::escapeIdentifier(m_name)); + } + +private: + QString m_schema; + QString m_name; +}; + class Object; class Table; class Index; @@ -89,14 +130,15 @@ public: /** * @brief Returns the CREATE statement for this object + * @param schema The schema name of the object * @param ifNotExists If set to true the "IF NOT EXISTS" qualifier will be added to the create statement * @return A QString with the CREATE statement. */ - virtual QString sql(bool ifNotExists = false) const = 0; + virtual QString sql(const QString& schema = QString("main"), bool ifNotExists = false) const = 0; /** * @brief parseSQL Parses the CREATE statement in sSQL. - * @param sSQL The type of the object. + * @param type The type of the object. * @param sSQL The create statement. * @return The parsed database object. The object may be empty if parsing failed. */ @@ -283,7 +325,7 @@ public: * @brief Returns the CREATE TABLE statement for this table object * @return A QString with the CREATE TABLE object. */ - QString sql(bool ifNotExists = false) const; + QString sql(const QString& schema = QString("main"), bool ifNotExists = false) const; void addField(const FieldPtr& f); bool removeField(const QString& sFieldName); @@ -403,7 +445,7 @@ public: * @brief Returns the CREATE INDEX statement for this index object * @return A QString with the CREATE INDEX object. */ - QString sql(bool ifNotExists = false) const; + QString sql(const QString& schema = QString("main"), bool ifNotExists = false) const; /** * @brief parseSQL Parses the CREATE INDEX statement in sSQL. @@ -431,7 +473,7 @@ public: virtual Types type() const { return Object::View; } - QString sql(bool ifNotExists = false) const { /* TODO */ Q_UNUSED(ifNotExists); return m_originalSql; } + QString sql(const QString& schema = QString("main"), bool ifNotExists = false) const { /* TODO */ Q_UNUSED(schema); Q_UNUSED(ifNotExists); return m_originalSql; } static ObjectPtr parseSQL(const QString& sSQL); @@ -456,7 +498,7 @@ public: virtual Types type() const { return Object::Trigger; } - QString sql(bool ifNotExists = false) const { /* TODO */ Q_UNUSED(ifNotExists); return m_originalSql; } + QString sql(const QString& schema = QString("main"), bool ifNotExists = false) const { /* TODO */ Q_UNUSED(schema); Q_UNUSED(ifNotExists); return m_originalSql; } static ObjectPtr parseSQL(const QString& sSQL); @@ -471,4 +513,4 @@ private: } //namespace sqlb -#endif // SQLITETYPES_H +#endif