17#include "moc_qgsqueryresultwidget.cpp" 
   42#include <QInputDialog> 
   45const QgsSettingsEntryString *QgsQueryResultWidget::settingLastSourceFolder = 
new QgsSettingsEntryString( QStringLiteral( 
"last-source-folder" ), sTreeSqlQueries, QString(), QStringLiteral( 
"Last used folder for SQL source files" ) );
 
   62  splitter->setCollapsible( 0, 
false );
 
   63  splitter->setCollapsible( 1, 
false );
 
   65  splitter->restoreState( settings.
value( QStringLiteral( 
"Windows/QueryResult/SplitState" ) ).toByteArray() );
 
   67  connect( splitter, &QSplitter::splitterMoved, 
this, [
this] {
 
   69    settings.
setValue( QStringLiteral( 
"Windows/QueryResult/SplitState" ), splitter->saveState() );
 
   73  mainLayout->setSpacing( 6 );
 
   74  progressLayout->setSpacing( 6 );
 
   76  mQueryResultsTableView->hide();
 
   77  mQueryResultsTableView->setItemDelegate( 
new QgsQueryResultItemDelegate( mQueryResultsTableView ) );
 
   78  mQueryResultsTableView->setContextMenuPolicy( Qt::CustomContextMenu );
 
   79  connect( mQueryResultsTableView, &QTableView::customContextMenuRequested, 
this, &QgsQueryResultPanelWidget::showCellContextMenu );
 
   85  QVBoxLayout *vl = 
new QVBoxLayout();
 
   86  vl->setContentsMargins( 0, 0, 0, 0 );
 
   87  vl->addWidget( mCodeEditorWidget );
 
   88  mSqlEditorContainer->setLayout( vl );
 
   90  connect( mExecuteButton, &QPushButton::pressed, 
this, &QgsQueryResultPanelWidget::executeQuery );
 
   92  connect( mLoadLayerPushButton, &QPushButton::pressed, 
this, [
this] {
 
  100        const bool res = mConnection->validateSqlVectorLayer( options, message );
 
  103          mMessageBar->pushCritical( QString(), message );
 
  107          emit createSqlVectorLayer( mConnection->providerKey(), mConnection->uri(), options );
 
  112        mMessageBar->pushCritical( tr( 
"Error validating query" ), e.
what() );
 
  116  connect( mSqlEditor, &QgsCodeEditorSQL::textChanged, 
this, &QgsQueryResultPanelWidget::updateButtons );
 
  118  connect( mSqlEditor, &QgsCodeEditorSQL::selectionChanged, 
this, [
this] {
 
  119    mExecuteButton->setText( mSqlEditor->selectedText().isEmpty() ? tr( 
"Execute" ) : tr( 
"Execute Selection" ) );
 
  121  connect( mFilterToolButton, &QToolButton::pressed, 
this, [
this] {
 
  126        std::unique_ptr<QgsVectorLayer> vlayer { mConnection->createSqlVectorLayer( sqlVectorLayerOptions() ) };
 
  128        if ( builder.exec() == QDialog::Accepted )
 
  130          mFilterLineEdit->setText( builder.sql() );
 
  135        mMessageBar->pushCritical( tr( 
"Error opening filter dialog" ), tr( 
"There was an error while preparing SQL filter dialog: %1." ).arg( ex.
what() ) );
 
  141  mStatusLabel->hide();
 
  142  mSqlErrorText->hide();
 
  144  mLoadAsNewLayerGroupBox->setCollapsed( 
true );
 
  151      mPkColumnsCheckBox->setVisible( showPkConfig );
 
  152      mPkColumnsComboBox->setVisible( showPkConfig );
 
  155      mGeometryColumnCheckBox->setVisible( showGeometryColumnConfig );
 
  156      mGeometryColumnComboBox->setVisible( showGeometryColumnConfig );
 
  159      mFilterLabel->setVisible( showFilterConfig );
 
  160      mFilterToolButton->setVisible( showFilterConfig );
 
  161      mFilterLineEdit->setVisible( showFilterConfig );
 
  164      mAvoidSelectingAsFeatureIdCheckBox->setVisible( showDisableSelectAtId );
 
  168  QShortcut *copySelection = 
new QShortcut( QKeySequence::Copy, mQueryResultsTableView );
 
  169  connect( copySelection, &QShortcut::activated, 
this, &QgsQueryResultPanelWidget::copySelection );
 
  171  setConnection( connection );
 
  174QgsQueryResultPanelWidget::~QgsQueryResultPanelWidget()
 
  177  cancelRunningQuery();
 
  187  return mCodeEditorWidget;
 
  192  mSqlVectorLayerOptions = options;
 
  193  if ( !options.
sql.isEmpty() )
 
  195    setQuery( options.
sql );
 
  199  mPkColumnsComboBox->setCheckedItems( {} );
 
  204  mGeometryColumnCheckBox->setChecked( !options.
geometryColumn.isEmpty() );
 
  205  mGeometryColumnComboBox->clear();
 
  208    mGeometryColumnComboBox->setCurrentText( options.
geometryColumn );
 
  210  mFilterLineEdit->setText( options.
filter );
 
  211  mLayerNameLineEdit->setText( options.
layerName );
 
  214void QgsQueryResultPanelWidget::setWidgetMode( QgsQueryResultWidget::QueryWidgetMode widgetMode )
 
  216  mQueryWidgetMode = widgetMode;
 
  217  switch ( widgetMode )
 
  219    case QgsQueryResultWidget::QueryWidgetMode::SqlQueryMode:
 
  220      mLoadAsNewLayerGroupBox->setTitle( tr( 
"Load as New Layer" ) );
 
  221      mLoadLayerPushButton->setText( tr( 
"Load Layer" ) );
 
  222      mLoadAsNewLayerGroupBox->setCollapsed( 
true );
 
  224    case QgsQueryResultWidget::QueryWidgetMode::QueryLayerUpdateMode:
 
  225      mLoadAsNewLayerGroupBox->setTitle( tr( 
"Update Query Layer" ) );
 
  226      mLoadLayerPushButton->setText( tr( 
"Update Layer" ) );
 
  227      mLoadAsNewLayerGroupBox->setCollapsed( 
false );
 
  232void QgsQueryResultPanelWidget::executeQuery()
 
  234  mQueryResultsTableView->hide();
 
  235  mSqlErrorText->hide();
 
  236  mFirstRowFetched = 
false;
 
  238  cancelRunningQuery();
 
  241    const QString sql { mSqlEditor->selectedText().isEmpty() ? mSqlEditor->text() : mSqlEditor->selectedText() };
 
  245                                                                                                         { QStringLiteral( 
"query" ), sql },
 
  246                                                                                                         { QStringLiteral( 
"provider" ), mConnection->providerKey() },
 
  247                                                                                                         { QStringLiteral( 
"connection" ), mConnection->uri() },
 
  251    mWasCanceled = 
false;
 
  252    mFeedback = std::make_unique<QgsFeedback>();
 
  253    mStopButton->setEnabled( 
true );
 
  254    mStatusLabel->show();
 
  255    mStatusLabel->setText( tr( 
"Executing query…" ) );
 
  256    mProgressBar->show();
 
  257    mProgressBar->setRange( 0, 0 );
 
  258    mSqlErrorMessage.clear();
 
  260    connect( mStopButton, &QPushButton::pressed, mFeedback.get(), [
this] {
 
  261      mStatusLabel->setText( tr( 
"Stopped" ) );
 
  263      mProgressBar->hide();
 
  268    connect( &mQueryResultWatcher, &QFutureWatcher<QgsAbstractDatabaseProviderConnection::QueryResult>::finished, 
this, &QgsQueryResultPanelWidget::startFetching, Qt::ConnectionType::UniqueConnection );
 
  273        return mConnection->execSql( sql, mFeedback.get() );
 
  277        mSqlErrorMessage = ex.
what();
 
  281    mQueryResultWatcher.setFuture( future );
 
  285    showError( tr( 
"Connection error" ), tr( 
"Cannot execute query: connection to the database is not available." ) );
 
  289void QgsQueryResultPanelWidget::updateButtons()
 
  291  mFilterLineEdit->setEnabled( mFirstRowFetched );
 
  292  mFilterToolButton->setEnabled( mFirstRowFetched );
 
  293  const bool isEmpty = mSqlEditor->text().isEmpty();
 
  294  mExecuteButton->setEnabled( !isEmpty );
 
  296  mLoadAsNewLayerGroupBox->setEnabled(
 
  297    mSqlErrorMessage.isEmpty() && mFirstRowFetched
 
  301void QgsQueryResultPanelWidget::showCellContextMenu( QPoint point )
 
  303  const QModelIndex modelIndex = mQueryResultsTableView->indexAt( point );
 
  304  if ( modelIndex.isValid() )
 
  306    QMenu *menu = 
new QMenu();
 
  307    menu->setAttribute( Qt::WA_DeleteOnClose );
 
  309    menu->addAction( 
QgsApplication::getThemeIcon( 
"mActionEditCopy.svg" ), tr( 
"Copy" ), 
this, [
this] { copySelection(); }, QKeySequence::Copy );
 
  311    menu->exec( mQueryResultsTableView->viewport()->mapToGlobal( point ) );
 
  315void QgsQueryResultPanelWidget::copySelection()
 
  317  const QModelIndexList selection = mQueryResultsTableView->selectionModel()->selectedIndexes();
 
  318  if ( selection.empty() )
 
  325  for ( 
const QModelIndex &index : selection )
 
  327    if ( minRow == -1 || index.row() < minRow )
 
  328      minRow = index.row();
 
  329    if ( maxRow == -1 || index.row() > maxRow )
 
  330      maxRow = index.row();
 
  331    if ( minCol == -1 || index.column() < minCol )
 
  332      minCol = index.column();
 
  333    if ( maxCol == -1 || index.column() > maxCol )
 
  334      maxCol = index.column();
 
  337  if ( minRow == maxRow && minCol == maxCol )
 
  340    const QString text = mModel->data( selection.at( 0 ), Qt::DisplayRole ).toString();
 
  341    QApplication::clipboard()->setText( text );
 
  345    copyResults( minRow, maxRow, minCol, maxCol );
 
  349void QgsQueryResultPanelWidget::updateSqlLayerColumns()
 
  354  mFilterToolButton->setEnabled( 
true );
 
  355  mFilterLineEdit->setEnabled( 
true );
 
  356  mPkColumnsComboBox->clear();
 
  357  mGeometryColumnComboBox->clear();
 
  358  const bool hasPkInformation { !mSqlVectorLayerOptions.primaryKeyColumns.isEmpty() };
 
  359  const bool hasGeomColInformation { !mSqlVectorLayerOptions.geometryColumn.isEmpty() };
 
  360  static const QStringList geomColCandidates { QStringLiteral( 
"geom" ), QStringLiteral( 
"geometry" ), QStringLiteral( 
"the_geom" ) };
 
  361  const QStringList constCols { mModel->columns() };
 
  362  for ( 
const QString &
c : constCols )
 
  364    const bool pkCheckedState = hasPkInformation ? mSqlVectorLayerOptions.primaryKeyColumns.contains( 
c ) : 
c.contains( QStringLiteral( 
"id" ), Qt::CaseSensitivity::CaseInsensitive );
 
  366    mPkColumnsComboBox->addItemWithCheckState( 
c, pkCheckedState && mPkColumnsComboBox->checkedItems().isEmpty() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked );
 
  367    mGeometryColumnComboBox->addItem( 
c );
 
  368    if ( !hasGeomColInformation && geomColCandidates.contains( 
c, Qt::CaseSensitivity::CaseInsensitive ) )
 
  370      mGeometryColumnComboBox->setCurrentText( 
c );
 
  373  mPkColumnsCheckBox->setChecked( hasPkInformation );
 
  374  mGeometryColumnCheckBox->setChecked( hasGeomColInformation );
 
  375  if ( hasGeomColInformation )
 
  377    mGeometryColumnComboBox->setCurrentText( mSqlVectorLayerOptions.geometryColumn );
 
  381void QgsQueryResultPanelWidget::cancelRunningQuery()
 
  390  if ( mQueryResultWatcher.isRunning() )
 
  392    mQueryResultWatcher.waitForFinished();
 
  396void QgsQueryResultPanelWidget::cancelApiFetcher()
 
  400    mApiFetcher->stopFetching();
 
  405void QgsQueryResultPanelWidget::startFetching()
 
  409    if ( !mSqlErrorMessage.isEmpty() )
 
  411      showError( tr( 
"SQL error" ), mSqlErrorMessage, 
true );
 
  417        mStatusLabel->setText( QStringLiteral( 
"Query executed successfully (%1 rows, %2 ms)" )
 
  418                                 .arg( QLocale().toString( mQueryResultWatcher.result().rowCount() ), QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() ) ) );
 
  422        mStatusLabel->setText( QStringLiteral( 
"Query executed successfully (%1 s)" ).arg( QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() ) ) );
 
  424      mProgressBar->hide();
 
  425      mModel = std::make_unique<QgsQueryResultModel>( mQueryResultWatcher.result() );
 
  431      connect( mModel.get(), &QgsQueryResultModel::fetchMoreRows, 
this, [
this]( 
long long maxRows ) {
 
  432        mFetchedRowsBatchCount = 0;
 
  433        mProgressBar->setRange( 0, maxRows );
 
  434        mProgressBar->show();
 
  437      connect( mModel.get(), &QgsQueryResultModel::rowsInserted, 
this, [
this]( 
const QModelIndex &, 
int first, 
int last ) {
 
  438        if ( !mFirstRowFetched )
 
  440          emit firstResultBatchFetched();
 
  441          mFirstRowFetched = true;
 
  442          mQueryResultsTableView->show();
 
  444          updateSqlLayerColumns();
 
  445          mActualRowCount = mModel->queryResult().rowCount();
 
  447        mStatusLabel->setText( tr( 
"Fetched rows: %1/%2 %3 %4 ms" )
 
  448                                 .arg( QLocale().toString( mModel->rowCount( mModel->index( -1, -1 ) ) ), mActualRowCount != -1 ? QLocale().toString( mActualRowCount ) : tr( 
"unknown" ), mWasCanceled ? tr( 
"(stopped)" ) : QString(), QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() ) ) );
 
  449        mFetchedRowsBatchCount += last - first + 1;
 
  450        mProgressBar->setValue( mFetchedRowsBatchCount );
 
  453      mQueryResultsTableView->setModel( mModel.get() );
 
  454      mQueryResultsTableView->show();
 
  456      connect( mModel.get(), &QgsQueryResultModel::fetchingComplete, mStopButton, [
this] {
 
  458        const QgsHistoryEntry currentHistoryEntry = QgsGui::historyProviderRegistry()->entry( mCurrentHistoryEntryId, ok );
 
  459        QVariantMap entryDetails = currentHistoryEntry.entry;
 
  460        entryDetails.insert( QStringLiteral( 
"rows" ), mActualRowCount );
 
  461        entryDetails.insert( QStringLiteral( 
"time" ), mQueryResultWatcher.result().queryExecutionTime() );
 
  463        QgsGui::historyProviderRegistry()->updateEntry( mCurrentHistoryEntryId, entryDetails );
 
  464        mProgressBar->hide();
 
  465        mStopButton->setEnabled( false );
 
  471    mStatusLabel->setText( tr( 
"SQL command aborted" ) );
 
  472    mProgressBar->hide();
 
  476void QgsQueryResultPanelWidget::showError( 
const QString &title, 
const QString &message, 
bool isSqlError )
 
  478  mStatusLabel->show();
 
  479  mStatusLabel->setText( tr( 
"An error occurred while executing the query" ) );
 
  480  mProgressBar->hide();
 
  481  mQueryResultsTableView->hide();
 
  484    mSqlErrorText->show();
 
  485    mSqlErrorText->setText( message );
 
  489    mMessageBar->pushCritical( title, message );
 
  493void QgsQueryResultPanelWidget::tokensReady( 
const QStringList &tokens )
 
  495  mSqlEditor->setExtraKeywords( mSqlEditor->extraKeywords() + tokens );
 
  496  mSqlErrorText->setExtraKeywords( mSqlErrorText->extraKeywords() + tokens );
 
  499void QgsQueryResultPanelWidget::copyResults()
 
  501  const int rowCount = mModel->rowCount( QModelIndex() );
 
  502  const int columnCount = mModel->columnCount( QModelIndex() );
 
  503  copyResults( 0, rowCount - 1, 0, columnCount - 1 );
 
  506void QgsQueryResultPanelWidget::copyResults( 
int fromRow, 
int toRow, 
int fromColumn, 
int toColumn )
 
  508  QStringList rowStrings;
 
  509  QStringList columnStrings;
 
  511  const int rowCount = mModel->rowCount( QModelIndex() );
 
  512  const int columnCount = mModel->columnCount( QModelIndex() );
 
  514  toRow = std::min( toRow, rowCount - 1 );
 
  515  toColumn = std::min( toColumn, columnCount - 1 );
 
  517  rowStrings.reserve( toRow - fromRow );
 
  520  for ( 
int col = fromColumn; col <= toColumn; col++ )
 
  522    columnStrings += mModel->headerData( col, Qt::Horizontal, Qt::DisplayRole ).toString();
 
  524  rowStrings += columnStrings.join( QLatin1Char( 
'\t' ) );
 
  525  columnStrings.clear();
 
  527  for ( 
int row = fromRow; row <= toRow; row++ )
 
  529    for ( 
int col = fromColumn; col <= toColumn; col++ )
 
  531      columnStrings += mModel->data( mModel->index( row, col ), Qt::DisplayRole ).toString();
 
  533    rowStrings += columnStrings.join( QLatin1Char( 
'\t' ) );
 
  534    columnStrings.clear();
 
  537  if ( !rowStrings.isEmpty() )
 
  539    const QString text = rowStrings.join( QLatin1Char( 
'\n' ) );
 
  540    QString html = QStringLiteral( 
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"><html><head><meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"/></head><body><table border=\"1\"><tr><td>%1</td></tr></table></body></html>" ).arg( text );
 
  541    html.replace( QLatin1String( 
"\t" ), QLatin1String( 
"</td><td>" ) ).replace( QLatin1String( 
"\n" ), QLatin1String( 
"</td></tr><tr><td>" ) );
 
  543    QMimeData *mdata = 
new QMimeData();
 
  544    mdata->setData( QStringLiteral( 
"text/html" ), html.toUtf8() );
 
  545    if ( !text.isEmpty() )
 
  547      mdata->setText( text );
 
  551    QApplication::clipboard()->setMimeData( mdata, QClipboard::Selection );
 
  553    QApplication::clipboard()->setMimeData( mdata, QClipboard::Clipboard );
 
  559  const thread_local QRegularExpression rx( QStringLiteral( 
";\\s*$" ) );
 
  560  mSqlVectorLayerOptions.sql = mSqlEditor->text().replace( rx, QString() );
 
  561  mSqlVectorLayerOptions.filter = mFilterLineEdit->text();
 
  562  mSqlVectorLayerOptions.primaryKeyColumns = mPkColumnsComboBox->checkedItems();
 
  563  mSqlVectorLayerOptions.geometryColumn = mGeometryColumnComboBox->currentText();
 
  564  mSqlVectorLayerOptions.layerName = mLayerNameLineEdit->text();
 
  565  mSqlVectorLayerOptions.disableSelectAtId = mAvoidSelectingAsFeatureIdCheckBox->isChecked();
 
  568  if ( !mPkColumnsCheckBox->isChecked() )
 
  572  if ( !mGeometryColumnCheckBox->isChecked() )
 
  581  mConnection.reset( connection );
 
  588    const QMultiMap<Qgis::SqlKeywordCategory, QStringList> keywordsDict { connection->
sqlDictionary() };
 
  589    QStringList keywords;
 
  590    for ( 
auto it = keywordsDict.constBegin(); it != keywordsDict.constEnd(); it++ )
 
  592      keywords.append( it.value() );
 
  596    mSqlEditor->setExtraKeywords( keywords );
 
  597    mSqlErrorText->setExtraKeywords( keywords );
 
  600    QThread *apiFetcherWorkerThread = 
new QThread();
 
  601    QgsConnectionsApiFetcher *apiFetcher = 
new QgsConnectionsApiFetcher( mConnection->uri(), mConnection->providerKey() );
 
  602    apiFetcher->moveToThread( apiFetcherWorkerThread );
 
  603    connect( apiFetcherWorkerThread, &QThread::started, apiFetcher, &QgsConnectionsApiFetcher::fetchTokens );
 
  604    connect( apiFetcher, &QgsConnectionsApiFetcher::tokensReady, 
this, &QgsQueryResultPanelWidget::tokensReady );
 
  605    connect( apiFetcher, &QgsConnectionsApiFetcher::fetchingFinished, apiFetcherWorkerThread, [apiFetcher, apiFetcherWorkerThread] {
 
  606      apiFetcherWorkerThread->quit();
 
  607      apiFetcherWorkerThread->wait();
 
  608      apiFetcherWorkerThread->deleteLater();
 
  609      apiFetcher->deleteLater();
 
  612    mApiFetcher = apiFetcher;
 
  613    apiFetcherWorkerThread->start();
 
  619void QgsQueryResultPanelWidget::setQuery( 
const QString &sql )
 
  621  mSqlEditor->setText( sql );
 
  624void QgsQueryResultPanelWidget::notify( 
const QString &title, 
const QString &text, 
Qgis::MessageLevel level )
 
  626  mMessageBar->pushMessage( title, text, level );
 
  638  dlg.setWindowTitle( tr( 
"Store Query" ) );
 
  639  dlg.setHintString( tr( 
"Name for the stored query" ) );
 
  640  dlg.setOverwriteEnabled( 
true );
 
  641  dlg.setConflictingNameWarning( tr( 
"A stored query with this name already exists, it will be overwritten." ) );
 
  642  dlg.setShowExistingNamesCompleter( 
true );
 
  643  if ( dlg.exec() != QDialog::Accepted )
 
  646  const QString name = dlg.name();
 
  647  if ( name.isEmpty() )
 
  668  mQueryWidget = 
new QgsQueryResultPanelWidget( 
nullptr, connection );
 
  669  mPanelStack->setMainPanel( mQueryWidget );
 
  671  mPresetQueryMenu = 
new QMenu( 
this );
 
  672  connect( mPresetQueryMenu, &QMenu::aboutToShow, 
this, &QgsQueryResultWidget::populatePresetQueryMenu );
 
  674  QToolButton *presetQueryButton = 
new QToolButton();
 
  675  presetQueryButton->setMenu( mPresetQueryMenu );
 
  677  presetQueryButton->setPopupMode( QToolButton::InstantPopup );
 
  678  mToolBar->addWidget( presetQueryButton );
 
  680  connect( mActionOpenQuery, &QAction::triggered, 
this, &QgsQueryResultWidget::openQuery );
 
  681  connect( mActionSaveQuery, &QAction::triggered, 
this, [
this] { saveQuery( 
false ); } );
 
  682  connect( mActionSaveQueryAs, &QAction::triggered, 
this, [
this] { saveQuery( 
true ); } );
 
  684  connect( mActionCut, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::cut );
 
  685  connect( mActionCopy, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::copy );
 
  686  connect( mActionPaste, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::paste );
 
  687  connect( mActionUndo, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::undo );
 
  688  connect( mActionRedo, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::redo );
 
  689  mActionUndo->setEnabled( 
false );
 
  690  mActionRedo->setEnabled( 
false );
 
  694  connect( mQueryWidget->sqlEditor(), &QgsCodeEditor::modificationChanged, 
this, &QgsQueryResultWidget::setHasChanged );
 
  696  connect( mActionShowHistory, &QAction::toggled, 
this, &QgsQueryResultWidget::showHistoryPanel );
 
  698  connect( mActionClear, &QAction::triggered, 
this, [
this] {
 
  699    mQueryWidget->sqlEditor()->setText( QString() );
 
  700    mActionUndo->setEnabled( 
false );
 
  701    mActionRedo->setEnabled( 
false );
 
  704  connect( mQueryWidget->sqlEditor(), &QgsCodeEditorSQL::textChanged, 
this, &QgsQueryResultWidget::updateButtons );
 
  706  connect( mQueryWidget->sqlEditor(), &QgsCodeEditorSQL::copyAvailable, mActionCut, &QAction::setEnabled );
 
  707  connect( mQueryWidget->sqlEditor(), &QgsCodeEditorSQL::copyAvailable, mActionCopy, &QAction::setEnabled );
 
  709  connect( mQueryWidget, &QgsQueryResultPanelWidget::createSqlVectorLayer, 
this, &QgsQueryResultWidget::createSqlVectorLayer );
 
  710  connect( mQueryWidget, &QgsQueryResultPanelWidget::firstResultBatchFetched, 
this, &QgsQueryResultWidget::firstResultBatchFetched );
 
  713  setHasChanged( 
false );
 
  716QgsQueryResultWidget::~QgsQueryResultWidget()
 
  718  if ( mHistoryWidget )
 
  720    mPanelStack->closePanel( mHistoryWidget );
 
  721    mHistoryWidget->deleteLater();
 
  727  if ( !options.
sql.isEmpty() )
 
  729    setQuery( options.
sql );
 
  731  mQueryWidget->setSqlVectorLayerOptions( options );
 
  734void QgsQueryResultWidget::setWidgetMode( QueryWidgetMode widgetMode )
 
  736  mQueryWidget->setWidgetMode( widgetMode );
 
  739void QgsQueryResultWidget::executeQuery()
 
  741  mQueryWidget->executeQuery();
 
  744void QgsQueryResultWidget::updateButtons()
 
  746  mQueryWidget->updateButtons();
 
  748  const bool isEmpty = mQueryWidget->sqlEditor()->text().isEmpty();
 
  749  mActionClear->setEnabled( !isEmpty );
 
  750  mActionUndo->setEnabled( mQueryWidget->sqlEditor()->isUndoAvailable() );
 
  751  mActionRedo->setEnabled( mQueryWidget->sqlEditor()->isRedoAvailable() );
 
  754void QgsQueryResultWidget::showError( 
const QString &title, 
const QString &message, 
bool isSqlError )
 
  756  mQueryWidget->showError( title, message, isSqlError );
 
  759void QgsQueryResultWidget::tokensReady( 
const QStringList & )
 
  763void QgsQueryResultWidget::copyResults()
 
  765  mQueryWidget->copyResults();
 
  768void QgsQueryResultWidget::copyResults( 
int fromRow, 
int toRow, 
int fromColumn, 
int toColumn )
 
  770  mQueryWidget->copyResults( fromRow, toRow, fromColumn, toColumn );
 
  773void QgsQueryResultWidget::openQuery()
 
  775  if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() && mHasChangedFileContents )
 
  777    if ( QMessageBox::warning( 
this, tr( 
"Unsaved Changes" ), tr( 
"There are unsaved changes in the query. Continue?" ), QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, QMessageBox::StandardButton::No ) == QMessageBox::StandardButton::No )
 
  781  QString initialDir = settingLastSourceFolder->value();
 
  782  if ( initialDir.isEmpty() )
 
  783    initialDir = QDir::homePath();
 
  785  const QString fileName = QFileDialog::getOpenFileName( 
this, tr( 
"Open Query" ), initialDir, tr( 
"SQL queries (*.sql *.SQL)" ) + QStringLiteral( 
";;" ) + QObject::tr( 
"All files" ) + QStringLiteral( 
" (*.*)" ) );
 
  787  if ( fileName.isEmpty() )
 
  790  QFileInfo fi( fileName );
 
  791  settingLastSourceFolder->setValue( fi.path() );
 
  795  mQueryWidget->codeEditorWidget()->loadFile( fileName );
 
  796  setHasChanged( 
false );
 
  799void QgsQueryResultWidget::saveQuery( 
bool saveAs )
 
  801  if ( mQueryWidget->codeEditorWidget()->filePath().isEmpty() || saveAs )
 
  803    QString selectedFilter;
 
  805    QString initialDir = settingLastSourceFolder->value();
 
  806    if ( initialDir.isEmpty() )
 
  807      initialDir = QDir::homePath();
 
  809    QString newPath = QFileDialog::getSaveFileName(
 
  813      tr( 
"SQL queries (*.sql *.SQL)" ) + QStringLiteral( 
";;" ) + QObject::tr( 
"All files" ) + QStringLiteral( 
" (*.*)" ),
 
  817    if ( !newPath.isEmpty() )
 
  819      QFileInfo fi( newPath );
 
  820      settingLastSourceFolder->setValue( fi.path() );
 
  822      if ( !selectedFilter.contains( QStringLiteral( 
"*.*)" ) ) )
 
  824      mQueryWidget->codeEditorWidget()->save( newPath );
 
  825      setHasChanged( 
false );
 
  828  else if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() )
 
  830    mQueryWidget->codeEditorWidget()->save();
 
  831    setHasChanged( 
false );
 
  837  mQueryWidget->setConnection( connection );
 
  841void QgsQueryResultWidget::setQuery( 
const QString &sql )
 
  843  mQueryWidget->sqlEditor()->setText( sql );
 
  845  mActionUndo->setEnabled( 
false );
 
  846  mActionRedo->setEnabled( 
false );
 
  849bool QgsQueryResultWidget::promptUnsavedChanges()
 
  851  if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() && mHasChangedFileContents )
 
  853    const QMessageBox::StandardButton ret = QMessageBox::question(
 
  857        "There are unsaved changes in this query. Do you want to save those?" 
  859      QMessageBox::StandardButton::Save
 
  860        | QMessageBox::StandardButton::Cancel
 
  861        | QMessageBox::StandardButton::Discard,
 
  862      QMessageBox::StandardButton::Cancel
 
  865    if ( ret == QMessageBox::StandardButton::Save )
 
  870    else if ( ret == QMessageBox::StandardButton::Discard )
 
  886void QgsQueryResultWidget::notify( 
const QString &title, 
const QString &text, 
Qgis::MessageLevel level )
 
  888  mQueryWidget->notify( title, text, level );
 
  892void QgsQueryResultWidget::setHasChanged( 
bool hasChanged )
 
  894  mActionSaveQuery->setEnabled( hasChanged );
 
  895  mHasChangedFileContents = hasChanged;
 
  899void QgsQueryResultWidget::updateDialogTitle()
 
  902  if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() )
 
  904    const QFileInfo fi( mQueryWidget->codeEditorWidget()->filePath() );
 
  905    fileName = fi.fileName();
 
  906    if ( mHasChangedFileContents )
 
  908      fileName.prepend( 
'*' );
 
  912  emit requestDialogTitleUpdate( fileName );
 
  915void QgsQueryResultWidget::populatePresetQueryMenu()
 
  917  mPresetQueryMenu->clear();
 
  919  QMenu *storeQueryMenu = 
new QMenu( tr( 
"Store Current Query" ), mPresetQueryMenu );
 
  920  mPresetQueryMenu->addMenu( storeQueryMenu );
 
  921  QAction *storeInProfileAction = 
new QAction( tr( 
"In User Profile…" ), storeQueryMenu );
 
  922  storeQueryMenu->addAction( storeInProfileAction );
 
  923  storeInProfileAction->setEnabled( !mQueryWidget->sqlEditor()->text().isEmpty() );
 
  924  connect( storeInProfileAction, &QAction::triggered, 
this, [
this] {
 
  927  QAction *storeInProjectAction = 
new QAction( tr( 
"In Current Project…" ), storeQueryMenu );
 
  928  storeQueryMenu->addAction( storeInProjectAction );
 
  929  storeInProjectAction->setEnabled( !mQueryWidget->sqlEditor()->text().isEmpty() );
 
  930  connect( storeInProjectAction, &QAction::triggered, 
this, [
this] {
 
  936  if ( !storedQueries.isEmpty() )
 
  938    QList< QgsStoredQueryManager::QueryDetails > userProfileQueries;
 
  940      return details.backend == Qgis::QueryStorageBackend::LocalProfile;
 
  943    QList< QgsStoredQueryManager::QueryDetails > projectQueries;
 
  945      return details.backend == Qgis::QueryStorageBackend::CurrentProject;
 
  951      QAction *action = 
new QAction( query.name, mPresetQueryMenu );
 
  952      mPresetQueryMenu->addAction( action );
 
  953      connect( action, &QAction::triggered, 
this, [
this, query] {
 
  954        mQueryWidget->sqlEditor()->insertText( query.definition );
 
  957    if ( userProfileQueries.empty() )
 
  959      QAction *action = 
new QAction( tr( 
"No Stored Queries Available" ), mPresetQueryMenu );
 
  960      action->setEnabled( 
false );
 
  961      mPresetQueryMenu->addAction( action );
 
  967      QAction *action = 
new QAction( query.name, mPresetQueryMenu );
 
  968      mPresetQueryMenu->addAction( action );
 
  969      connect( action, &QAction::triggered, 
this, [
this, query] {
 
  970        mQueryWidget->sqlEditor()->insertText( query.definition );
 
  973    if ( projectQueries.empty() )
 
  975      QAction *action = 
new QAction( tr( 
"No Stored Queries Available" ), mPresetQueryMenu );
 
  976      action->setEnabled( 
false );
 
  977      mPresetQueryMenu->addAction( action );
 
  980    mPresetQueryMenu->addSeparator();
 
  982    QMenu *removeQueryMenu = 
new QMenu( tr( 
"Removed Stored Query" ), mPresetQueryMenu );
 
  983    mPresetQueryMenu->addMenu( removeQueryMenu );
 
  987      QAction *action = 
new QAction( tr( 
"%1…" ).arg( query.name ), mPresetQueryMenu );
 
  988      removeQueryMenu->addAction( action );
 
  989      connect( action, &QAction::triggered, 
this, [
this, query] {
 
  990        const QMessageBox::StandardButton res = QMessageBox::question( 
this, tr( 
"Remove Stored Query" ), tr( 
"Are you sure you want to remove the stored query “%1”?" ).arg( query.name ), QMessageBox::Yes | QMessageBox::No, QMessageBox::No );
 
  991        if ( res == QMessageBox::Yes )
 
 1013  dlg.setWindowTitle( tr( 
"Store Query" ) );
 
 1014  dlg.setHintString( tr( 
"Name for the stored query" ) );
 
 1015  dlg.setOverwriteEnabled( 
true );
 
 1016  dlg.setConflictingNameWarning( tr( 
"A stored query with this name already exists, it will be overwritten." ) );
 
 1017  dlg.setShowExistingNamesCompleter( 
true );
 
 1018  if ( dlg.exec() != QDialog::Accepted )
 
 1021  const QString name = dlg.name();
 
 1022  if ( name.isEmpty() )
 
 1033void QgsQueryResultWidget::showHistoryPanel( 
bool show )
 
 1040    mHistoryWidget->setPanelTitle( tr( 
"SQL History" ) );
 
 1041    mPanelStack->showPanel( mHistoryWidget );
 
 1044      Q_UNUSED( connectionUri );
 
 1045      Q_UNUSED( provider );
 
 1047      mQueryWidget->sqlEditor()->setText( sql );
 
 1048      mActionUndo->setEnabled( 
false );
 
 1049      mActionRedo->setEnabled( 
false );
 
 1050      mHistoryWidget->acceptPanel();
 
 1053  else if ( mHistoryWidget )
 
 1055    mPanelStack->closePanel( mHistoryWidget );
 
 1056    mHistoryWidget->deleteLater();
 
 1064void QgsConnectionsApiFetcher::fetchTokens()
 
 1066  if ( mStopFetching )
 
 1068    emit fetchingFinished();
 
 1076    emit fetchingFinished();
 
 1080  if ( !mStopFetching && connection )
 
 1082    mFeedback = std::make_unique<QgsFeedback>();
 
 1083    QStringList schemas;
 
 1088        schemas = connection->
schemas();
 
 1089        emit tokensReady( schemas );
 
 1098      schemas.push_back( QString() ); 
 
 1101    for ( 
const auto &schema : std::as_const( schemas ) )
 
 1103      if ( mStopFetching )
 
 1106        emit fetchingFinished();
 
 1110      QStringList tableNames;
 
 1116          if ( mStopFetching )
 
 1119            emit fetchingFinished();
 
 1122          tableNames.push_back( table.tableName() );
 
 1124        emit tokensReady( tableNames );
 
 1132      for ( 
const auto &table : std::as_const( tableNames ) )
 
 1134        if ( mStopFetching )
 
 1137          emit fetchingFinished();
 
 1141        QStringList fieldNames;
 
 1144          const QgsFields fields( connection->
fields( schema, table, mFeedback.get() ) );
 
 1145          if ( mStopFetching )
 
 1148            emit fetchingFinished();
 
 1152          for ( 
const auto &field : std::as_const( fields ) )
 
 1154            fieldNames.push_back( field.name() );
 
 1155            if ( mStopFetching )
 
 1158              emit fetchingFinished();
 
 1162          emit tokensReady( fieldNames );
 
 1173  emit fetchingFinished();
 
 1176void QgsConnectionsApiFetcher::stopFetching()
 
 1180    mFeedback->cancel();
 
 1183QgsQueryResultItemDelegate::QgsQueryResultItemDelegate( QObject *parent )
 
 1184  : QStyledItemDelegate( parent )
 
 1188QString QgsQueryResultItemDelegate::displayText( 
const QVariant &value, 
const QLocale &locale )
 const 
 1191  QString result { QgsExpressionUtils::toLocalizedString( value ) };
 
 1193  if ( result.length() > 255 )
 
 1195    result.truncate( 255 );
 
 1196    result.append( QStringLiteral( 
"…" ) );
 
 1210  setObjectName( QStringLiteral( 
"QgsQueryResultDialog" ) );
 
 1213  mWidget = 
new QgsQueryResultWidget( 
this, connection );
 
 1214  QVBoxLayout *l = 
new QVBoxLayout();
 
 1215  l->setContentsMargins( 0, 0, 0, 0 );
 
 1216  l->addWidget( mWidget );
 
 1220void QgsQueryResultDialog::closeEvent( QCloseEvent *event )
 
 1222  if ( !mWidget->promptUnsavedChanges() )
 
 1237  : mIdentifierName( identifierName )
 
 1239  setObjectName( QStringLiteral( 
"SQLCommandsDialog" ) );
 
 1243  mWidget = 
new QgsQueryResultWidget( 
nullptr, connection );
 
 1244  setCentralWidget( mWidget );
 
 1246  connect( mWidget, &QgsQueryResultWidget::requestDialogTitleUpdate, 
this, &QgsQueryResultMainWindow::updateWindowTitle );
 
 1248  updateWindowTitle( QString() );
 
 1251void QgsQueryResultMainWindow::closeEvent( QCloseEvent *event )
 
 1253  if ( !mWidget->promptUnsavedChanges() )
 
 1263void QgsQueryResultMainWindow::updateWindowTitle( 
const QString &fileName )
 
 1265  if ( fileName.isEmpty() )
 
 1267    if ( !mIdentifierName.isEmpty() )
 
 1268      setWindowTitle( tr( 
"%1 — Execute SQL" ).arg( mIdentifierName ) );
 
 1270      setWindowTitle( tr( 
"Execute SQL" ) );
 
 1274    if ( !mIdentifierName.isEmpty() )
 
 1275      setWindowTitle( tr( 
"%1 — %2 — Execute SQL" ).arg( fileName, mIdentifierName ) );
 
 1277      setWindowTitle( tr( 
"%1 — Execute SQL" ).arg( fileName ) );
 
MessageLevel
Level for messages This will be used both for message log and message bar in application.
 
@ Warning
Warning message.
 
QueryStorageBackend
Stored query storage backends.
 
@ CurrentProject
Current QGIS project.
 
@ LocalProfile
Local user profile.
 
@ UnstableFeatureIds
SQL layer definition supports disabling select at id.
 
@ SubsetStringFilter
SQL layer definition supports subset string filter.
 
@ PrimaryKeys
SQL layer definition supports primary keys.
 
@ GeometryColumn
SQL layer definition supports geometry column.
 
Provides common functionality for database based connections.
 
virtual QList< QgsAbstractDatabaseProviderConnection::TableProperty > tables(const QString &schema=QString(), const QgsAbstractDatabaseProviderConnection::TableFlags &flags=QgsAbstractDatabaseProviderConnection::TableFlags(), QgsFeedback *feedback=nullptr) const
Returns information on the tables in the given schema.
 
QFlags< TableFlag > TableFlags
 
@ SqlLayers
Can create vector layers from SQL SELECT queries.
 
@ Schemas
Can list schemas (if not set, the connection does not support schemas)
 
virtual Qgis::SqlLayerDefinitionCapabilities sqlLayerDefinitionCapabilities()
Returns SQL layer definition capabilities (Filters, GeometryColumn, PrimaryKeys).
 
virtual QMultiMap< Qgis::SqlKeywordCategory, QStringList > sqlDictionary()
Returns a dictionary of SQL keywords supported by the provider.
 
virtual QStringList schemas() const
Returns information about the existing schemas.
 
Capabilities capabilities() const
Returns connection capabilities.
 
virtual QgsFields fields(const QString &schema, const QString &table, QgsFeedback *feedback=nullptr) const
Returns the fields of a table and schema.
 
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
 
A SQL editor based on QScintilla2.
 
void collapsedStateChanged(bool collapsed)
Signal emitted when groupbox collapsed/expanded state is changed, and when first shown.
 
Custom QgsHistoryWidget for use with the database query provider.
 
void sqlTriggered(const QString &connectionUri, const QString &provider, const QString &sql)
Emitted when the user has triggered a previously executed SQL statement in the widget.
 
void canceled()
Internal routines can connect to this signal if they use event loop.
 
Container of fields for a vector layer.
 
static QString ensureFileNameHasExtension(const QString &fileName, const QStringList &extensions)
Ensures that a fileName ends with an extension from the provided list of extensions.
 
static void enableAutoGeometryRestore(QWidget *widget, const QString &key=QString())
Register the widget to allow its position to be automatically saved and restored when open and closed...
 
static QgsHistoryProviderRegistry * historyProviderRegistry()
Returns the global history provider registry, used for tracking history providers.
 
static QgsStoredQueryManager * storedQueryManager()
Returns the global stored SQL query manager.
 
long long addEntry(const QString &providerId, const QVariantMap &entry, bool &ok, QgsHistoryProviderRegistry::HistoryEntryOptions options=QgsHistoryProviderRegistry::HistoryEntryOptions())
Adds an entry to the history logs.
 
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
 
static QgsProject * instance()
Returns the QgsProject singleton instance.
 
void setDirty(bool b=true)
Flag the project as dirty (modified).
 
Custom exception class for provider connection related exceptions.
 
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
 
QgsProviderMetadata * providerMetadata(const QString &providerKey) const
Returns metadata of the provider or nullptr if not found.
 
Query Builder for layers.
 
Stores settings for use within QGIS.
 
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
 
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
 
Contains details about a stored query.
 
QList< QgsStoredQueryManager::QueryDetails > allQueries() const
Returns details of all queries stored in the manager.
 
QStringList allQueryNames(Qgis::QueryStorageBackend backend=Qgis::QueryStorageBackend::LocalProfile) const
Returns a list of the names of all stored queries for the specified backend.
 
void removeQuery(const QString &name, Qgis::QueryStorageBackend backend=Qgis::QueryStorageBackend::LocalProfile)
Removes the stored query with matching name.
 
void storeQuery(const QString &name, const QString &query, Qgis::QueryStorageBackend backend=Qgis::QueryStorageBackend::LocalProfile)
Saves a query to the manager.
 
Temporarily sets a cursor override for the QApplication for the lifetime of the object.
 
@ UnknownCount
Provider returned an unknown feature count.
 
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
 
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
 
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
 
The QueryResult class represents the result of a query executed by execSql()
 
The SqlVectorLayerOptions stores all information required to create a SQL (query) layer.
 
QString sql
The SQL expression that defines the SQL (query) layer.
 
QStringList primaryKeyColumns
List of primary key column names.
 
QString filter
Additional subset string (provider-side filter), not all data providers support this feature: check s...
 
QString layerName
Optional name for the new layer.
 
bool disableSelectAtId
If SelectAtId is disabled (default is false), not all data providers support this feature: check supp...
 
QString geometryColumn
Name of the geometry column.
 
The TableProperty class represents a database table or view.