mirror of
https://github.com/sqlitebrowser/sqlitebrowser.git
synced 2026-01-20 02:50:46 -06:00
Add initial support for multiple database schemata
This adds initial basic support for handling different database schemata at once to the backend code. This is still far from working properly but shouldn't break much either - mostly because it's not really used yet in the user interface code.
This commit is contained in:
@@ -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<QString, sqlb::ObjectPtr> 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(";
|
||||
|
||||
@@ -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<sqlb::ObjectPtr> tables = pdb.objMap.values("table");
|
||||
QList<sqlb::ObjectPtr> 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<sqlb::Index>());
|
||||
index = *(pdb.getObjectByName(sqlb::ObjectIdentifier("main", curIndex)).dynamicCast<sqlb::Index>());
|
||||
|
||||
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<sqlb::Table>()->fieldInformation();
|
||||
sqlb::FieldInfoList tableFields = pdb.getObjectByName(sqlb::ObjectIdentifier("main", index.table())).dynamicCast<sqlb::Table>()->fieldInformation();
|
||||
ui->tableTableColumns->setRowCount(tableFields.size());
|
||||
int tableRows = 0;
|
||||
for(int i=0;i<tableFields.size();++i)
|
||||
|
||||
@@ -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(curTable).dynamicCast<sqlb::Table>());
|
||||
m_table = *(pdb.getObjectByName(sqlb::ObjectIdentifier("main", curTable)).dynamicCast<sqlb::Table>());
|
||||
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<sqlb::ConstraintPtr> fks = fkobj.dynamicCast<sqlb::Table>()->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<sqlb::Table>()->rowidColumn()))
|
||||
m.setQuery(QString("SELECT COUNT(%1) FROM %2.%3 WHERE %4 IS NULL;")
|
||||
.arg(sqlb::escapeIdentifier(pdb.getObjectByName(sqlb::ObjectIdentifier("main", curTable)).dynamicCast<sqlb::Table>()->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<sqlb::Table>());
|
||||
m_table = *(pdb.getObjectByName(sqlb::ObjectIdentifier("main", curTable)).dynamicCast<sqlb::Table>());
|
||||
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<sqlb::Table>());
|
||||
m_table = *(pdb.getObjectByName(sqlb::ObjectIdentifier("main", curTable)).dynamicCast<sqlb::Table>());
|
||||
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()));
|
||||
|
||||
@@ -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()));
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<sqlb::Table>()->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::Table>();
|
||||
sqlb::TablePtr tbl = pdb->getObjectByName(sqlb::ObjectIdentifier("main", tableName)).dynamicCast<sqlb::Table>();
|
||||
if(tbl)
|
||||
{
|
||||
foreach(const sqlb::FieldPtr& f, tbl->fields())
|
||||
|
||||
@@ -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<QString> 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; i<tablefields.size(); ++i)
|
||||
{
|
||||
QString format = storedData.displayFormats[i+1];
|
||||
@@ -498,9 +498,9 @@ void MainWindow::populateTable()
|
||||
}
|
||||
}
|
||||
if(only_defaults)
|
||||
m_browseTableModel->setTable(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::Table>();
|
||||
sqlb::TablePtr obj = db.getObjectByName(sqlb::ObjectIdentifier("main", table)).dynamicCast<sqlb::Table>();
|
||||
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<sqlb::Table>()->fields().at(field_number-1)->name();
|
||||
QString field_name = db.getObjectByName(sqlb::ObjectIdentifier("main", current_table)).dynamicCast<sqlb::Table>()->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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<sqlb::ObjectPtr> 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<sqlb::ObjectPtr> objects = db->schemata["main"].values("table");
|
||||
objects.append(db->schemata["main"].values("index"));
|
||||
for(QList<sqlb::ObjectPtr>::const_iterator i=objects.constBegin();i!=objects.constEnd();++i)
|
||||
{
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem(ui->treeSelectedObjects);
|
||||
|
||||
229
src/sqlitedb.cpp
229
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<sqlb::ObjectPtr> tables = objMap.values("table");
|
||||
QMutableListIterator<sqlb::ObjectPtr> 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<QByteArray>& rowdata)
|
||||
bool DBBrowserDB::getRow(const sqlb::ObjectIdentifier& table, const QString& rowid, QList<QByteArray>& rowdata)
|
||||
{
|
||||
QString sQuery = QString("SELECT * FROM %1 WHERE %2='%3';")
|
||||
.arg(sqlb::escapeIdentifier(sTableName))
|
||||
.arg(sqlb::escapeIdentifier(getObjectByName(sTableName).dynamicCast<sqlb::Table>()->rowidColumn()))
|
||||
.arg(table.toString())
|
||||
.arg(sqlb::escapeIdentifier(getObjectByName(table).dynamicCast<sqlb::Table>()->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::Table>();
|
||||
sqlb::TablePtr table = getObjectByName(tablename).dynamicCast<sqlb::Table>();
|
||||
|
||||
// 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<sqlb::Table>()->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;i<structure.size();i++)
|
||||
table.addField(structure.at(i));
|
||||
|
||||
// Execute it and update the schema
|
||||
return executeSQL(table.sql());
|
||||
return executeSQL(table.sql(name.schema()));
|
||||
}
|
||||
|
||||
bool DBBrowserDB::addColumn(const QString& tablename, const sqlb::FieldPtr& field)
|
||||
bool DBBrowserDB::addColumn(const sqlb::ObjectIdentifier& tablename, const sqlb::FieldPtr& field)
|
||||
{
|
||||
QString sql = QString("ALTER TABLE %1 ADD COLUMN %2").arg(sqlb::escapeIdentifier(tablename)).arg(field->toString());
|
||||
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<sqlb::Table>()->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<sqlb::Table>();
|
||||
foreach(const auto& column, columns)
|
||||
tab->addField(sqlb::FieldPtr(new sqlb::Field(column.first, column.second)));
|
||||
} else {
|
||||
sqlb::ViewPtr view = object.dynamicCast<sqlb::View>();
|
||||
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<sqlb::Trigger>();
|
||||
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<sqlb::Table>()->isVirtual()) || type == sqlb::Object::Types::View)
|
||||
{
|
||||
auto columns = queryColumnInformation(schema_name, val_name);
|
||||
|
||||
if(type == sqlb::Object::Types::Table)
|
||||
{
|
||||
sqlb::TablePtr tab = object.dynamicCast<sqlb::Table>();
|
||||
foreach(const auto& column, columns)
|
||||
tab->addField(sqlb::FieldPtr(new sqlb::Field(column.first, column.second)));
|
||||
} else {
|
||||
sqlb::ViewPtr view = object.dynamicCast<sqlb::View>();
|
||||
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<sqlb::Trigger>();
|
||||
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<QPair<QString, QString>> DBBrowserDB::queryColumnInformation(const QString& object_name)
|
||||
QVector<QPair<QString, QString>> DBBrowserDB::queryColumnInformation(const QString& schema_name, const QString& object_name)
|
||||
{
|
||||
QVector<QPair<QString, QString>> 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;
|
||||
|
||||
@@ -16,7 +16,8 @@ enum
|
||||
kLogMsg_App
|
||||
};
|
||||
|
||||
typedef QMultiMap<QString, sqlb::ObjectPtr> objectMap;
|
||||
typedef QMultiMap<QString, sqlb::ObjectPtr> objectMap; // Maps from object type (table, index, view, trigger) to a pointer to the object representation
|
||||
typedef QMap<QString, objectMap> 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<QByteArray>& rowdata);
|
||||
bool getRow(const sqlb::ObjectIdentifier& table, const QString& rowid, QList<QByteArray>& 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<QPair<QString, QString>> queryColumnInformation(const QString& object_name);
|
||||
QVector<QPair<QString, QString>> 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);
|
||||
|
||||
@@ -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<QString>& display_format)
|
||||
void SqliteTableModel::setTable(const sqlb::ObjectIdentifier& table, int sortColumn, Qt::SortOrder sortOrder, const QVector<QString>& 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)))
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
|
||||
#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<QString> &display_format = QVector<QString>());
|
||||
void setTable(const sqlb::ObjectIdentifier& table, int sortColumn = 0, Qt::SortOrder sortOrder = Qt::AscendingOrder, const QVector<QString> &display_format = QVector<QString>());
|
||||
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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -21,6 +21,47 @@ uint qHash(const QVector<T>& 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
|
||||
|
||||
Reference in New Issue
Block a user