mirror of
https://github.com/sqlitebrowser/sqlitebrowser.git
synced 2026-01-20 02:50:46 -06:00
Allow updating views
This adds a new context menu option that allows unlocking views for updating. This requires appropriate triggers to be in place and the user to type in a column name that can be used as a 'primary key' for the view. By default views are still locked from editing. Inserting into and deleting from views isn't supported yet. See issue #141.
This commit is contained in:
@@ -156,6 +156,7 @@ void MainWindow::init()
|
||||
popupBrowseDataHeaderMenu->addAction(ui->actionShowRowidColumn);
|
||||
popupBrowseDataHeaderMenu->addAction(ui->actionBrowseTableEditDisplayFormat);
|
||||
popupBrowseDataHeaderMenu->addAction(ui->actionSetTableEncoding);
|
||||
popupBrowseDataHeaderMenu->addAction(ui->actionUnlockViewEditing);
|
||||
popupBrowseDataHeaderMenu->addSeparator();
|
||||
popupBrowseDataHeaderMenu->addAction(ui->actionSetAllTablesEncoding);
|
||||
|
||||
@@ -453,6 +454,9 @@ void MainWindow::populateTable()
|
||||
// Hide rowid column. Needs to be done before the column widths setting because of the workaround in there
|
||||
showRowidColumn(false);
|
||||
|
||||
// Enable editing in general, but lock view editing
|
||||
unlockViewEditing(false);
|
||||
|
||||
// Column widths
|
||||
for(int i=1;i<m_browseTableModel->columnCount();i++)
|
||||
ui->dataTable->setColumnWidth(i, ui->dataTable->horizontalHeader()->defaultSectionSize());
|
||||
@@ -497,6 +501,9 @@ void MainWindow::populateTable()
|
||||
// because of the filter row generation.
|
||||
showRowidColumn(storedData.showRowid);
|
||||
|
||||
// Enable editing in general and (un)lock view editing depending on the settings
|
||||
unlockViewEditing(!storedData.unlockViewPk.isEmpty(), storedData.unlockViewPk);
|
||||
|
||||
// Column widths
|
||||
for(auto widthIt=storedData.columnWidths.constBegin();widthIt!=storedData.columnWidths.constEnd();++widthIt)
|
||||
ui->dataTable->setColumnWidth(widthIt.key(), widthIt.value());
|
||||
@@ -516,11 +523,15 @@ void MainWindow::populateTable()
|
||||
plotDock->updatePlot(m_browseTableModel, &browseTableSettings[ui->comboBrowseTable->currentText()], true, false);
|
||||
}
|
||||
|
||||
// Activate the add and delete record buttons and editing only if a table has been selected
|
||||
bool editable = db.getObjectByName(tablename)->type() == sqlb::Object::Types::Table && !db.readOnly();
|
||||
ui->buttonNewRecord->setEnabled(editable);
|
||||
ui->buttonDeleteRecord->setEnabled(editable);
|
||||
ui->dataTable->setEditTriggers(editable ? QAbstractItemView::SelectedClicked | QAbstractItemView::AnyKeyPressed | QAbstractItemView::EditKeyPressed : QAbstractItemView::NoEditTriggers);
|
||||
// Show/hide menu options depending on whether this is a table or a view
|
||||
if(db.getObjectByName(ui->comboBrowseTable->currentText())->type() == sqlb::Object::Table)
|
||||
{
|
||||
// Table
|
||||
ui->actionUnlockViewEditing->setVisible(false);
|
||||
} else {
|
||||
// View
|
||||
ui->actionUnlockViewEditing->setVisible(true);
|
||||
}
|
||||
|
||||
// Set the recordset label
|
||||
setRecordsetLabel();
|
||||
@@ -1438,6 +1449,19 @@ void MainWindow::activateFields(bool enable)
|
||||
ui->actionSave_Remote->setEnabled(enable);
|
||||
}
|
||||
|
||||
void MainWindow::enableEditing(bool enable_edit, bool enable_insertdelete)
|
||||
{
|
||||
// Don't enable anything if this is a read only database
|
||||
bool edit = enable_edit && !db.readOnly();
|
||||
bool insertdelete = enable_insertdelete && !db.readOnly();
|
||||
|
||||
// Apply settings
|
||||
ui->buttonNewRecord->setEnabled(insertdelete);
|
||||
ui->buttonDeleteRecord->setEnabled(insertdelete);
|
||||
ui->dataTable->setEditTriggers(edit ? QAbstractItemView::SelectedClicked | QAbstractItemView::AnyKeyPressed | QAbstractItemView::EditKeyPressed : QAbstractItemView::NoEditTriggers);
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::browseTableHeaderClicked(int logicalindex)
|
||||
{
|
||||
// Abort if there is more than one column selected because this tells us that the user pretty sure wants to do a range selection instead of sorting data
|
||||
@@ -1939,6 +1963,7 @@ bool MainWindow::loadProject(QString filename, bool readOnly)
|
||||
ui->dataTable->sortByColumn(browseTableSettings[ui->comboBrowseTable->currentText()].sortOrderIndex,
|
||||
browseTableSettings[ui->comboBrowseTable->currentText()].sortOrderMode);
|
||||
showRowidColumn(browseTableSettings[ui->comboBrowseTable->currentText()].showRowid);
|
||||
unlockViewEditing(!browseTableSettings[ui->comboBrowseTable->currentText()].unlockViewPk.isEmpty(), browseTableSettings[ui->comboBrowseTable->currentText()].unlockViewPk);
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
@@ -2366,3 +2391,48 @@ void MainWindow::fileOpenReadOnly()
|
||||
// Redirect to 'standard' fileOpen(), with the read only flag set
|
||||
fileOpen(QString(), false, true);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
m_browseTableModel->setPseudoPk(QString());
|
||||
enableEditing(true, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the view gets unlocked for editing and we don't have a 'primary key' for this view yet, then ask for one
|
||||
if(unlock && pk.isEmpty())
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
// Ask for a PK
|
||||
pk = QInputDialog::getText(this, qApp->applicationName(), tr("Please enter a pseudo-primary key in order to enable editing on this view. "
|
||||
"This should be the name of a unique column in the view."));
|
||||
|
||||
// Cancelled?
|
||||
if(pk.isEmpty())
|
||||
return;
|
||||
|
||||
// Do some basic testing of the input and if the input appears to be good, go on
|
||||
if(db.executeSQL(QString("SELECT %1 FROM %2 LIMIT 1;").arg(sqlb::escapeIdentifier(pk)).arg(sqlb::escapeIdentifier(currentTable)), false, true))
|
||||
break;
|
||||
}
|
||||
} else if(!unlock) {
|
||||
// Locking the view is done by unsetting the pseudo-primary key
|
||||
pk.clear();
|
||||
}
|
||||
|
||||
// (De)activate editing
|
||||
enableEditing(unlock, false);
|
||||
m_browseTableModel->setPseudoPk(pk);
|
||||
|
||||
// Update checked status of the popup menu action
|
||||
ui->actionUnlockViewEditing->setChecked(unlock);
|
||||
|
||||
// Save settings for this table
|
||||
browseTableSettings[currentTable].unlockViewPk = pk;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ struct BrowseDataTableSettings
|
||||
QString encoding;
|
||||
QString plotXAxis;
|
||||
QMap<QString, PlotDock::PlotSettings> plotYAxes;
|
||||
QString unlockViewPk;
|
||||
|
||||
friend QDataStream& operator<<(QDataStream& stream, const BrowseDataTableSettings& object)
|
||||
{
|
||||
@@ -45,6 +46,7 @@ struct BrowseDataTableSettings
|
||||
stream << object.encoding;
|
||||
stream << object.plotXAxis;
|
||||
stream << object.plotYAxes;
|
||||
stream << object.unlockViewPk;
|
||||
|
||||
return stream;
|
||||
}
|
||||
@@ -68,6 +70,7 @@ struct BrowseDataTableSettings
|
||||
|
||||
stream >> object.plotXAxis;
|
||||
stream >> object.plotYAxes;
|
||||
stream >> object.unlockViewPk;
|
||||
|
||||
return stream;
|
||||
}
|
||||
@@ -154,6 +157,7 @@ private:
|
||||
void setCurrentFile(const QString& fileName);
|
||||
void addToRecentFilesMenu(const QString& filename);
|
||||
void activateFields(bool enable = true);
|
||||
void enableEditing(bool enable_edit, bool enable_insertdelete);
|
||||
void loadExtensionsFromSettings();
|
||||
|
||||
protected:
|
||||
@@ -242,6 +246,7 @@ private slots:
|
||||
void browseDataSetTableEncoding(bool forAllTables = false);
|
||||
void browseDataSetDefaultTableEncoding();
|
||||
void fileOpenReadOnly();
|
||||
void unlockViewEditing(bool unlock, QString pk = QString());
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1779,6 +1779,17 @@
|
||||
<enum>QAction::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionUnlockViewEditing">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Unlock view editing</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>This unlocks the current view for editing. However, you will need appropriate triggers for editing.</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
@@ -2814,6 +2825,22 @@
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionUnlockViewEditing</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>unlockViewEditing(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>518</x>
|
||||
<y>314</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>fileOpen()</slot>
|
||||
@@ -2877,5 +2904,6 @@
|
||||
<slot>browseDataFetchAllData()</slot>
|
||||
<slot>exportTableToJson()</slot>
|
||||
<slot>fileOpenReadOnly()</slot>
|
||||
<slot>unlockViewEditing(bool)</slot>
|
||||
</slots>
|
||||
</ui>
|
||||
|
||||
@@ -945,14 +945,14 @@ bool DBBrowserDB::deleteRecords(const QString& table, const QStringList& rowids)
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool DBBrowserDB::updateRecord(const QString& table, const QString& column, const QString& rowid, const QByteArray& value, bool itsBlob)
|
||||
bool DBBrowserDB::updateRecord(const QString& table, const QString& column, const QString& rowid, const QByteArray& value, bool itsBlob, const QString& pseudo_pk)
|
||||
{
|
||||
if (!isOpen()) return false;
|
||||
|
||||
QString sql = QString("UPDATE %1 SET %2=? WHERE %3='%4';")
|
||||
.arg(sqlb::escapeIdentifier(table))
|
||||
.arg(sqlb::escapeIdentifier(column))
|
||||
.arg(sqlb::escapeIdentifier(getObjectByName(table).dynamicCast<sqlb::Table>()->rowidColumn()))
|
||||
.arg(sqlb::escapeIdentifier(pseudo_pk.isEmpty() ? getObjectByName(table).dynamicCast<sqlb::Table>()->rowidColumn() : pseudo_pk))
|
||||
.arg(rowid);
|
||||
|
||||
logSQL(sql, kLogMsg_App);
|
||||
|
||||
@@ -67,7 +67,7 @@ public:
|
||||
*/
|
||||
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);
|
||||
bool updateRecord(const QString& 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);
|
||||
|
||||
@@ -31,6 +31,7 @@ void SqliteTableModel::reset()
|
||||
m_mWhere.clear();
|
||||
m_vDataTypes.clear();
|
||||
m_vDisplayFormat.clear();
|
||||
m_pseudoPk.clear();
|
||||
}
|
||||
|
||||
void SqliteTableModel::setChunkSize(size_t chunksize)
|
||||
@@ -341,7 +342,7 @@ bool SqliteTableModel::setTypedData(const QModelIndex& index, bool isBlob, const
|
||||
if(oldValue == newValue && oldValue.isNull() == newValue.isNull())
|
||||
return true;
|
||||
|
||||
if(m_db.updateRecord(m_sTable, m_headers.at(index.column()), m_data[index.row()].at(0), newValue, isBlob))
|
||||
if(m_db.updateRecord(m_sTable, m_headers.at(index.column()), m_data[index.row()].at(0), newValue, isBlob, m_pseudoPk))
|
||||
{
|
||||
// Only update the cache if this row has already been read, if not there's no need to do any changes to the cache
|
||||
if(index.row() < m_data.size())
|
||||
@@ -767,3 +768,19 @@ bool SqliteTableModel::dropMimeData(const QMimeData* data, Qt::DropAction, int r
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SqliteTableModel::setPseudoPk(const QString& pseudoPk)
|
||||
{
|
||||
if(pseudoPk.isEmpty())
|
||||
{
|
||||
m_pseudoPk.clear();
|
||||
if(m_headers.size())
|
||||
m_headers[0] = "rowid";
|
||||
} else {
|
||||
m_pseudoPk = pseudoPk;
|
||||
if(m_headers.size())
|
||||
m_headers[0] = pseudoPk;
|
||||
}
|
||||
|
||||
buildQuery();
|
||||
}
|
||||
|
||||
@@ -48,9 +48,13 @@ public:
|
||||
|
||||
bool isBinary(const QModelIndex& index) const;
|
||||
|
||||
void setEncoding(QString encoding) { m_encoding = encoding; }
|
||||
void setEncoding(const QString& encoding) { m_encoding = encoding; }
|
||||
QString encoding() const { return m_encoding; }
|
||||
|
||||
// The pseudo-primary key is exclusively for editing views
|
||||
void setPseudoPk(const QString& pseudoPk);
|
||||
QString pseudoPk() const { return m_pseudoPk; }
|
||||
|
||||
typedef QList<QByteArray> QByteArrayList;
|
||||
|
||||
sqlb::ForeignKeyClause getForeignKeyClause(int column) const;
|
||||
@@ -82,6 +86,7 @@ private:
|
||||
|
||||
QString m_sQuery;
|
||||
QString m_sTable;
|
||||
QString m_pseudoPk;
|
||||
int m_iSortColumn;
|
||||
QString m_sSortOrder;
|
||||
QMap<int, QString> m_mWhere;
|
||||
|
||||
Reference in New Issue
Block a user