Skip to content

Commit c8cd858

Browse files
committed
Add "Save Database As..." action
The feature is implemented using the SQLite backup API. https://sqlite.org/backup.html This allows saving in-memory databases and saving database files to another file name.
1 parent 42ce899 commit c8cd858

File tree

5 files changed

+118
-0
lines changed

5 files changed

+118
-0
lines changed

src/MainWindow.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,30 @@ void MainWindow::refreshTableBrowsers(bool force_refresh)
687687
QApplication::restoreOverrideCursor();
688688
}
689689

690+
bool MainWindow::fileSaveAs() {
691+
692+
QString fileName = FileDialog::getSaveFileName(
693+
OpenDatabaseFile,
694+
this,
695+
tr("Choose a database file to save under"),
696+
FileDialog::getSqlDatabaseFileFilter()
697+
);
698+
// catch situation where user has canceled file selection from dialog
699+
if(!fileName.isEmpty()) {
700+
bool result = db.saveAs(fileName.toStdString());
701+
if(result) {
702+
setCurrentFile(fileName);
703+
addToRecentFilesMenu(fileName);
704+
} else {
705+
QMessageBox::warning(this, QApplication::applicationName(),
706+
tr("Error while saving the database to the new file."));
707+
}
708+
return result;
709+
} else {
710+
return false;
711+
}
712+
}
713+
690714
bool MainWindow::fileClose()
691715
{
692716
// Stop any running SQL statements before closing the database
@@ -1415,6 +1439,9 @@ void MainWindow::dbState(bool dirty)
14151439
{
14161440
ui->fileSaveAction->setEnabled(dirty);
14171441
ui->fileRevertAction->setEnabled(dirty);
1442+
// Unfortunately, sqlite does not allow to backup the DB while there are pending savepoints,
1443+
// so we cannot "Save As" when the DB is dirty.
1444+
ui->fileSaveAsAction->setEnabled(!dirty);
14181445
}
14191446

14201447
void MainWindow::fileSave()
@@ -1824,6 +1851,7 @@ void MainWindow::activateFields(bool enable)
18241851
bool tempDb = db.currentFile() == ":memory:";
18251852

18261853
ui->fileCloseAction->setEnabled(enable);
1854+
ui->fileSaveAsAction->setEnabled(enable);
18271855
ui->fileAttachAction->setEnabled(enable);
18281856
ui->fileCompactAction->setEnabled(enable && write);
18291857
ui->fileExportJsonAction->setEnabled(enable);

src/MainWindow.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ private slots:
167167
void fileNewInMemoryDatabase();
168168
void refreshTableBrowsers(bool force_refresh = false);
169169
bool fileClose();
170+
bool fileSaveAs();
170171
void createTable();
171172
void createIndex();
172173
void compact();

src/MainWindow.ui

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,7 @@ You can drag SQL statements from an object row and drop them into other applicat
785785
<addaction name="fileOpenReadOnlyAction"/>
786786
<addaction name="fileAttachAction"/>
787787
<addaction name="fileRecentFiles"/>
788+
<addaction name="fileSaveAsAction"/>
788789
<addaction name="fileCloseAction"/>
789790
<addaction name="separator"/>
790791
<addaction name="fileSaveAction"/>
@@ -2240,6 +2241,21 @@ You can drag SQL statements from the Schema column and drop them into the SQL ed
22402241
<string>This shows the number of rows for each table and view in the database.</string>
22412242
</property>
22422243
</action>
2244+
<action name="fileSaveAsAction">
2245+
<property name="enabled">
2246+
<bool>false</bool>
2247+
</property>
2248+
<property name="icon">
2249+
<iconset resource="icons/icons.qrc">
2250+
<normaloff>:/icons/db_save</normaloff>:/icons/db_save</iconset>
2251+
</property>
2252+
<property name="text">
2253+
<string>Save Database &amp;As...</string>
2254+
</property>
2255+
<property name="toolTip">
2256+
<string>Save the current database as a different file</string>
2257+
</property>
2258+
</action>
22432259
</widget>
22442260
<customwidgets>
22452261
<customwidget>
@@ -2350,6 +2366,22 @@ You can drag SQL statements from the Schema column and drop them into the SQL ed
23502366
</hint>
23512367
</hints>
23522368
</connection>
2369+
<connection>
2370+
<sender>fileSaveAsAction</sender>
2371+
<signal>triggered()</signal>
2372+
<receiver>MainWindow</receiver>
2373+
<slot>fileSaveAs()</slot>
2374+
<hints>
2375+
<hint type="sourcelabel">
2376+
<x>-1</x>
2377+
<y>-1</y>
2378+
</hint>
2379+
<hint type="destinationlabel">
2380+
<x>399</x>
2381+
<y>299</y>
2382+
</hint>
2383+
</hints>
2384+
</connection>
23532385
<connection>
23542386
<sender>fileCompactAction</sender>
23552387
<signal>triggered()</signal>

src/sqlitedb.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,62 @@ bool DBBrowserDB::close()
755755
return true;
756756
}
757757

