Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions distri/sqlitebrowser.desktop.appdata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@
</screenshots>
<url type="homepage">https://sqlitebrowser.org/</url>
<url type="bugtracker">https://github.com/sqlitebrowser/sqlitebrowser/issues</url>
<url type="donation">https://www.patreon.com/db4s</url>
<url type="faq">https://github.com/sqlitebrowser/sqlitebrowser/wiki/Frequently-Asked-Questions</url>
<url type="translate">https://github.com/sqlitebrowser/sqlitebrowser/wiki/Translations</url>
<url type="contribute">https://github.com/sqlitebrowser/sqlitebrowser/wiki#for-developers</url>
<url type="vcs-browser">https://github.com/sqlitebrowser/sqlitebrowser</url>
<releases>
<release version="3.13.1" date="2024-10-16" />
<release version="3.13.0" date="2024-07-23" />
Expand Down
2 changes: 2 additions & 0 deletions src/AddRecordDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ void AddRecordDialog::populateFields()

// Initialize fields, fks and pk differently depending on whether it's a table or a view.
const sqlb::TablePtr table = pdb.getTableByName(curTable);
if(!table)
return;
fields = table->fields;
if (!table->isView())
{
Expand Down
10 changes: 8 additions & 2 deletions src/EditTableDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ EditTableDialog::EditTableDialog(DBBrowserDB& db, const sqlb::ObjectIdentifier&
if(m_bNewTable == false)
{
// Existing table, so load and set the current layout
m_table = *pdb.getTableByName(curTable);
auto tbl = pdb.getTableByName(curTable);
if(!tbl)
return;
m_table = *tbl;
ui->labelEditWarning->setVisible(!m_table.fullyParsed());

// Initialise the list of tracked columns for table layout changes
Expand Down Expand Up @@ -661,8 +664,11 @@ void EditTableDialog::fieldItemChanged(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);
auto tbl = pdb.getTableByName(curTable);
if(!tbl)
return;
m.setQuery(QString("SELECT COUNT(%1) FROM %2 WHERE coalesce(NULL,%3) IS NULL;").arg(
QString::fromStdString(sqlb::joinStringVector(sqlb::escapeIdentifier(pdb.getTableByName(curTable)->rowidColumns()), ",")),
QString::fromStdString(sqlb::joinStringVector(sqlb::escapeIdentifier(tbl->rowidColumns()), ",")),
QString::fromStdString(curTable.toString()),
QString::fromStdString(sqlb::escapeIdentifier(field.name()))));
if(!m.completeCache())
Expand Down
8 changes: 8 additions & 0 deletions src/ExtendedTableWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,15 @@ QWidget* ExtendedTableWidgetEditorDelegate::createEditor(QWidget* parent, const
// If no column name is set, assume the primary key is meant
if(fk->columns().empty()) {
sqlb::TablePtr obj = m->db().getTableByName(foreignTable);
if(!obj)
return nullptr;
column = obj->primaryKeyColumns().front().name();
} else
column = fk->columns().at(0);

sqlb::TablePtr currentTable = m->db().getTableByName(m->currentTableName());
if(!currentTable)
return nullptr;
QString query = QString("SELECT %1 FROM %2").arg(QString::fromStdString(sqlb::escapeIdentifier(column)), QString::fromStdString(foreignTable.toString()));

// if the current column of the current table does NOT have not-null constraint,
Expand Down Expand Up @@ -257,6 +261,8 @@ ExtendedTableWidget::ExtendedTableWidget(QWidget* parent) :
m_frozen_column_count(0),
m_item_border_delegate(new ItemBorderDelegate(this))
{
setWordWrap(Settings::getValue("databrowser", "cell_word_wrap").toBool());

setHorizontalScrollMode(ExtendedTableWidget::ScrollPerPixel);
// Force ScrollPerItem, so scrolling shows all table rows
setVerticalScrollMode(ExtendedTableWidget::ScrollPerItem);
Expand Down Expand Up @@ -511,6 +517,8 @@ void ExtendedTableWidget::reloadSettings()
verticalHeader()->setDefaultSectionSize(fontMetrics.height()+10);
if(m_frozen_table_view)
m_frozen_table_view->reloadSettings();

setWordWrap(Settings::getValue("databrowser", "cell_word_wrap").toBool());
}

bool ExtendedTableWidget::copyMimeData(const QModelIndexList& fromIndices, QMimeData* mimeData, const bool withHeaders, const bool inSQL)
Expand Down
14 changes: 14 additions & 0 deletions src/ImportCsvDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <QTextStream>
#include <QFileInfo>
#include <memory>
#include <regex>

// Enable this line to show basic performance stats after each imported CSV file. Please keep in mind that while these
// numbers might help to estimate the performance of the algorithm, this is not a proper benchmark.
Expand Down Expand Up @@ -71,11 +72,13 @@ ImportCsvDialog::ImportCsvDialog(const std::vector<QString>& filenames, DBBrowse
ui->comboSeparator->blockSignals(true);
ui->comboQuote->blockSignals(true);
ui->comboEncoding->blockSignals(true);
ui->checkReplaceNonAlnum->blockSignals(true);

ui->checkboxHeader->setChecked(Settings::getValue("importcsv", "firstrowheader").toBool());
ui->checkBoxTrimFields->setChecked(Settings::getValue("importcsv", "trimfields").toBool());
ui->checkBoxSeparateTables->setChecked(Settings::getValue("importcsv", "separatetables").toBool());
ui->checkLocalConventions->setChecked(Settings::getValue("importcsv", "localconventions").toBool());
ui->checkReplaceNonAlnum->setChecked(Settings::getValue("importcsv", "replacenonalnum").toBool());
setSeparatorChar(getSettingsChar("importcsv", "separator"));
setQuoteChar(getSettingsChar("importcsv", "quotecharacter"));
setEncoding(Settings::getValue("importcsv", "encoding").toString());
Expand All @@ -87,6 +90,7 @@ ImportCsvDialog::ImportCsvDialog(const std::vector<QString>& filenames, DBBrowse
ui->comboSeparator->blockSignals(false);
ui->comboQuote->blockSignals(false);
ui->comboEncoding->blockSignals(false);
ui->checkReplaceNonAlnum->blockSignals(false);

// Prepare and show interface depending on how many files are selected
if (csvFilenames.size() > 1)
Expand Down Expand Up @@ -193,6 +197,7 @@ void ImportCsvDialog::accept()
Settings::setValue("importcsv", "separatetables", ui->checkBoxSeparateTables->isChecked());
Settings::setValue("importcsv", "localconventions", ui->checkLocalConventions->isChecked());
Settings::setValue("importcsv", "encoding", currentEncoding());
Settings::setValue("importcsv", "replacenonalnum", ui->checkReplaceNonAlnum->isChecked());

// Get all the selected files and start the import
if (ui->filePickerBlock->isVisible())
Expand Down Expand Up @@ -425,6 +430,13 @@ sqlb::FieldVector ImportCsvDialog::generateFieldList(const QString& filename) co
{
// Take field name from CSV
fieldname = std::string(rowData.fields[i].data, rowData.fields[i].data_length);

// Replace any non-nlphanumeric characters with an underscore
if(ui->checkReplaceNonAlnum->isChecked())
{
std::regex pattern("[^a-zA-Z0-9_]");
fieldname = std::regex_replace(fieldname, pattern, "_");
}
}

// If we don't have a field name by now, generate one
Expand Down Expand Up @@ -879,6 +891,8 @@ void ImportCsvDialog::toggleAdvancedSection(bool show)
ui->checkIgnoreDefaults->setVisible(show);
ui->labelOnConflictStrategy->setVisible(show);
ui->comboOnConflictStrategy->setVisible(show);
ui->labelReplaceNonAlnum->setVisible(show);
ui->checkReplaceNonAlnum->setVisible(show);
}

char32_t ImportCsvDialog::toUtf8(const QString& s) const
Expand Down
23 changes: 23 additions & 0 deletions src/ImportCsvDialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,23 @@
</property>
</widget>
</item>
<item row="13" column="1">
<widget class="QCheckBox" name="checkReplaceNonAlnum">
<property name="toolTip">
<string>Replace non-alphanumeric characters in column name with an underscore.</string>
</property>
</widget>
</item>
<item row="13" column="0">
<widget class="QLabel" name="labelReplaceNonAlnum">
<property name="text">
<string>Replace non-alphanumeric in column name</string>
</property>
<property name="buddy">
<cstring>checkReplaceNonAlnum</cstring>
</property>
</widget>
</item>
</layout>
</item>
<item>
Expand Down Expand Up @@ -793,6 +810,12 @@
</hint>
</hints>
</connection>
<connection>
<sender>checkReplaceNonAlnum</sender>
<signal>toggled(bool)</signal>
<receiver>ImportCsvDialog</receiver>
<slot>updatePreview()</slot>
</connection>
</connections>
<slots>
<slot>updatePreview()</slot>
Expand Down
67 changes: 59 additions & 8 deletions src/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,11 @@ bool MainWindow::fileOpen(const QString& fileName, bool openFromProject, bool re
loadPragmas();

refreshTableBrowsers();

if (db.readOnly()) {
if (!fileSystemWatch.files().isEmpty())
fileSystemWatch.removePaths(fileSystemWatch.files());
fileSystemWatch.addPath(wFile);
}
retval = true;
} else {
QMessageBox::warning(this, qApp->applicationName(), tr("Could not open database file.\nReason: %1").arg(db.lastError()));
Expand Down Expand Up @@ -700,6 +704,10 @@ void MainWindow::refreshTableBrowsers(bool all)
QApplication::restoreOverrideCursor();
}

void MainWindow::refreshDb() {
refreshTableBrowsers();
}

bool MainWindow::fileSaveAs() {

QString fileName = FileDialog::getSaveFileName(
Expand Down Expand Up @@ -743,6 +751,8 @@ bool MainWindow::fileClose()
if(!db.close())
return false;

if (!fileSystemWatch.files().isEmpty())
fileSystemWatch.removePaths(fileSystemWatch.files());
TableBrowser::resetSharedSettings();
setCurrentFile(QString());
loadPragmas();
Expand Down Expand Up @@ -1032,6 +1042,8 @@ void MainWindow::editObject()
refreshTableBrowsers();
} else if(type == "view") {
sqlb::TablePtr view = db.getTableByName(obj);
if(!view)
return;
runSqlNewTab(QString("DROP VIEW IF EXISTS %1;\n%2").arg(QString::fromStdString(obj.toString()), QString::fromStdString(view->sql())),
tr("Edit View %1").arg(QString::fromStdString(obj.toDisplayString())),
"https://www.sqlite.org/lang_createview.html",
Expand Down Expand Up @@ -1235,34 +1247,60 @@ void MainWindow::executeQuery()
// Prepare a lambda function for logging the results of a query
auto logged_queries = std::make_shared<QString>();
auto query_logger = [sqlWidget, editor, logged_queries](bool ok, const QString& status_message, int from_position, int to_position) {
int editor_length = editor->length();
int lines = editor->lines();
if(editor_length == 0 || lines == 0)
{
sqlWidget->finishExecution(status_message, ok);
return;
}

from_position = qBound(0, from_position, editor_length);
to_position = qBound(0, to_position, editor_length);

// Clamp a (line, index) pair from lineIndexFromPosition to valid editor bounds.
auto clampToEditor = [&](int& line, int& index) {
line = qMin(line, lines - 1);
index = qMin(index, editor->text(line).size());
};

int execute_from_line, execute_from_index;
editor->lineIndexFromPosition(from_position, &execute_from_line, &execute_from_index);
clampToEditor(execute_from_line, execute_from_index);

// Special case: if the start position is at the end of a line, then move to the beginning of next line.
// Otherwise for the typical case, the line reference is one less than expected.
// Note that execute_from_index uses character positions and not byte positions, so at() can be used.
QChar char_at_index = editor->text(execute_from_line).at(execute_from_index);
if (char_at_index == '\r' || char_at_index == '\n') {
execute_from_line++;
// The next lines could be empty, so skip all of them too.
while(editor->text(execute_from_line).trimmed().isEmpty())
QString line_text = editor->text(execute_from_line);
if(execute_from_index < line_text.size())
{
QChar char_at_index = line_text.at(execute_from_index);
if (char_at_index == '\r' || char_at_index == '\n') {
execute_from_line++;
execute_from_index = 0;
// The next lines could be empty, so skip all of them too.
while(execute_from_line < lines && editor->text(execute_from_line).trimmed().isEmpty())
execute_from_line++;
execute_from_index = 0;
}
}
clampToEditor(execute_from_line, execute_from_index);

// If there was an error highlight the erroneous SQL statement
if(!ok)
{
int end_of_current_statement_line, end_of_current_statement_index;
editor->lineIndexFromPosition(to_position, &end_of_current_statement_line, &end_of_current_statement_index);
clampToEditor(end_of_current_statement_line, end_of_current_statement_index);
editor->setErrorIndicator(execute_from_line, execute_from_index, end_of_current_statement_line, end_of_current_statement_index);

editor->setCursorPosition(execute_from_line, execute_from_index);
}

// Log the query and the result message.
// The query takes the last placeholder as it may itself contain the sequence '%' + number.
QString query = editor->text(from_position, to_position);
QString query;
if(from_position < to_position)
query = editor->text(from_position, to_position);
QString log_message = "-- " + tr("At line %1:").arg(execute_from_line+1) + "\n" + query.trimmed() + "\n-- " + tr("Result: %1").arg(status_message);
logged_queries->append(log_message + "\n");

Expand Down Expand Up @@ -2462,6 +2500,14 @@ void MainWindow::reloadSettings()

ui->actionDropQualifiedCheck->setChecked(Settings::getValue("SchemaDock", "dropQualifiedNames").toBool());
ui->actionEnquoteNamesCheck->setChecked(Settings::getValue("SchemaDock", "dropEnquotedNames").toBool());

if (Settings::getValue("db", "watcher").toBool())
connect(&fileSystemWatch, &QFileSystemWatcher::fileChanged, this, &MainWindow::refreshDb, Qt::UniqueConnection);
else {
disconnect(&fileSystemWatch, &QFileSystemWatcher::fileChanged, nullptr, nullptr);
if (!fileSystemWatch.files().isEmpty())
fileSystemWatch.removePaths(fileSystemWatch.files());
}
}

void MainWindow::checkNewVersion(const bool automatic)
Expand Down Expand Up @@ -3267,6 +3313,11 @@ void MainWindow::saveProject(const QString& currentFilename)
xml.writeAttribute("name", QString::fromStdString(tableIt->first.name()));

auto obj = db.getTableByName(tableIt->first);
if(!obj)
{
xml.writeEndElement();
continue;
}
saveBrowseDataTableSettings(tableIt->second, obj, xml);
xml.writeEndElement();
}
Expand Down
5 changes: 5 additions & 0 deletions src/MainWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

#include <memory>
#include <vector>

#include <QMainWindow>
#include <QFileSystemWatcher>

struct BrowseDataTableSettings;
class DbStructureModel;
Expand Down Expand Up @@ -121,6 +123,8 @@ friend TableBrowserDock;
QString currentProjectFilename;
bool isProjectModified;

QFileSystemWatcher fileSystemWatch;

void init();
void clearCompleterModelsFields();

Expand Down Expand Up @@ -176,6 +180,7 @@ private slots:
void fileNewInMemoryDatabase(bool open_create_dialog = true);
// Refresh visible table browsers. When all is true, refresh all browsers.
void refreshTableBrowsers(bool all = false);
void refreshDb();
bool fileClose();
bool fileSaveAs();
void createTable();
Expand Down
4 changes: 4 additions & 0 deletions src/PreferencesDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ void PreferencesDialog::loadSettings()
}

ui->spinStructureFontSize->setValue(Settings::getValue("db", "fontsize").toInt());
ui->watcherCheckBox->setChecked(Settings::getValue("db", "watcher").toBool());

// Gracefully handle the preferred Data Browser font not being available
int matchingFont = ui->comboDataBrowserFont->findText(Settings::getValue("databrowser", "font").toString(), Qt::MatchExactly);
Expand All @@ -119,6 +120,7 @@ void PreferencesDialog::loadSettings()
ui->spinSymbolLimit->setValue(Settings::getValue("databrowser", "symbol_limit").toInt());
ui->spinCompleteThreshold->setValue(Settings::getValue("databrowser", "complete_threshold").toInt());
ui->checkShowImagesInline->setChecked(Settings::getValue("databrowser", "image_preview").toBool());
ui->checkCellWordWrap->setChecked(Settings::getValue("databrowser", "cell_word_wrap").toBool());
ui->txtNull->setText(Settings::getValue("databrowser", "null_text").toString());
ui->txtBlob->setText(Settings::getValue("databrowser", "blob_text").toString());
ui->editFilterEscape->setText(Settings::getValue("databrowser", "filter_escape").toString());
Expand Down Expand Up @@ -196,12 +198,14 @@ void PreferencesDialog::saveSettings(bool accept)
Settings::setValue("db", "defaultsqltext", ui->editDatabaseDefaultSqlText->text());
Settings::setValue("db", "defaultfieldtype", ui->defaultFieldTypeComboBox->currentIndex());
Settings::setValue("db", "fontsize", ui->spinStructureFontSize->value());
Settings::setValue("db", "watcher", ui->watcherCheckBox->isChecked());

Settings::setValue("checkversion", "enabled", ui->checkUpdates->isChecked());

Settings::setValue("databrowser", "font", ui->comboDataBrowserFont->currentText());
Settings::setValue("databrowser", "fontsize", ui->spinDataBrowserFontSize->value());
Settings::setValue("databrowser", "image_preview", ui->checkShowImagesInline->isChecked());
Settings::setValue("databrowser", "cell_word_wrap", ui->checkCellWordWrap->isChecked());
saveColorSetting(ui->fr_null_fg, "null_fg");
saveColorSetting(ui->fr_null_bg, "null_bg");
saveColorSetting(ui->fr_reg_fg, "reg_fg");
Expand Down
Loading
Loading