Skip to content

Commit 51dbe72

Browse files
committed
Multi-threading patch
This was done by Michael Krause. https://lists.sqlitebrowser.org/pipermail/db4s-dev/2018-February/000305.html In this commit I only fixed two compiler warnings, some whitespace issues and removed some debug messages.
1 parent 9c2cec6 commit 51dbe72

19 files changed

+1463
-341
lines changed

CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ endif()
7878
add_subdirectory(${QHEXEDIT_DIR})
7979
add_subdirectory(${QCUSTOMPLOT_DIR})
8080

81+
add_subdirectory(/home/michael/dev/gtest/git/googletest /home/michael/dev/gtest/build)
82+
8183
find_package(Qt5Widgets REQUIRED)
8284
find_package(Qt5LinguistTools REQUIRED)
8385
find_package(Qt5Network REQUIRED)
@@ -117,6 +119,8 @@ set(SQLB_MOC_HDR
117119
src/SqlExecutionArea.h
118120
src/VacuumDialog.h
119121
src/sqlitetablemodel.h
122+
src/RowLoader.h
123+
src/RowCache.h
120124
src/sqltextedit.h
121125
src/docktextedit.h
122126
src/DbStructureModel.h
@@ -154,6 +158,7 @@ set(SQLB_SRC
154158
src/VacuumDialog.cpp
155159
src/sqlitedb.cpp
156160
src/sqlitetablemodel.cpp
161+
src/RowLoader.cpp
157162
src/sqlitetypes.cpp
158163
src/sqltextedit.cpp
159164
src/docktextedit.cpp
@@ -371,6 +376,7 @@ endif()
371376
target_link_libraries(${PROJECT_NAME}
372377
qhexedit
373378
qcustomplot
379+
pthread
374380
${QT_LIBRARIES}
375381
${WIN32_STATIC_LINK}
376382
${LIBSQLITE}

src/DbStructureModel.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ QMimeData* DbStructureModel::mimeData(const QModelIndexList& indices) const
219219
sqlb::ObjectIdentifier objid(data(index.sibling(index.row(), ColumnSchema), Qt::DisplayRole).toString(),
220220
data(index.sibling(index.row(), ColumnName), Qt::DisplayRole).toString());
221221
tableModel.setTable(objid);
222-
tableModel.waitForFetchingFinished();
222+
tableModel.completeCache();
223223
for(int i=0; i < tableModel.rowCount(); ++i)
224224
{
225225
QString insertStatement = "INSERT INTO " + objid.toString() + " VALUES(";

src/EditTableDialog.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column)
388388
.arg(sqlb::escapeIdentifier(pdb.getObjectByName(curTable).dynamicCast<sqlb::Table>()->rowidColumn()))
389389
.arg(curTable.toString())
390390
.arg(sqlb::escapeIdentifier(field->name())));
391-
m.waitForFetchingFinished();
391+
m.completeCache();
392392
if(m.data(m.index(0, 0)).toInt() > 0)
393393
{
394394
// There is a NULL value, so print an error message, uncheck the combobox, and return here
@@ -416,7 +416,7 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column)
416416
.arg(curTable.toString())
417417
.arg(sqlb::escapeIdentifier(field->name()))
418418
.arg(sqlb::escapeIdentifier(field->name())));
419-
m.waitForFetchingFinished();
419+
m.completeCache();
420420
if(m.data(m.index(0, 0)).toInt() > 0)
421421
{
422422
// There is a non-integer value, so print an error message, uncheck the combobox, and return here
@@ -458,10 +458,10 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column)
458458
// Because our renameColumn() function fails when setting a column to unique when it already contains the same values
459459
SqliteTableModel m(pdb, this);
460460
m.setQuery(QString("SELECT COUNT(%2) FROM %1;").arg(curTable.toString()).arg(sqlb::escapeIdentifier(field->name())));
461-
m.waitForFetchingFinished();
461+
m.completeCache();
462462
int rowcount = m.data(m.index(0, 0)).toInt();
463463
m.setQuery(QString("SELECT COUNT(DISTINCT %2) FROM %1;").arg(curTable.toString()).arg(sqlb::escapeIdentifier(field->name())));
464-
m.waitForFetchingFinished();
464+
m.completeCache();
465465
int uniquecount = m.data(m.index(0, 0)).toInt();
466466
if(rowcount != uniquecount)
467467
{

src/ExportDataDialog.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ bool ExportDataDialog::exportQueryCsv(const QString& sQuery, const QString& sFil
117117
QByteArray utf8Query = sQuery.toUtf8();
118118
sqlite3_stmt *stmt;
119119

120-
int status = sqlite3_prepare_v2(pdb._db, utf8Query.data(), utf8Query.size(), &stmt, nullptr);
120+
auto pDb = pdb.get("exporting CSV");
121+
int status = sqlite3_prepare_v2(pDb.get(), utf8Query.data(), utf8Query.size(), &stmt, nullptr);
121122
if(SQLITE_OK == status)
122123
{
123124
if(ui->checkHeader->isChecked())
@@ -199,7 +200,9 @@ bool ExportDataDialog::exportQueryJson(const QString& sQuery, const QString& sFi
199200
{
200201
QByteArray utf8Query = sQuery.toUtf8();
201202
sqlite3_stmt *stmt;
202-
int status = sqlite3_prepare_v2(pdb._db, utf8Query.data(), utf8Query.size(), &stmt, nullptr);
203+
204+
auto pDb = pdb.get("exporting JSON");
205+
int status = sqlite3_prepare_v2(pDb.get(), utf8Query.data(), utf8Query.size(), &stmt, nullptr);
203206

204207
QJsonArray json_table;
205208

src/ExtendedTableWidget.cpp

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#include <iostream>
2+
13
#include "ExtendedTableWidget.h"
24
#include "sqlitetablemodel.h"
35
#include "FilterTableHeader.h"
@@ -604,19 +606,29 @@ void ExtendedTableWidget::updateGeometries()
604606
// If so and if it is a SqliteTableModel and if the parent implementation of this method decided that a scrollbar is needed, update its maximum value
605607
SqliteTableModel* m = qobject_cast<SqliteTableModel*>(model());
606608
if(m && verticalScrollBar()->maximum())
607-
verticalScrollBar()->setMaximum(m->totalRowCount() - numVisibleRows() + 1);
609+
verticalScrollBar()->setMaximum(m->rowCount() - numVisibleRows() + 1);
608610
}
609611
}
610612

611613
void ExtendedTableWidget::vscrollbarChanged(int value)
612614
{
615+
//std::cout << "ExtendedTableWidget::vscrollbarChanged " << value << std::endl;
616+
613617
// Cancel if there is no model set yet - this shouldn't happen (because without a model there should be no scrollbar) but just to be sure...
614618
if(!model())
615619
return;
616620

617621
// Fetch more data from the DB if necessary
618-
if((value + numVisibleRows()) >= model()->rowCount() && model()->canFetchMore(QModelIndex()))
619-
model()->fetchMore(QModelIndex());
622+
const auto nrows = model()->rowCount();
623+
if(nrows == 0)
624+
return;
625+
626+
if(auto * m = dynamic_cast<SqliteTableModel*>(model()))
627+
{
628+
int row_begin = std::min(value, nrows - 1);
629+
int row_end = std::min(value + numVisibleRows(), nrows);
630+
m->triggerCacheLoad(row_begin, row_end);
631+
}
620632
}
621633

622634
int ExtendedTableWidget::numVisibleRows()
@@ -682,13 +694,11 @@ void ExtendedTableWidget::selectTableLine(int lineToSelect)
682694
SqliteTableModel* m = qobject_cast<SqliteTableModel*>(model());
683695

684696
// Are there even that many lines?
685-
if(lineToSelect >= m->totalRowCount())
697+
if(lineToSelect >= m->rowCount())
686698
return;
687699

688700
QApplication::setOverrideCursor( Qt::WaitCursor );
689-
// Make sure this line has already been fetched
690-
while(lineToSelect >= m->rowCount() && m->canFetchMore())
691-
m->fetchMore();
701+
m->triggerCacheLoad(lineToSelect);
692702

693703
// Select it
694704
clearSelection();
@@ -703,7 +713,7 @@ void ExtendedTableWidget::selectTableLines(int firstLine, int count)
703713

704714
int lastLine = firstLine+count-1;
705715
// Are there even that many lines?
706-
if(lastLine >= m->totalRowCount())
716+
if(lastLine >= m->rowCount())
707717
return;
708718

709719
selectTableLine(firstLine);

src/ImportCsvDialog.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,8 @@ bool ImportCsvDialog::importCsv(const QString& fileName, const QString& name)
607607
sQuery.chop(1); // Remove last comma
608608
sQuery.append(")");
609609
sqlite3_stmt* stmt;
610-
sqlite3_prepare_v2(pdb->_db, sQuery.toUtf8(), sQuery.toUtf8().length(), &stmt, nullptr);
610+
auto pDb = pdb->get("importing CSV");
611+
sqlite3_prepare_v2(pDb.get(), sQuery.toUtf8(), sQuery.toUtf8().length(), &stmt, nullptr);
611612

612613
// Parse entire file
613614
size_t lastRowNum = 0;

src/MainWindow.cpp

Lines changed: 69 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#include <iostream>
2+
13
#include "MainWindow.h"
24
#include "ui_MainWindow.h"
35

@@ -57,6 +59,7 @@
5759
MainWindow::MainWindow(QWidget* parent)
5860
: QMainWindow(parent),
5961
ui(new Ui::MainWindow),
62+
db(),
6063
m_browseTableModel(new SqliteTableModel(db, this, Settings::getValue("db", "prefetchsize").toInt())),
6164
m_currentTabTableModel(m_browseTableModel),
6265
m_remoteDb(new RemoteDatabase),
@@ -292,6 +295,11 @@ void MainWindow::init()
292295
connect(m_browseTableModel, &SqliteTableModel::finishedFetch, this, &MainWindow::setRecordsetLabel);
293296
connect(ui->dataTable, &ExtendedTableWidget::selectedRowsToBeDeleted, this, &MainWindow::deleteRecord);
294297

298+
connect(m_browseTableModel, &SqliteTableModel::finishedFetch, [this](){
299+
auto & settings = browseTableSettings[currentlyBrowsedTableName()];
300+
plotDock->updatePlot(m_browseTableModel, &settings, true, true);
301+
});
302+
295303
// Lambda function for keyboard shortcuts for selecting next/previous table in Browse Data tab
296304
connect(ui->dataTable, &ExtendedTableWidget::switchTable, [this](bool next) {
297305
int index = ui->comboBrowseTable->currentIndex();
@@ -505,13 +513,18 @@ void MainWindow::populateTable()
505513
{
506514
connect(ui->dataTable->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(dataTableSelectionChanged(QModelIndex)));
507515

508-
// Lambda function for updating the delete record button to reflect number of selected records
516+
// Lambda function for updating the delete record button to reflect number of selected records. -- TODO actual
517+
// call this once to disable Delete button...
509518
connect(ui->dataTable->selectionModel(), &QItemSelectionModel::selectionChanged, [this](const QItemSelection&, const QItemSelection&) {
510-
// NOTE: We're assuming here that the selection is always contiguous, i.e. that there are never two selected rows with a non-selected
511-
// row in between.
519+
// NOTE: We're assuming here that the selection is always contiguous, i.e. that there are never two selected
520+
// rows with a non-selected row in between.
512521
int rows = 0;
513-
if(ui->dataTable->selectionModel()->selectedIndexes().count())
514-
rows = ui->dataTable->selectionModel()->selectedIndexes().last().row() - ui->dataTable->selectionModel()->selectedIndexes().first().row() + 1;
522+
523+
const auto & sel = ui->dataTable->selectionModel()->selectedIndexes();
524+
if(sel.count())
525+
rows = sel.last().row() - sel.first().row() + 1;
526+
527+
ui->buttonDeleteRecord->setEnabled(rows != 0);
515528

516529
if(rows > 1)
517530
ui->buttonDeleteRecord->setText(tr("Delete records"));
@@ -552,6 +565,8 @@ void MainWindow::populateTable()
552565
// Encoding
553566
m_browseTableModel->setEncoding(defaultBrowseTableEncoding);
554567

568+
setRecordsetLabel();
569+
555570
// Plot
556571
attachPlot(ui->dataTable, m_browseTableModel, &browseTableSettings[tablename]);
557572

@@ -609,6 +624,8 @@ void MainWindow::populateTable()
609624
// Encoding
610625
m_browseTableModel->setEncoding(storedData.encoding);
611626

627+
setRecordsetLabel();
628+
612629
// Plot
613630
attachPlot(ui->dataTable, m_browseTableModel, &browseTableSettings[tablename], false);
614631
}
@@ -710,8 +727,8 @@ void MainWindow::deleteRecord()
710727
}
711728
}
712729

713-
if(old_row > m_browseTableModel->totalRowCount())
714-
old_row = m_browseTableModel->totalRowCount();
730+
if(old_row > m_browseTableModel->rowCount())
731+
old_row = m_browseTableModel->rowCount();
715732
selectTableLine(old_row);
716733
} else {
717734
QMessageBox::information( this, QApplication::applicationName(), tr("Please select a record first"));
@@ -750,8 +767,8 @@ void MainWindow::navigateNext()
750767
{
751768
int curRow = ui->dataTable->currentIndex().row();
752769
curRow += ui->dataTable->numVisibleRows() - 1;
753-
if(curRow >= m_browseTableModel->totalRowCount())
754-
curRow = m_browseTableModel->totalRowCount() - 1;
770+
if(curRow >= m_browseTableModel->rowCount())
771+
curRow = m_browseTableModel->rowCount() - 1;
755772
selectTableLine(curRow);
756773
}
757774

@@ -762,7 +779,7 @@ void MainWindow::navigateBegin()
762779

763780
void MainWindow::navigateEnd()
764781
{
765-
selectTableLine(m_browseTableModel->totalRowCount()-1);
782+
selectTableLine(m_browseTableModel->rowCount()-1);
766783
}
767784

768785

@@ -771,8 +788,8 @@ void MainWindow::navigateGoto()
771788
int row = ui->editGoto->text().toInt();
772789
if(row <= 0)
773790
row = 1;
774-
if(row > m_browseTableModel->totalRowCount())
775-
row = m_browseTableModel->totalRowCount();
791+
if(row > m_browseTableModel->rowCount())
792+
row = m_browseTableModel->rowCount();
776793

777794
selectTableLine(row - 1);
778795
ui->editGoto->setText(QString::number(row));
@@ -782,7 +799,7 @@ void MainWindow::setRecordsetLabel()
782799
{
783800
// Get all the numbers, i.e. the number of the first row and the last row as well as the total number of rows
784801
int from = ui->dataTable->verticalHeader()->visualIndexAt(0) + 1;
785-
int total = m_browseTableModel->totalRowCount();
802+
int total = m_browseTableModel->rowCount();
786803
int to = ui->dataTable->verticalHeader()->visualIndexAt(ui->dataTable->height()) - 1;
787804
if (to == -2)
788805
to = total;
@@ -791,7 +808,23 @@ void MainWindow::setRecordsetLabel()
791808
gotoValidator->setRange(0, total);
792809

793810
// Update the label showing the current position
794-
ui->labelRecordset->setText(tr("%1 - %2 of %3").arg(from).arg(to).arg(total));
811+
QString txt;
812+
switch(m_browseTableModel->rowCountAvailable())
813+
{
814+
case SqliteTableModel::RowCount::Unknown:
815+
txt = tr("determining row count...");
816+
break;
817+
case SqliteTableModel::RowCount::Partial:
818+
txt = tr("%1 - %2 of >= %3").arg(from).arg(to).arg(total);
819+
break;
820+
case SqliteTableModel::RowCount::Complete:
821+
default:
822+
txt = tr("%1 - %2 of %3").arg(from).arg(to).arg(total);
823+
break;
824+
}
825+
ui->labelRecordset->setText(txt);
826+
827+
ui->dataTable->setDisabled( m_browseTableModel->rowCountAvailable() == SqliteTableModel::RowCount::Unknown );
795828
}
796829

797830
void MainWindow::refresh()
@@ -1119,7 +1152,8 @@ void MainWindow::executeQuery()
11191152
// Execute next statement
11201153
int tail_length_before = tail_length;
11211154
const char* qbegin = tail;
1122-
sql3status = sqlite3_prepare_v2(db._db,tail, tail_length, &vm, &tail);
1155+
auto pDb = db.get("executing query");
1156+
sql3status = sqlite3_prepare_v2(pDb.get(), tail, tail_length, &vm, &tail);
11231157
QString queryPart = QString::fromUtf8(qbegin, tail - qbegin);
11241158
tail_length -= (tail - qbegin);
11251159
int execution_end_index = execution_start_index + tail_length_before - tail_length;
@@ -1144,12 +1178,20 @@ void MainWindow::executeQuery()
11441178
{
11451179
// If we get here, the SQL statement returns some sort of data. So hand it over to the model for display. Don't set the modified flag
11461180
// because statements that display data don't change data as well.
1181+
pDb = nullptr;
11471182

1148-
sqlWidget->getModel()->setQuery(queryPart);
1183+
auto * model = sqlWidget->getModel();
1184+
model->setQuery(queryPart);
1185+
1186+
// Wait until the initial loading of data (= first chunk and row count) has been performed. I have the
1187+
// feeling that a lot of stuff would need rewriting if we wanted to become more asynchronous here:
1188+
// essentially the entire loop over the commands would need to be signal-driven.
1189+
model->waitUntilIdle();
1190+
qApp->processEvents(); // to make row count available
11491191

11501192
// The query takes the last placeholder as it may itself contain the sequence '%' + number
11511193
statusMessage = tr("%1 rows returned in %2ms from: %3").arg(
1152-
sqlWidget->getModel()->totalRowCount()).arg(timer.elapsed()).arg(queryPart.trimmed());
1194+
model->rowCount()).arg(timer.elapsed()).arg(queryPart.trimmed());
11531195
ok = true;
11541196
ui->actionSqlResultsSave->setEnabled(true);
11551197
ui->actionSqlResultsSaveAsView->setEnabled(!db.readOnly());
@@ -1165,7 +1207,7 @@ void MainWindow::executeQuery()
11651207

11661208
QString stmtHasChangedDatabase;
11671209
if(query_part_type == InsertStatement || query_part_type == UpdateStatement || query_part_type == DeleteStatement)
1168-
stmtHasChangedDatabase = tr(", %1 rows affected").arg(sqlite3_changes(db._db));
1210+
stmtHasChangedDatabase = tr(", %1 rows affected").arg(sqlite3_changes(pDb.get()));
11691211

11701212
// Attach/Detach statements don't modify the original database
11711213
if(query_part_type != StatementType::AttachStatement && query_part_type != StatementType::DetachStatement)
@@ -1178,18 +1220,20 @@ void MainWindow::executeQuery()
11781220
case SQLITE_MISUSE:
11791221
continue;
11801222
default:
1181-
statusMessage = QString::fromUtf8(sqlite3_errmsg(db._db)) + ": " + queryPart;
1223+
statusMessage = QString::fromUtf8(sqlite3_errmsg(pDb.get())) + ": " + queryPart;
11821224
ok = true;
11831225
break;
11841226
}
11851227
timer.restart();
11861228
} else {
1187-
statusMessage = QString::fromUtf8(sqlite3_errmsg(db._db)) + ": " + queryPart;
1229+
statusMessage = QString::fromUtf8(sqlite3_errmsg(pDb.get())) + ": " + queryPart;
11881230
sqlWidget->getEditor()->setErrorIndicator(execution_start_line, execution_start_index, execution_start_line, execution_end_index);
11891231
ok = false;
11901232

11911233
}
11921234

1235+
pDb = nullptr; // release db
1236+
11931237
execution_start_index = execution_end_index;
11941238

11951239
// Revert to save point now if it wasn't needed. We need to do this here because there are some rare cases where the next statement might
@@ -1206,6 +1250,7 @@ void MainWindow::executeQuery()
12061250
// Process events to keep the UI responsive
12071251
qApp->processEvents();
12081252
}
1253+
12091254
sqlWidget->finishExecution(statusMessage, ok);
12101255
attachPlot(sqlWidget->getTableResult(), sqlWidget->getModel());
12111256

@@ -2907,8 +2952,10 @@ void MainWindow::requestCollation(const QString& name, int eTextRep)
29072952
"that this application can't provide without further knowledge.\n"
29082953
"If you choose to proceed, be aware bad things can happen to your database.\n"
29092954
"Create a backup!").arg(name), QMessageBox::Yes | QMessageBox::No);
2910-
if(reply == QMessageBox::Yes)
2911-
sqlite3_create_collation(db._db, name.toUtf8(), eTextRep, nullptr, collCompare);
2955+
if(reply == QMessageBox::Yes) {
2956+
auto pDb = db.get("creating collation");
2957+
sqlite3_create_collation(pDb.get(), name.toUtf8(), eTextRep, nullptr, collCompare);
2958+
}
29122959
}
29132960

29142961
void MainWindow::renameSqlTab(int index)

0 commit comments

Comments
 (0)