758+
bool DBBrowserDB::saveAs(const std::string& filename) {
759+
int rc;
760+
sqlite3_backup *pBackup;
761+
sqlite3 *pTo;
762+
763+
if(!_db)
764+
return false;
765+
766+
waitForDbRelease();
767+
768+
// Open the database file identified by filename. Exit early if this fails
769+
// for any reason.
770+
rc = sqlite3_open(filename.c_str(), &pTo);
771+
if(rc!=SQLITE_OK) {
772+
qWarning() << tr("Cannot open destination file: '%1'").arg(filename.c_str());
773+
return false;
774+
} else {
775+
// Set up the backup procedure to copy from the "main" database of
776+
// connection _db to the main database of connection pTo.
777+
// If something goes wrong, pBackup will be set to nullptr and an error
778+
// code and message left in connection pTo.
779+
//
780+
// If the backup object is successfully created, call backup_step()
781+
// to copy data from _db to pTo. Then call backup_finish()
782+
// to release resources associated with the pBackup object. If an
783+
// error occurred, then an error code and message will be left in
784+
// connection pTo. If no error occurred, then the error code belonging
785+
// to pTo is set to SQLITE_OK.
786+
//
787+
pBackup = sqlite3_backup_init(pTo, "main", _db, "main");
788+
if(pBackup == nullptr) {
789+
qWarning() << tr("Cannot backup to file: '%1'. Message: %2").arg(filename.c_str()).arg(sqlite3_errmsg(pTo));
790+
sqlite3_close(pTo);
791+
return false;
792+
} else {
793+
sqlite3_backup_step(pBackup, -1);
794+
sqlite3_backup_finish(pBackup);
795+
}
796+
rc = sqlite3_errcode(pTo);
797+
}
798+
799+
if(rc == SQLITE_OK) {
800+
// Close current database and set backup as current
801+
sqlite3_close(_db);
802+
_db = pTo;
803+
curDBFilename = QString::fromStdString(filename);
804+
805+
return true;
806+
} else {
807+
qWarning() << tr("Cannot backup to file: '%1'. Message: %2").arg(filename.c_str()).arg(sqlite3_errmsg(pTo));
808+
// Close failed database connection.
809+
sqlite3_close(pTo);
810+
return false;
811+
}
812+
}
813+
758814
DBBrowserDB::db_pointer_type DBBrowserDB::get(const QString& user, bool force_wait)
759815
{
760816
if(!_db)

src/sqlitedb.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class DBBrowserDB : public QObject
8080
bool detach(const std::string& attached_as);
8181
bool create ( const QString & db);
8282
bool close();
83+
bool saveAs(const std::string& filename);
8384

8485
// This returns the SQLite version as well as the SQLCipher if DB4S is compiled with encryption support
8586
static void getSqliteVersion(QString& sqlite, QString& sqlcipher);

0 commit comments

Comments
 (0)