diff --git a/src/Data.cpp b/src/Data.cpp index ffda72e4..ba41efdc 100644 --- a/src/Data.cpp +++ b/src/Data.cpp @@ -1,6 +1,9 @@ #include "Data.h" +#include +#include #include + #include // Note that these aren't all possible BOMs. But they are probably the most common ones. @@ -88,6 +91,19 @@ QByteArray removeBom(QByteArray& data) } } +QString isImageData(const QByteArray& data) +{ + // Check if it's an image. First do a quick test by calling canRead() which only checks the first couple of bytes or so. Only if + // that returned true, do a more sophisticated test of the data. This way we get both, good performance and proper data checking. + QBuffer imageBuffer(const_cast(&data)); + QImageReader readerBuffer(&imageBuffer); + QString imageFormat = readerBuffer.format(); + if(readerBuffer.canRead() && !readerBuffer.read().isNull()) + return imageFormat; + else + return QString(); +} + QStringList toStringList(const QList& list) { QStringList strings; for (const QByteArray &item : list) { diff --git a/src/Data.h b/src/Data.h index a725d196..037076d3 100644 --- a/src/Data.h +++ b/src/Data.h @@ -21,6 +21,9 @@ bool startsWithBom(const QByteArray& data); // with a BOM an empty byte array is returned and the original data is not modified. QByteArray removeBom(QByteArray& data); +// Check if a byte array contains an image. Returns the name of the image format for images or a null string for non-image data. +QString isImageData(const QByteArray& data); + QStringList toStringList(const QList& list); QByteArray encodeString(const QByteArray& str, const QString& encoding); diff --git a/src/EditDialog.cpp b/src/EditDialog.cpp index f09540d8..11b052d4 100644 --- a/src/EditDialog.cpp +++ b/src/EditDialog.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -856,12 +855,9 @@ int EditDialog::checkDataType(const QByteArray& bArrdata) return Null; } - // Check if it's an image. First do a quick test by calling canRead() which only checks the first couple of bytes or so. Only if - // that returned true, do a more sophisticated test of the data. This way we get both, good performance and proper data checking. - QBuffer imageBuffer(&cellData); - QImageReader readerBuffer(&imageBuffer); - QString imageFormat = readerBuffer.format(); - if(readerBuffer.canRead() && !readerBuffer.read().isNull()) + // Check if it's an image + QString imageFormat = isImageData(cellData); + if(!imageFormat.isNull()) return imageFormat == "svg" ? SVG : Image; // Check if it's text only diff --git a/src/EditTableDialog.cpp b/src/EditTableDialog.cpp index d4ac98bf..86a970ed 100644 --- a/src/EditTableDialog.cpp +++ b/src/EditTableDialog.cpp @@ -374,7 +374,6 @@ void EditTableDialog::checkInput() if (normTableName != m_table.name()) { const std::string oldTableName = m_table.name(); m_table.setName(normTableName); - m_fkEditorDelegate->updateTablesList(oldTableName); // update fk's that refer to table itself recursively const auto& fields = m_table.fields; @@ -505,6 +504,7 @@ void EditTableDialog::fieldItemChanged(QTreeWidgetItem *item, int column) field.setName(item->text(column).toStdString()); m_table.renameKeyInAllConstraints(oldFieldName.toStdString(), item->text(column).toStdString()); qobject_cast(ui->treeWidget->itemWidget(item, kType))->setProperty("column", item->text(column)); + qobject_cast(ui->treeWidget->itemWidget(item, kCollation))->setProperty("column", item->text(column)); // Update the field name in the map of old column names to new column names if(!m_bNewTable) @@ -845,24 +845,46 @@ void EditTableDialog::fieldSelectionChanged() if(hasSelection) { ui->buttonMoveUp->setEnabled(ui->treeWidget->selectionModel()->currentIndex().row() != 0); + ui->buttonMoveTop->setEnabled(ui->buttonMoveUp->isEnabled()); ui->buttonMoveDown->setEnabled(ui->treeWidget->selectionModel()->currentIndex().row() != ui->treeWidget->topLevelItemCount() - 1); + ui->buttonMoveBottom->setEnabled(ui->buttonMoveDown->isEnabled()); } } void EditTableDialog::moveUp() { - moveCurrentField(false); + moveCurrentField(MoveUp); } void EditTableDialog::moveDown() { - moveCurrentField(true); + moveCurrentField(MoveDown); } -void EditTableDialog::moveCurrentField(bool down) +void EditTableDialog::moveTop() +{ + moveCurrentField(MoveTop); +} + +void EditTableDialog::moveBottom() +{ + moveCurrentField(MoveBottom); +} + +void EditTableDialog::moveCurrentField(MoveFieldDirection dir) { int currentRow = ui->treeWidget->currentIndex().row(); - int newRow = currentRow + (down ? 1 : -1); + int newRow; + if(dir == MoveUp) + newRow = currentRow - 1; + else if(dir == MoveDown) + newRow = currentRow + 1; + else if(dir == MoveTop) + newRow = 0; + else if(dir == MoveBottom) + newRow = ui->treeWidget->topLevelItemCount() - 1; + else + return; // Save the comboboxes first by making copies QComboBox* newCombo[2]; @@ -891,7 +913,9 @@ void EditTableDialog::moveCurrentField(bool down) ui->treeWidget->setCurrentIndex(ui->treeWidget->currentIndex().sibling(newRow, 0)); // Finally update the table SQL - std::swap(m_table.fields[static_cast(newRow)], m_table.fields[static_cast(currentRow)]); + sqlb::Field temp = m_table.fields[static_cast(currentRow)]; + m_table.fields.erase(m_table.fields.begin() + currentRow); + m_table.fields.insert(m_table.fields.begin() + newRow, temp); // Update the SQL preview updateSqlText(); diff --git a/src/EditTableDialog.h b/src/EditTableDialog.h index 3cc29d16..2e9fdbf2 100644 --- a/src/EditTableDialog.h +++ b/src/EditTableDialog.h @@ -50,10 +50,18 @@ private: kConstraintSql = 3 }; + enum MoveFieldDirection + { + MoveUp, + MoveDown, + MoveTop, + MoveBottom + }; + void updateColumnWidth(); void updateSqlText(); - void moveCurrentField(bool down); + void moveCurrentField(MoveFieldDirection dir); private slots: void populateFields(); @@ -71,6 +79,8 @@ private slots: void updateTypeAndCollation(); void moveUp(); void moveDown(); + void moveTop(); + void moveBottom(); void setWithoutRowid(bool without_rowid); void changeSchema(const QString& schema); void removeConstraint(); diff --git a/src/EditTableDialog.ui b/src/EditTableDialog.ui index 7abf5a21..a4cf5053 100644 --- a/src/EditTableDialog.ui +++ b/src/EditTableDialog.ui @@ -6,7 +6,7 @@ 0 0 - 650 + 652 600 @@ -111,7 +111,7 @@ - Add field + Add @@ -131,7 +131,7 @@ false - Remove field + Remove @@ -145,13 +145,36 @@ + + + + false + + + Move to top + + + + :/icons/arrow_top:/icons/arrow_top + + + Qt::ToolButtonTextBesideIcon + + + true + + + Qt::NoArrow + + + false - Move field up + Move up @@ -171,7 +194,7 @@ false - Move field down + Move down @@ -185,6 +208,26 @@ + + + + false + + + Move to bottom + + + + :/icons/arrow_bottom:/icons/arrow_bottom + + + Qt::ToolButtonTextBesideIcon + + + true + + + @@ -275,7 +318,7 @@ Check constraint - + Collation @@ -463,12 +506,18 @@ buttonMore comboSchema checkWithoutRowid + groupDefinition addFieldButton removeFieldButton + buttonMoveTop buttonMoveUp buttonMoveDown + buttonMoveBottom treeWidget sqlTextEdit + buttonAddConstraint + buttonRemoveConstraint + tableConstraints @@ -529,8 +578,8 @@ addField() - 94 - 253 + 78 + 255 79 @@ -545,8 +594,8 @@ removeField() - 222 - 253 + 167 + 255 249 @@ -613,8 +662,8 @@ 78 - 117 - 176 + 138 + 172 @@ -625,8 +674,8 @@ setWithoutRowid(bool) - 344 - 143 + 485 + 160 324 @@ -657,8 +706,8 @@ changeSchema(QString) - 186 - 155 + 327 + 139 647 @@ -673,8 +722,8 @@ removeConstraint() - 186 - 155 + 295 + 255 647 @@ -682,6 +731,38 @@ + + buttonMoveTop + clicked() + EditTableDialog + moveTop() + + + 207 + 240 + + + 202 + 190 + + + + + buttonMoveBottom + clicked() + EditTableDialog + moveBottom() + + + 530 + 246 + + + 400 + 186 + + + fieldSelectionChanged() @@ -695,5 +776,7 @@ setWithoutRowid(bool) changeSchema(QString) removeConstraint() + moveTop() + moveBottom() diff --git a/src/ExtendedTableWidget.cpp b/src/ExtendedTableWidget.cpp index bb6856d8..a6980e7d 100644 --- a/src/ExtendedTableWidget.cpp +++ b/src/ExtendedTableWidget.cpp @@ -844,11 +844,19 @@ int ExtendedTableWidget::numVisibleRows() const std::unordered_set ExtendedTableWidget::selectedCols() const { std::unordered_set selectedCols; - for(const QModelIndex & idx : selectedIndexes()) + for(const auto& idx : selectionModel()->selectedColumns()) selectedCols.insert(idx.column()); return selectedCols; } +std::unordered_set ExtendedTableWidget::colsInSelection() const +{ + std::unordered_set colsInSelection; + for(const QModelIndex & idx : selectedIndexes()) + colsInSelection.insert(idx.column()); + return colsInSelection; +} + void ExtendedTableWidget::cellClicked(const QModelIndex& index) { // If Ctrl-Shift is pressed try to jump to the row referenced by the foreign key of the clicked cell diff --git a/src/ExtendedTableWidget.h b/src/ExtendedTableWidget.h index b7b0f7ba..2091954e 100644 --- a/src/ExtendedTableWidget.h +++ b/src/ExtendedTableWidget.h @@ -52,7 +52,11 @@ public: FilterTableHeader* filterHeader() { return m_tableHeader; } public: + // Get set of selected columns (all cells in column has to be selected) std::unordered_set selectedCols() const; + // Get set of columns traversed by selection (only some cells in column has to be selected) + std::unordered_set colsInSelection() const; + int numVisibleRows() const; void sortByColumns(const std::vector& columns); diff --git a/src/ForeignKeyEditorDelegate.cpp b/src/ForeignKeyEditorDelegate.cpp index 971dcd4a..b21c3f84 100644 --- a/src/ForeignKeyEditorDelegate.cpp +++ b/src/ForeignKeyEditorDelegate.cpp @@ -85,7 +85,8 @@ ForeignKeyEditorDelegate::ForeignKeyEditorDelegate(const DBBrowserDB& db, sqlb:: { for(const auto& jt : it.second) { - if(jt.second->type() == sqlb::Object::Types::Table) + // Don't insert the current table into the list. The name and fields of the current table are always taken from the m_table reference + if(jt.second->type() == sqlb::Object::Types::Table && jt.second->name() != m_table.name()) m_tablesIds.insert({jt.second->name(), std::dynamic_pointer_cast(jt.second)->fieldNames()}); } } @@ -105,14 +106,25 @@ QWidget* ForeignKeyEditorDelegate::createEditor(QWidget* parent, const QStyleOpt QComboBox* box = editor->idsComboBox; box->clear(); box->addItem(QString()); // for those heroes who don't like to specify key explicitly - for(const auto& n : m_tablesIds[tableName.toStdString()]) - box->addItem(QString::fromStdString(n)); + + // For recursive foreign keys get the field list from the m_table reference. For other foreign keys from the prepared field lists. + if(tableName.toStdString() == m_table.name()) + { + for(const auto& n : m_table.fieldNames()) + box->addItem(QString::fromStdString(n)); + } else { + for(const auto& n : m_tablesIds[tableName.toStdString()]) + box->addItem(QString::fromStdString(n)); + } + + box->setCurrentIndex(0); }); editor->tablesComboBox->clear(); for(const auto& i : m_tablesIds) editor->tablesComboBox->addItem(QString::fromStdString(i.first)); + editor->tablesComboBox->addItem(QString::fromStdString(m_table.name())); // For recursive foreign keys return editor; } @@ -175,12 +187,4 @@ void ForeignKeyEditorDelegate::updateEditorGeometry(QWidget* editor, const QStyl editor->setGeometry(option.rect); } -void ForeignKeyEditorDelegate::updateTablesList(const std::string& oldTableName) -{ - // this is used for recursive table constraints when - // table column references column within same table - m_tablesIds.erase(oldTableName); - m_tablesIds.insert({m_table.name(), m_table.fieldNames()}); -} - #include "ForeignKeyEditorDelegate.moc" diff --git a/src/ForeignKeyEditorDelegate.h b/src/ForeignKeyEditorDelegate.h index 8f6560a6..076706a9 100644 --- a/src/ForeignKeyEditorDelegate.h +++ b/src/ForeignKeyEditorDelegate.h @@ -25,8 +25,6 @@ public: void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; - void updateTablesList(const std::string& oldTableName); - private: const DBBrowserDB& m_db; sqlb::Table& m_table; diff --git a/src/PreferencesDialog.cpp b/src/PreferencesDialog.cpp index 1ce272d0..18000b1f 100644 --- a/src/PreferencesDialog.cpp +++ b/src/PreferencesDialog.cpp @@ -109,6 +109,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->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()); @@ -217,6 +218,7 @@ void PreferencesDialog::saveSettings() Settings::setValue("databrowser", "font", ui->comboDataBrowserFont->currentText()); Settings::setValue("databrowser", "fontsize", ui->spinDataBrowserFontSize->value()); + Settings::setValue("databrowser", "image_preview", ui->checkShowImagesInline->isChecked()); saveColorSetting(ui->fr_null_fg, "null_fg"); saveColorSetting(ui->fr_null_bg, "null_bg"); saveColorSetting(ui->fr_reg_fg, "reg_fg"); diff --git a/src/PreferencesDialog.ui b/src/PreferencesDialog.ui index 089c639c..443929fc 100644 --- a/src/PreferencesDialog.ui +++ b/src/PreferencesDialog.ui @@ -821,6 +821,23 @@ Can be set to 0 for disabling completion. + + + + Show images in cell + + + checkShowImagesInline + + + + + + + Enable this option to show a preview of BLOBs containing image data in the cells. This can affect the performance of the data browser, however. + + + @@ -1866,10 +1883,12 @@ Can be set to 0 for disabling completion. + tabWidget comboDefaultLocation locationEdit setLocationButton languageComboBox + appStyleCombo toolbarStyleComboMain toolbarStyleComboStructure toolbarStyleComboBrowse @@ -1877,6 +1896,7 @@ Can be set to 0 for disabling completion. toolbarStyleComboEditCell checkUseRemotes checkUpdates + buttonManageFileExtension encodingComboBox foreignKeysCheckBox checkHideSchemaLinebreaks @@ -1886,6 +1906,9 @@ Can be set to 0 for disabling completion. editDatabaseDefaultSqlText comboDataBrowserFont spinDataBrowserFontSize + spinSymbolLimit + spinCompleteThreshold + checkShowImagesInline fr_null_fg fr_null_bg txtNull @@ -1902,20 +1925,25 @@ Can be set to 0 for disabling completion. spinEditorFontSize spinLogFontSize spinTabSize + wrapComboBox + quoteComboBox checkAutoCompletion + checkCompleteUpper checkErrorIndicators checkHorizontalTiling listExtensions buttonAddExtension buttonRemoveExtension checkRegexDisabled - editRemoteCloneDirectory - buttonRemoteBrowseCloneDirectory - tableCaCerts + checkAllowLoadExtension + tabCertificates tableClientCerts buttonClientCertAdd buttonClientCertRemove - tabWidget + buttonProxy + editRemoteCloneDirectory + buttonRemoteBrowseCloneDirectory + tableCaCerts @@ -1992,12 +2020,12 @@ Can be set to 0 for disabling completion. setVisible(bool) - 365 - 207 + 344 + 230 - 108 - 280 + 119 + 273 @@ -2008,8 +2036,8 @@ Can be set to 0 for disabling completion. setVisible(bool) - 365 - 207 + 344 + 230 365 @@ -2024,8 +2052,8 @@ Can be set to 0 for disabling completion. activateRemoteTab(bool) - 161 - 172 + 231 + 393 382 @@ -2040,8 +2068,8 @@ Can be set to 0 for disabling completion. addClientCertificate() - 578 - 315 + 722 + 110 596 @@ -2056,8 +2084,8 @@ Can be set to 0 for disabling completion. removeClientCertificate() - 578 - 353 + 722 + 139 597 @@ -2072,8 +2100,8 @@ Can be set to 0 for disabling completion. chooseRemoteCloneDirectory() - 567 - 54 + 732 + 538 595 @@ -2088,12 +2116,12 @@ Can be set to 0 for disabling completion. setEnabled(bool) - 474 - 464 + 642 + 450 - 474 - 492 + 642 + 480 @@ -2104,8 +2132,8 @@ Can be set to 0 for disabling completion. on_buttonBox_clicked(QAbstractButton*) - 385 - 591 + 394 + 584 385 @@ -2120,8 +2148,8 @@ Can be set to 0 for disabling completion. configureProxy() - 385 - 591 + 225 + 506 385 diff --git a/src/Settings.cpp b/src/Settings.cpp index 8c262c95..67aa490c 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -208,6 +208,8 @@ QVariant Settings::getDefaultValue(const std::string& group, const std::string& return 5000; if(name == "complete_threshold") return 1000; + if(name == "image_preview") + return false; if(name == "indent_compact") return false; if(name == "auto_switch_mode") diff --git a/src/TableBrowser.cpp b/src/TableBrowser.cpp index 450a1fb4..06973418 100644 --- a/src/TableBrowser.cpp +++ b/src/TableBrowser.cpp @@ -130,31 +130,31 @@ TableBrowser::TableBrowser(QWidget* parent) : connect(ui->dataTable, &ExtendedTableWidget::selectedRowsToBeDeleted, this, &TableBrowser::deleteRecord); connect(ui->fontComboBox, &QFontComboBox::currentFontChanged, this, [this](const QFont &font) { - modifyColumnFormat(ui->dataTable->selectedCols(), [font](CondFormat& format) { format.setFontFamily(font.family()); }); + modifyColumnFormat(ui->dataTable->colsInSelection(), [font](CondFormat& format) { format.setFontFamily(font.family()); }); }); connect(ui->fontSizeBox, QOverload::of(&QSpinBox::valueChanged), this, [this](int pointSize) { - modifyColumnFormat(ui->dataTable->selectedCols(), [pointSize](CondFormat& format) { format.setFontPointSize(pointSize); }); + modifyColumnFormat(ui->dataTable->colsInSelection(), [pointSize](CondFormat& format) { format.setFontPointSize(pointSize); }); }); connect(ui->actionFontColor, &QAction::triggered, this, [this]() { QColor color = QColorDialog::getColor(QColor(m_browseTableModel->data(currentIndex(), Qt::ForegroundRole).toString())); if(color.isValid()) - modifyColumnFormat(ui->dataTable->selectedCols(), [color](CondFormat& format) { format.setForegroundColor(color); }); + modifyColumnFormat(ui->dataTable->colsInSelection(), [color](CondFormat& format) { format.setForegroundColor(color); }); }); connect(ui->actionBackgroundColor, &QAction::triggered, this, [this]() { QColor color = QColorDialog::getColor(QColor(m_browseTableModel->data(currentIndex(), Qt::BackgroundRole).toString())); if(color.isValid()) - modifyColumnFormat(ui->dataTable->selectedCols(), [color](CondFormat& format) { format.setBackgroundColor(color); }); + modifyColumnFormat(ui->dataTable->colsInSelection(), [color](CondFormat& format) { format.setBackgroundColor(color); }); }); connect(ui->actionBold, &QAction::toggled, this, [this](bool checked) { - modifyColumnFormat(ui->dataTable->selectedCols(), [checked](CondFormat& format) { format.setBold(checked); }); + modifyColumnFormat(ui->dataTable->colsInSelection(), [checked](CondFormat& format) { format.setBold(checked); }); }); connect(ui->actionItalic, &QAction::toggled, this, [this](bool checked) { - modifyColumnFormat(ui->dataTable->selectedCols(), [checked](CondFormat& format) { format.setItalic(checked); }); + modifyColumnFormat(ui->dataTable->colsInSelection(), [checked](CondFormat& format) { format.setItalic(checked); }); }); connect(ui->actionUnderline, &QAction::toggled, this, [this](bool checked) { - modifyColumnFormat(ui->dataTable->selectedCols(), [checked](CondFormat& format) { format.setUnderline(checked); }); + modifyColumnFormat(ui->dataTable->colsInSelection(), [checked](CondFormat& format) { format.setUnderline(checked); }); }); connect(ui->actionLeftAlign, &QAction::triggered, this, [this]() { @@ -162,33 +162,33 @@ TableBrowser::TableBrowser(QWidget* parent) : ui->actionRightAlign->setChecked(false); ui->actionCenter->setChecked(false); ui->actionJustify->setChecked(false); - modifyColumnFormat(ui->dataTable->selectedCols(), [](CondFormat& format) { format.setAlignment(CondFormat::AlignLeft); }); + modifyColumnFormat(ui->dataTable->colsInSelection(), [](CondFormat& format) { format.setAlignment(CondFormat::AlignLeft); }); }); connect(ui->actionRightAlign, &QAction::triggered, this, [this]() { ui->actionLeftAlign->setChecked(false); ui->actionRightAlign->setChecked(true); ui->actionCenter->setChecked(false); ui->actionJustify->setChecked(false); - modifyColumnFormat(ui->dataTable->selectedCols(), [](CondFormat& format) { format.setAlignment(CondFormat::AlignRight); }); + modifyColumnFormat(ui->dataTable->colsInSelection(), [](CondFormat& format) { format.setAlignment(CondFormat::AlignRight); }); }); connect(ui->actionCenter, &QAction::triggered, this, [this]() { ui->actionLeftAlign->setChecked(false); ui->actionRightAlign->setChecked(false); ui->actionCenter->setChecked(true); ui->actionJustify->setChecked(false); - modifyColumnFormat(ui->dataTable->selectedCols(), [](CondFormat& format) { format.setAlignment(CondFormat::AlignCenter); }); + modifyColumnFormat(ui->dataTable->colsInSelection(), [](CondFormat& format) { format.setAlignment(CondFormat::AlignCenter); }); }); connect(ui->actionJustify, &QAction::triggered, this, [this]() { ui->actionLeftAlign->setChecked(false); ui->actionRightAlign->setChecked(false); ui->actionCenter->setChecked(false); ui->actionJustify->setChecked(true); - modifyColumnFormat(ui->dataTable->selectedCols(), [](CondFormat& format) { format.setAlignment(CondFormat::AlignJustify); }); + modifyColumnFormat(ui->dataTable->colsInSelection(), [](CondFormat& format) { format.setAlignment(CondFormat::AlignJustify); }); }); connect(ui->actionEditCondFormats, &QAction::triggered, this, [this]() { editCondFormats(currentIndex().column()); }); connect(ui->actionClearFormat, &QAction::triggered, this, [this]() { - for (int column : ui->dataTable->selectedCols()) + for (int column : ui->dataTable->colsInSelection()) clearAllCondFormats(column); }); @@ -236,6 +236,41 @@ TableBrowser::TableBrowser(QWidget* parent) : connect(ui->actionToggleFormatToolbar, &QAction::toggled, ui->formatFrame, &QFrame::setVisible); ui->actionToggleFormatToolbar->setChecked(false); ui->formatFrame->setVisible(false); + + // Set up find frame + ui->frameFind->hide(); + + QShortcut* shortcutHideFindFrame = new QShortcut(QKeySequence("ESC"), ui->editFindExpression); + connect(shortcutHideFindFrame, &QShortcut::activated, ui->buttonFindClose, &QToolButton::click); + + connect(ui->actionFind, &QAction::triggered, [this](bool checked) { + if(checked) + { + ui->frameFind->show(); + ui->editFindExpression->setFocus(); + } else { + ui->buttonFindClose->click(); + } + }); + + connect(ui->editFindExpression, &QLineEdit::returnPressed, ui->buttonFindNext, &QToolButton::click); + connect(ui->editFindExpression, &QLineEdit::textChanged, this, [this]() { + // When the text has changed but neither Return nor F3 or similar nor any buttons were pressed, we want to include the current + // cell in the search as well. This makes sure the selected cell does not jump around every time the text is changed but only + // when the current cell does not match the search expression anymore. + find(ui->editFindExpression->text(), true, true); + }); + connect(ui->buttonFindClose, &QToolButton::clicked, this, [this](){ + ui->dataTable->setFocus(); + ui->frameFind->hide(); + ui->actionFind->setChecked(false); + }); + connect(ui->buttonFindPrevious, &QToolButton::clicked, this, [this](){ + find(ui->editFindExpression->text(), false); + }); + connect(ui->buttonFindNext, &QToolButton::clicked, this, [this](){ + find(ui->editFindExpression->text(), true); + }); } TableBrowser::~TableBrowser() @@ -328,6 +363,7 @@ void TableBrowser::setEnabled(bool enable) ui->actionRefresh->setEnabled(enable); ui->actionPrintTable->setEnabled(enable); ui->editGlobalFilter->setEnabled(enable); + ui->actionFind->setEnabled(enable); updateInsertDeleteRecordButton(); } @@ -379,7 +415,7 @@ void TableBrowser::updateTable() } statusMessage += tr(". Sum: %1; Average: %2; Min: %3; Max: %4").arg(sum).arg(sum/sel.count()).arg(min).arg(max); } - }; + } emit statusMessageRequested(statusMessage); }); } @@ -1310,3 +1346,54 @@ void TableBrowser::jumpToRow(const sqlb::ObjectIdentifier& table, QString column ui->dataTable->filterHeader()->setFilter(static_cast(column_index-obj->fields.begin()+1), QString("=") + value); updateTable(); } + +void TableBrowser::find(const QString& expr, bool forward, bool include_first) +{ + // Get the cell from which the search should be started. If there is a selected cell, use that. If there is no selected cell, start at the first cell. + QModelIndex start; + if(ui->dataTable->selectionModel()->hasSelection()) + start = ui->dataTable->selectionModel()->selectedIndexes().front(); + else + start = m_browseTableModel->index(0, 0); + + // Prepare the match flags with all the search settings + Qt::MatchFlags flags = Qt::MatchWrap; + + if(ui->checkFindCaseSensitive->isChecked()) + flags |= Qt::MatchCaseSensitive; + + if(ui->checkFindWholeCell->isChecked()) + flags |= Qt::MatchFixedString; + else + flags |= Qt::MatchContains; + + if(ui->checkFindRegEx->isChecked()) + flags |= Qt::MatchRegExp; + + // Prepare list of columns to search in. We only search in non-hidden rows + std::vector column_list; + sqlb::ObjectIdentifier tableName = currentlyBrowsedTableName(); + if(browseTableSettings[tableName].showRowid) + column_list.push_back(0); + for(int i=1;icolumnCount();i++) + { + if(browseTableSettings[tableName].hiddenColumns.contains(i) == false) + column_list.push_back(i); + else if(browseTableSettings[tableName].hiddenColumns[i] == false) + column_list.push_back(i); + } + + // Perform the actual search using the model class + const auto match = m_browseTableModel->nextMatch(start, column_list, expr, flags, !forward, include_first); + + // Select the next match if we found one + if(match.isValid()) { + ui->dataTable->setCurrentIndex(match); + ui->dataTable->scrollTo(match); + } + // Make the expression control red if no results were found + if(match.isValid() || expr == "") + ui->editFindExpression->setStyleSheet(""); + else + ui->editFindExpression->setStyleSheet("QLineEdit {color: white; background-color: rgb(255, 102, 102)}"); +} diff --git a/src/TableBrowser.h b/src/TableBrowser.h index fd1594cd..e6164410 100644 --- a/src/TableBrowser.h +++ b/src/TableBrowser.h @@ -152,6 +152,9 @@ private slots: void browseDataSetTableEncoding(bool forAllTables = false); void browseDataSetDefaultTableEncoding(); +private: + void find(const QString& expr, bool forward, bool include_first = false); + private: Ui::TableBrowser* ui; QIntValidator* gotoValidator; diff --git a/src/TableBrowser.ui b/src/TableBrowser.ui index 7b73be4b..3ea88432 100644 --- a/src/TableBrowser.ui +++ b/src/TableBrowser.ui @@ -7,7 +7,7 @@ 0 0 651 - 362 + 400 @@ -85,6 +85,7 @@ + @@ -194,6 +195,152 @@ + + + + + 16777215 + 31 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 1 + + + 1 + + + 1 + + + 1 + + + 3 + + + + + Find next match [Enter, F3] + + + Find next match with wrapping + + + + :/icons/down:/icons/down + + + F3 + + + + + + + Find previous match [Shift+F3] + + + Find previous match with mapping + + + + :/icons/up:/icons/up + + + Shift+F3 + + + + + + + Interpret search pattern as a regular expression + + + <html><head/><body><p>When checked, the pattern to find is interpreted as a UNIX regular expression. See <a href="https://en.wikibooks.org/wiki/Regular_Expressions">Regular Expression in Wikibooks</a>.</p></body></html> + + + Regular Expression + + + + + + + The found pattern must be a whole word + + + Whole Cell + + + + + + + Qt::DefaultContextMenu + + + Text pattern to find considering the checks in this frame + + + Find in table + + + true + + + + + + + Close Find Bar + + + Close Find Bar + + + + :/icons/close:/icons/close + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + The found pattern must match in letter case + + + Case Sensitive + + + + + + @@ -588,6 +735,24 @@ Qt::WidgetShortcut + + + true + + + + :/icons/find:/icons/find + + + Find in cells + + + Open the find tool bar which allows you to search for values in the table view below. + + + Ctrl+F + + true @@ -788,6 +953,14 @@ comboBrowseTable dataTable + editGlobalFilter + editFindExpression + buttonFindPrevious + buttonFindNext + checkFindCaseSensitive + checkFindWholeCell + checkFindRegEx + buttonFindClose buttonBegin buttonPrevious buttonNext @@ -806,8 +979,8 @@ updateTable() - 118 - 141 + 159 + 31 399 @@ -822,8 +995,8 @@ navigatePrevious() - 86 - 539 + 54 + 358 399 @@ -838,8 +1011,8 @@ navigateNext() - 183 - 539 + 139 + 358 399 @@ -854,8 +1027,8 @@ navigateGoto() - 365 - 539 + 403 + 360 399 @@ -870,8 +1043,8 @@ navigateGoto() - 506 - 538 + 550 + 360 399 @@ -886,8 +1059,8 @@ navigateEnd() - 223 - 539 + 169 + 358 499 @@ -902,8 +1075,8 @@ navigateBegin() - 50 - 539 + 24 + 358 499 @@ -1147,7 +1320,7 @@ 326 - 347 + 291 diff --git a/src/icons/bullet_arrow_bottom.png b/src/icons/bullet_arrow_bottom.png new file mode 100644 index 00000000..1a28d825 Binary files /dev/null and b/src/icons/bullet_arrow_bottom.png differ diff --git a/src/icons/bullet_arrow_top.png b/src/icons/bullet_arrow_top.png new file mode 100644 index 00000000..0ce86d2b Binary files /dev/null and b/src/icons/bullet_arrow_top.png differ diff --git a/src/icons/icons.qrc b/src/icons/icons.qrc index f9e08049..4e59acb3 100644 --- a/src/icons/icons.qrc +++ b/src/icons/icons.qrc @@ -83,6 +83,8 @@ color_swatch.png edit_cond_formats.png clear_sorting.png + bullet_arrow_bottom.png + bullet_arrow_top.png text_bold.png text_italic.png text_underline.png diff --git a/src/sql/ObjectIdentifier.cpp b/src/sql/ObjectIdentifier.cpp index 8910b432..d94aa1c7 100644 --- a/src/sql/ObjectIdentifier.cpp +++ b/src/sql/ObjectIdentifier.cpp @@ -53,6 +53,11 @@ std::string escapeIdentifier(const std::string& id) } } +std::string escapeString(const std::string& literal) +{ + return '\'' + duplicate_char(literal, '\'') + '\''; +} + bool ObjectIdentifier::fromSerialised(const std::string& serialised) { auto pos_comma = serialised.find(","); diff --git a/src/sql/ObjectIdentifier.h b/src/sql/ObjectIdentifier.h index 380da00f..8d2f5282 100644 --- a/src/sql/ObjectIdentifier.h +++ b/src/sql/ObjectIdentifier.h @@ -20,6 +20,9 @@ char getIdentifierQuoteChar(); // Add quotes to an identifier std::string escapeIdentifier(const std::string& id); +// Add SQL quotes to a string literal and escape any single quote character +std::string escapeString(const std::string& literal); + // Object identifier consisting of schema name and object name class ObjectIdentifier { diff --git a/src/sqlitedb.cpp b/src/sqlitedb.cpp index 0c47515e..cb118fdc 100644 --- a/src/sqlitedb.cpp +++ b/src/sqlitedb.cpp @@ -45,6 +45,10 @@ QString escapeIdentifier(const QString& id) { return QString::fromStdString(escapeIdentifier(id.toStdString())); } +QString escapeString(const QString& literal) +{ + return QString::fromStdString(escapeString(literal.toStdString())); +} } // collation callbacks @@ -324,7 +328,7 @@ bool DBBrowserDB::attach(const QString& filePath, QString attach_as) } } - if(!executeSQL(QString("ATTACH '%1' AS %2 %3").arg(filePath).arg(sqlb::escapeIdentifier(attach_as)).arg(key), false)) + if(!executeSQL(QString("ATTACH %1 AS %2 %3").arg(sqlb::escapeString(filePath)).arg(sqlb::escapeIdentifier(attach_as)).arg(key), false)) { QMessageBox::warning(nullptr, qApp->applicationName(), lastErrorMessage); return false; @@ -334,7 +338,7 @@ bool DBBrowserDB::attach(const QString& filePath, QString attach_as) delete cipherSettings; #else // Attach database - if(!executeSQL(QString("ATTACH '%1' AS %2").arg(filePath).arg(sqlb::escapeIdentifier(attach_as)), false)) + if(!executeSQL(QString("ATTACH %1 AS %2").arg(sqlb::escapeString(filePath)).arg(sqlb::escapeIdentifier(attach_as)), false)) { QMessageBox::warning(nullptr, qApp->applicationName(), lastErrorMessage); return false; diff --git a/src/sqlitedb.h b/src/sqlitedb.h index d0f23ad7..bdaa0db5 100644 --- a/src/sqlitedb.h +++ b/src/sqlitedb.h @@ -33,6 +33,7 @@ int collCompare(void* pArg, int sizeA, const void* sA, int sizeB, const void* sB namespace sqlb { QString escapeIdentifier(const QString& id); +QString escapeString(const QString& literal); } /// represents a single SQLite database. except when noted otherwise, diff --git a/src/sqlitetablemodel.cpp b/src/sqlitetablemodel.cpp index e2d090c6..eacfdd02 100644 --- a/src/sqlitetablemodel.cpp +++ b/src/sqlitetablemodel.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "RowLoader.h" @@ -422,6 +423,16 @@ QVariant SqliteTableModel::data(const QModelIndex &index, int role) const bool isNumber; value.toDouble(&isNumber); return static_cast((isNumber ? Qt::AlignRight : Qt::AlignLeft) | Qt::AlignVCenter); + } else if(role == Qt::DecorationRole) { + if(!row_available) + return QVariant(); + + if(Settings::getValue("databrowser", "image_preview").toBool() && !isImageData(cached_row->at(column)).isNull()) + { + QImage img; + img.loadFromData(cached_row->at(column)); + return QPixmap::fromImage(img); + } } @@ -1035,3 +1046,108 @@ void SqliteTableModel::waitUntilIdle () const { worker->waitUntilIdle(); } + +QModelIndex SqliteTableModel::nextMatch(const QModelIndex& start, const std::vector& column_list, const QString& value, Qt::MatchFlags flags, bool reverse, bool dont_skip_to_next_field) const +{ + // Extract flags + bool whole_cell = !(flags & Qt::MatchContains); + bool regex = flags & Qt::MatchRegExp; + Qt::CaseSensitivity case_sensitive = ((flags & Qt::MatchCaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive); + bool wrap = flags & Qt::MatchWrap; + int increment = (reverse ? -1 : 1); + + // Prepare the regular expression for regex mode + QRegularExpression reg_exp; + if(regex) + { + reg_exp = QRegularExpression(value, (case_sensitive ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption)); + if(!reg_exp.isValid()) + return QModelIndex(); + + if(whole_cell) + { +#if QT_VERSION < QT_VERSION_CHECK(5, 12, 0) + reg_exp.setPattern("\\A(" + reg_exp.pattern() + ")\\Z"); +#else + reg_exp.setPattern(QRegularExpression::anchoredPattern(reg_exp.pattern())); +#endif + } + } + + // Wait until the row count is there + waitUntilIdle(); + + // Make sure the start position starts in a column from the list of columns to search in + QModelIndex pos = start; + if(std::find(column_list.begin(), column_list.end(), pos.column()) == column_list.end()) + { + // If for some weird reason the start index is not in the column list, we simply use the first column of the column list instead + pos = pos.sibling(pos.row(), reverse ? column_list.back() : column_list.front()); + } + + // Get the last cell to search in. If wrapping is enabled, we search until we hit the start cell again. If wrapping is not enabled, we start at the last + // cell of the table. + QModelIndex end = (wrap ? pos : index(rowCount(), column_list.back())); + + // Loop through all cells for the search + while(true) + { + // Go to the next cell and skip all columns in between which we do not care about. This is done as the first step in order + // to skip the start index when matching the first cell is disabled. + if(dont_skip_to_next_field == false) + { + while(true) + { + // Next cell position + int next_row = pos.row(); + int next_column = pos.column() + increment; + + // Have we reached the end of the row? Then go to the next one + if(next_column < 0 || next_column >= static_cast(m_headers.size())) + { + next_row += increment; + next_column = (reverse ? column_list.back() : column_list.front()); + } + + // Have we reached the last row? Then wrap around to the first one + if(wrap && (next_row < 0 || next_row >= rowCount())) + next_row = (reverse ? rowCount()-1 : 0); + + // Set next index for search + pos = pos.sibling(next_row, next_column); + + // Have we hit the last column? We have not found anything then + if(pos == end) + return QModelIndex(); + + // Is this a column which we are supposed to search in? If so, stop looking for the next cell and start comparing + if(std::find(column_list.begin(), column_list.end(), next_column) != column_list.end()) + break; + } + } + + // Make sure the next time we hit the above check, we actuall move on to the next cell and do not skip the loop again. + dont_skip_to_next_field = false; + + // Get row from cache. If it is not in the cache, load the next chunk from the database + const size_t row = static_cast(pos.row()); + if(!m_cache.count(row)) + { + triggerCacheLoad(static_cast(row)); + waitUntilIdle(); + } + const Row* row_data = &m_cache.at(row); + + // Get cell data + const size_t column = static_cast(pos.column()); + QString data = row_data->at(column); + + // Perform comparison + if(whole_cell && !regex && data.compare(value, case_sensitive) == 0) + return pos; + else if(!whole_cell && !regex && data.contains(value, case_sensitive)) + return pos; + else if(regex && reg_exp.match(data).hasMatch()) + return pos; + } +} diff --git a/src/sqlitetablemodel.h b/src/sqlitetablemodel.h index 02e84a9b..2fa7cfc8 100644 --- a/src/sqlitetablemodel.h +++ b/src/sqlitetablemodel.h @@ -117,6 +117,19 @@ public: void addCondFormat(int column, const CondFormat& condFormat); void setCondFormats(int column, const std::vector& condFormats); + // Search for the specified expression in the given cells. This intended as a replacement for QAbstractItemModel::match() even though + // it does not override it, which - because of the different parameters - is not possible. + // start contains the index to start with, column_list contains the ordered list of the columns to look in, value is the value to search for, + // flags allows to modify the search process (Qt::MatchContains, Qt::MatchRegExp, Qt::MatchCaseSensitive, and Qt::MatchWrap are understood), + // reverse can be set to true to progress through the cells in backwards direction, and dont_skip_to_next_field can be set to true if the current + // cell can be matched as well. + QModelIndex nextMatch(const QModelIndex& start, + const std::vector& column_list, + const QString& value, + Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchContains), + bool reverse = false, + bool dont_skip_to_next_field = false) const; + DBBrowserDB& db() { return m_db; } public slots: diff --git a/src/tests/testsqlobjects.cpp b/src/tests/testsqlobjects.cpp index 9b7efa35..bdc847b4 100644 --- a/src/tests/testsqlobjects.cpp +++ b/src/tests/testsqlobjects.cpp @@ -182,7 +182,7 @@ void TestTable::parseSQL() "\tinfo VARCHAR(255) CHECK (info == 'x')\n" ");"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sSQL)))); + Table tab(*Table::parseSQL(sSQL)); QCOMPARE(tab.name(), "hero"); QCOMPARE(tab.rowidColumns(), {"_rowid_"}); @@ -212,7 +212,7 @@ void TestTable::parseSQLdefaultexpr() "date datetime default CURRENT_TIMESTAMP," "zoi integer)"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sSQL)))); + Table tab(*Table::parseSQL(sSQL)); QCOMPARE(tab.name(), "chtest"); QCOMPARE(tab.fields.at(0).name(), "id"); @@ -246,7 +246,7 @@ void TestTable::parseSQLMultiPk() "PRIMARY KEY(\"id1\",\"id2\")\n" ");"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sSQL)))); + Table tab(*Table::parseSQL(sSQL)); QCOMPARE(tab.name(), "hero"); QCOMPARE(tab.fields.at(0).name(), "id1"); @@ -265,7 +265,7 @@ void TestTable::parseSQLForeignKey() { std::string sSQL = "CREATE TABLE grammar_test(id, test, FOREIGN KEY(test) REFERENCES other_table);"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sSQL)))); + Table tab(*Table::parseSQL(sSQL)); QCOMPARE(tab.name(), "grammar_test"); QCOMPARE(tab.fields.at(0).name(), "id"); @@ -276,7 +276,7 @@ void TestTable::parseSQLSingleQuotes() { std::string sSQL = "CREATE TABLE 'test'('id','test');"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sSQL)))); + Table tab(*Table::parseSQL(sSQL)); QCOMPARE(tab.name(), "test"); QCOMPARE(tab.fields.at(0).name(), "id"); @@ -287,7 +287,7 @@ void TestTable::parseSQLSquareBrackets() { std::string sSQL = "CREATE TABLE [test]([id],[test]);"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sSQL)))); + Table tab(*Table::parseSQL(sSQL)); QCOMPARE(tab.name(), "test"); QCOMPARE(tab.fields.at(0).name(), "id"); @@ -299,7 +299,7 @@ void TestTable::parseSQLKeywordInIdentifier() { std::string sSQL = "CREATE TABLE deffered(key integer primary key, if text);"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sSQL)))); + Table tab(*Table::parseSQL(sSQL)); QCOMPARE(tab.name(), "deffered"); QCOMPARE(tab.fields.at(0).name(), "key"); @@ -313,7 +313,7 @@ void TestTable::parseSQLSomeKeywordsInIdentifier() "`Area of Work` TEXT," "`Average Number of Volunteers` INTEGER);"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sSQL)))); + Table tab(*Table::parseSQL(sSQL)); QCOMPARE(tab.name(), "Average Number of Volunteers by Area of Work"); QCOMPARE(tab.fields.at(0).name(), "Area of Work"); @@ -324,7 +324,7 @@ void TestTable::parseSQLWithoutRowid() { std::string sSQL = "CREATE TABLE test(a integer primary key, b integer) WITHOUT ROWID;"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sSQL)))); + Table tab(*Table::parseSQL(sSQL)); QCOMPARE(tab.primaryKey()->columnList(), {"a"}); QCOMPARE(tab.rowidColumns(), {"a"}); @@ -337,7 +337,7 @@ void TestTable::parseNonASCIIChars() "PRIMARY KEY(`Fieldöäüß`)" ");"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sSQL)))); + Table tab(*Table::parseSQL(sSQL)); QCOMPARE(tab.name(), "lösung"); QCOMPARE(tab.fields.at(0).name(), "Fieldöäüß"); @@ -350,7 +350,7 @@ void TestTable::parseNonASCIICharsEs() "PRIMARY KEY(\"Field áéíóúÁÉÍÓÚñÑçÇ\")" ");"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sSQL)))); + Table tab(*Table::parseSQL(sSQL)); QCOMPARE(tab.name(), "Cigüeñas de Alcalá"); QCOMPARE(tab.fields.at(0).name(), "Field áéíóúÁÉÍÓÚñÑçÇ"); @@ -360,7 +360,7 @@ void TestTable::parseSQLEscapedQuotes() { std::string sSql = "CREATE TABLE double_quotes(a text default 'a''a');"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sSql)))); + Table tab(*Table::parseSQL(sSql)); QCOMPARE(tab.name(), "double_quotes"); QCOMPARE(tab.fields.at(0).name(), "a"); @@ -371,7 +371,7 @@ void TestTable::parseSQLForeignKeys() { std::string sql = "CREATE TABLE foreign_key_test(a int, b int, foreign key (a) references x, foreign key (b) references w(y,z) on delete set null);"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sql)))); + Table tab(*Table::parseSQL(sql)); QCOMPARE(tab.name(), "foreign_key_test"); QCOMPARE(tab.fields.at(0).name(), "a"); @@ -386,7 +386,7 @@ void TestTable::parseSQLCheckConstraint() { std::string sql = "CREATE TABLE a (\"b\" text CHECK(\"b\"='A' or \"b\"='B'));"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sql)))); + Table tab(*Table::parseSQL(sql)); QCOMPARE(tab.name(), "a"); QCOMPARE(tab.fields.at(0).name(), "b"); @@ -398,7 +398,7 @@ void TestTable::parseDefaultValues() { std::string sql = "CREATE TABLE test(a int DEFAULT 0, b int DEFAULT -1, c text DEFAULT 'hello', d text DEFAULT '0');"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sql)))); + Table tab(*Table::parseSQL(sql)); QCOMPARE(tab.name(), "test"); QCOMPARE(tab.fields.at(0).name(), "a"); @@ -422,7 +422,7 @@ void TestTable::createTableWithIn() "value NVARCHAR(5) CHECK (value IN ('a', 'b', 'c'))" ");"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sSQL)))); + Table tab(*Table::parseSQL(sSQL)); QCOMPARE(tab.name(), "not_working"); QCOMPARE(tab.fields.at(1).check(), "\"value\" IN ('a', 'b', 'c')"); @@ -439,7 +439,7 @@ void TestTable::createTableWithNotLikeConstraint() "value6 INTEGER CHECK(value6 NOT BETWEEN 1 AND 100)\n" ");"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sSQL)))); + Table tab(*Table::parseSQL(sSQL)); QCOMPARE(tab.name(), "hopefully_working"); QCOMPARE(tab.fields.at(0).check(), "\"value\" NOT LIKE 'prefix%'"); @@ -458,7 +458,7 @@ void TestTable::rowValues() "CHECK((a, b) = (1, 2))\n" ");"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sql)))); + Table tab(*Table::parseSQL(sql)); QCOMPARE(tab.name(), "test"); QCOMPARE(std::dynamic_pointer_cast(tab.constraint({}, sqlb::Constraint::CheckConstraintType))->expression(), "(\"a\", \"b\") = (1, 2)"); @@ -473,7 +473,7 @@ void TestTable::complexExpressions() "d INTEGER CHECK((((d > 0))))\n" ");"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sql)))); + Table tab(*Table::parseSQL(sql)); QCOMPARE(tab.name(), "test"); QCOMPARE(tab.fields.at(0).check(), "(\"a\" > 0)"); @@ -488,7 +488,7 @@ void TestTable::datetimeExpression() "entry INTEGER DEFAULT (DATETIME(CURRENT_TIMESTAMP, 'LOCALTIME'))\n" ");"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sql)))); + Table tab(*Table::parseSQL(sql)); QCOMPARE(tab.name(), "test"); QCOMPARE(tab.fields.at(0).name(), "entry"); @@ -502,7 +502,7 @@ void TestTable::extraParentheses() "xy INTEGER DEFAULT (1 + (5) - 4)\n" ");"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sql)))); + Table tab(*Table::parseSQL(sql)); QCOMPARE(tab.name(), "test"); QCOMPARE(tab.fields.at(0).name(), "xy"); @@ -516,7 +516,7 @@ void TestTable::moduloOperator() "xy INTEGER DEFAULT (7 % 2)\n" ");"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sql)))); + Table tab(*Table::parseSQL(sql)); QCOMPARE(tab.name(), "test"); QCOMPARE(tab.fields.at(0).name(), "xy"); @@ -533,7 +533,7 @@ void TestTable::complexExpression() "CHECK((a = 'S' AND b IS NOT NULL) OR (a IN ('A', 'P')))" ");"; - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sql)))); + Table tab(*Table::parseSQL(sql)); QCOMPARE(tab.name(), "test"); QCOMPARE(tab.fields.at(0).name(), "uuid"); @@ -549,7 +549,7 @@ void TestTable::parseTest() { QFETCH(std::string, sql); - Table tab(*(std::dynamic_pointer_cast(Table::parseSQL(sql)))); + Table tab(*Table::parseSQL(sql)); QVERIFY(tab.fullyParsed()); }