40#include <QDialogButtonBox>
42#include <QInputDialog>
47#include <QtConcurrentRun>
49#include "moc_qgsqueryresultwidget.cpp"
51using namespace Qt::StringLiterals;
71 splitter->setCollapsible( 0,
false );
72 splitter->setCollapsible( 1,
false );
74 splitter->restoreState( settings.
value( u
"Windows/QueryResult/SplitState"_s ).toByteArray() );
76 connect( splitter, &QSplitter::splitterMoved,
this, [
this] {
78 settings.
setValue( u
"Windows/QueryResult/SplitState"_s, splitter->saveState() );
82 mainLayout->setSpacing( 6 );
83 progressLayout->setSpacing( 6 );
85 mResultsContainer->hide();
86 mQueryResultsTableView->hide();
87 mQueryResultsTableView->setItemDelegate(
new QgsQueryResultItemDelegate( mQueryResultsTableView ) );
88 mQueryResultsTableView->setContextMenuPolicy( Qt::CustomContextMenu );
89 connect( mQueryResultsTableView, &QTableView::customContextMenuRequested,
this, &QgsQueryResultPanelWidget::showCellContextMenu );
95 QVBoxLayout *vl =
new QVBoxLayout();
96 vl->setContentsMargins( 0, 0, 0, 0 );
97 vl->addWidget( mCodeEditorWidget );
98 mSqlEditorContainer->setLayout( vl );
100 connect( mExecuteButton, &QPushButton::pressed,
this, &QgsQueryResultPanelWidget::executeQuery );
102 connect( mLoadLayerPushButton, &QPushButton::pressed,
this, [
this] {
110 const bool res = mConnection->validateSqlVectorLayer( options, message );
113 mMessageBar->pushCritical( QString(), message );
117 emit createSqlVectorLayer( mConnection->providerKey(), mConnection->uri(), options );
122 mMessageBar->pushCritical( tr(
"Error validating query" ), e.
what() );
126 connect( mSqlEditor, &QgsCodeEditorSQL::textChanged,
this, &QgsQueryResultPanelWidget::updateButtons );
128 connect( mSqlEditor, &QgsCodeEditorSQL::selectionChanged,
this, [
this] { mExecuteButton->setText( mSqlEditor->selectedText().isEmpty() ? tr(
"Execute" ) : tr(
"Execute Selection" ) ); } );
129 connect( mFilterToolButton, &QToolButton::pressed,
this, [
this] {
134 std::unique_ptr<QgsVectorLayer> vlayer { mConnection->createSqlVectorLayer( sqlVectorLayerOptions() ) };
136 if ( builder.exec() == QDialog::Accepted )
138 mFilterLineEdit->setText( builder.
sql() );
143 mMessageBar->pushCritical( tr(
"Error opening filter dialog" ), tr(
"There was an error while preparing SQL filter dialog: %1." ).arg( ex.
what() ) );
149 mStatusLabel->hide();
150 mSqlErrorText->hide();
152 mLoadAsNewLayerGroupBox->setCollapsed(
true );
159 mPkColumnsCheckBox->setVisible( showPkConfig );
160 mPkColumnsComboBox->setVisible( showPkConfig );
163 mGeometryColumnCheckBox->setVisible( showGeometryColumnConfig );
164 mGeometryColumnComboBox->setVisible( showGeometryColumnConfig );
167 mFilterLabel->setVisible( showFilterConfig );
168 mFilterToolButton->setVisible( showFilterConfig );
169 mFilterLineEdit->setVisible( showFilterConfig );
172 mAvoidSelectingAsFeatureIdCheckBox->setVisible( showDisableSelectAtId );
176 QShortcut *copySelection =
new QShortcut( QKeySequence::Copy, mQueryResultsTableView );
177 connect( copySelection, &QShortcut::activated,
this, &QgsQueryResultPanelWidget::copySelection );
179 setConnection( connection );
182QgsQueryResultPanelWidget::~QgsQueryResultPanelWidget()
185 cancelRunningQuery();
195 return mCodeEditorWidget;
200 mSqlVectorLayerOptions = options;
201 if ( !options.
sql.isEmpty() )
203 setQuery( options.
sql );
207 mPkColumnsComboBox->setCheckedItems( {} );
212 mGeometryColumnCheckBox->setChecked( !options.
geometryColumn.isEmpty() );
213 mGeometryColumnComboBox->clear();
216 mGeometryColumnComboBox->setCurrentText( options.
geometryColumn );
218 mFilterLineEdit->setText( options.
filter );
219 mLayerNameLineEdit->setText( options.
layerName );
222void QgsQueryResultPanelWidget::setWidgetMode( QgsQueryResultWidget::QueryWidgetMode widgetMode )
224 mQueryWidgetMode = widgetMode;
225 switch ( widgetMode )
227 case QgsQueryResultWidget::QueryWidgetMode::SqlQueryMode:
228 mLoadAsNewLayerGroupBox->setTitle( tr(
"Load as New Layer" ) );
229 mLoadLayerPushButton->setText( tr(
"Load Layer" ) );
230 mLoadAsNewLayerGroupBox->setCollapsed(
true );
232 case QgsQueryResultWidget::QueryWidgetMode::QueryLayerUpdateMode:
233 mLoadAsNewLayerGroupBox->setTitle( tr(
"Update Query Layer" ) );
234 mLoadLayerPushButton->setText( tr(
"Update Layer" ) );
235 mLoadAsNewLayerGroupBox->setCollapsed(
false );
240void QgsQueryResultPanelWidget::executeQuery()
242 mQueryResultsTableView->hide();
243 mSqlErrorText->hide();
244 mResultsContainer->hide();
245 mFirstRowFetched =
false;
247 cancelRunningQuery();
250 const QString sql { mSqlEditor->selectedText().isEmpty() ? mSqlEditor->text() : mSqlEditor->selectedText() };
257 { u
"provider"_s, mConnection->providerKey() },
258 { u
"connection"_s, mConnection->uri() },
263 mWasCanceled =
false;
264 mFeedback = std::make_unique<QgsFeedback>();
265 mStopButton->setEnabled(
true );
266 mStatusLabel->show();
267 mStatusLabel->setText( tr(
"Executing query…" ) );
268 mProgressBar->show();
269 mProgressBar->setRange( 0, 0 );
270 mSqlErrorMessage.clear();
272 connect( mStopButton, &QPushButton::pressed, mFeedback.get(), [
this] {
273 mStatusLabel->setText( tr(
"Stopped" ) );
275 mProgressBar->hide();
280 connect( &mQueryResultWatcher, &QFutureWatcher<QgsAbstractDatabaseProviderConnection::QueryResult>::finished,
this, &QgsQueryResultPanelWidget::startFetching, Qt::ConnectionType::UniqueConnection );
282 QFuture<QgsAbstractDatabaseProviderConnection::QueryResult> future = QtConcurrent::run( [
this, sql]() -> QgsAbstractDatabaseProviderConnection::QueryResult {
285 return mConnection->execSql( sql, mFeedback.get() );
287 catch ( QgsProviderConnectionException &ex )
289 mSqlErrorMessage = ex.
what();
290 return QgsAbstractDatabaseProviderConnection::QueryResult();
293 mQueryResultWatcher.setFuture( future );
297 showError( tr(
"Connection error" ), tr(
"Cannot execute query: connection to the database is not available." ) );
301void QgsQueryResultPanelWidget::updateButtons()
303 mFilterLineEdit->setEnabled( mFirstRowFetched );
304 mFilterToolButton->setEnabled( mFirstRowFetched );
305 const bool isEmpty = mSqlEditor->text().isEmpty();
306 mExecuteButton->setEnabled( !isEmpty );
308 mLoadAsNewLayerGroupBox->setEnabled( mSqlErrorMessage.isEmpty() && mFirstRowFetched );
311void QgsQueryResultPanelWidget::showCellContextMenu( QPoint point )
313 const QModelIndex modelIndex = mQueryResultsTableView->indexAt( point );
314 if ( modelIndex.isValid() )
316 QMenu *menu =
new QMenu();
317 menu->setAttribute( Qt::WA_DeleteOnClose );
319 menu->addAction(
QgsApplication::getThemeIcon(
"mActionEditCopy.svg" ), tr(
"Copy" ),
this, [
this] { copySelection(); }, QKeySequence::Copy );
321 menu->exec( mQueryResultsTableView->viewport()->mapToGlobal( point ) );
325void QgsQueryResultPanelWidget::copySelection()
327 const QModelIndexList selection = mQueryResultsTableView->selectionModel()->selectedIndexes();
328 if ( selection.empty() )
335 for (
const QModelIndex &index : selection )
337 if ( minRow == -1 || index.row() < minRow )
338 minRow = index.row();
339 if ( maxRow == -1 || index.row() > maxRow )
340 maxRow = index.row();
341 if ( minCol == -1 || index.column() < minCol )
342 minCol = index.column();
343 if ( maxCol == -1 || index.column() > maxCol )
344 maxCol = index.column();
347 if ( minRow == maxRow && minCol == maxCol )
350 const QString text = mModel->data( selection.at( 0 ), Qt::DisplayRole ).toString();
351 QApplication::clipboard()->setText( text );
355 copyResults( minRow, maxRow, minCol, maxCol );
359void QgsQueryResultPanelWidget::updateSqlLayerColumns()
364 mFilterToolButton->setEnabled(
true );
365 mFilterLineEdit->setEnabled(
true );
366 mPkColumnsComboBox->clear();
367 mGeometryColumnComboBox->clear();
368 const bool hasPkInformation { !mSqlVectorLayerOptions.primaryKeyColumns.isEmpty() };
369 const bool hasGeomColInformation { !mSqlVectorLayerOptions.geometryColumn.isEmpty() };
370 static const QStringList geomColCandidates { u
"geom"_s, u
"geometry"_s, u
"the_geom"_s };
371 const QStringList constCols { mModel->columns() };
372 for (
const QString &
c : constCols )
374 const bool pkCheckedState = hasPkInformation ? mSqlVectorLayerOptions.primaryKeyColumns.contains(
c ) :
c.contains( u
"id"_s, Qt::CaseSensitivity::CaseInsensitive );
376 mPkColumnsComboBox->addItemWithCheckState(
c, pkCheckedState && mPkColumnsComboBox->checkedItems().isEmpty() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked );
377 mGeometryColumnComboBox->addItem(
c );
378 if ( !hasGeomColInformation && geomColCandidates.contains(
c, Qt::CaseSensitivity::CaseInsensitive ) )
380 mGeometryColumnComboBox->setCurrentText(
c );
383 mPkColumnsCheckBox->setChecked( hasPkInformation );
384 mGeometryColumnCheckBox->setChecked( hasGeomColInformation );
385 if ( hasGeomColInformation )
387 mGeometryColumnComboBox->setCurrentText( mSqlVectorLayerOptions.geometryColumn );
391void QgsQueryResultPanelWidget::cancelRunningQuery()
400 if ( mQueryResultWatcher.isRunning() )
402 mQueryResultWatcher.waitForFinished();
406void QgsQueryResultPanelWidget::cancelApiFetcher()
410 mApiFetcher->stopFetching();
415void QgsQueryResultPanelWidget::startFetching()
419 if ( !mSqlErrorMessage.isEmpty() )
421 showError( tr(
"SQL error" ), mSqlErrorMessage,
true );
427 mStatusLabel->setText(
428 u
"Query executed successfully (%1 rows, %2 ms)"_s.arg( QLocale().toString( mQueryResultWatcher.result().rowCount() ), QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() ) )
433 mStatusLabel->setText( u
"Query executed successfully (%1 s)"_s.arg( QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() ) ) );
435 mProgressBar->hide();
436 mModel = std::make_unique<QgsQueryResultModel>( mQueryResultWatcher.result() );
442 connect( mModel.get(), &QgsQueryResultModel::fetchMoreRows,
this, [
this](
long long maxRows ) {
443 mFetchedRowsBatchCount = 0;
444 mProgressBar->setRange( 0, maxRows );
445 mProgressBar->show();
448 connect( mModel.get(), &QgsQueryResultModel::rowsInserted,
this, [
this](
const QModelIndex &,
int first,
int last ) {
449 if ( !mFirstRowFetched )
451 emit firstResultBatchFetched();
452 mFirstRowFetched = true;
453 mQueryResultsTableView->show();
454 mResultsContainer->show();
456 updateSqlLayerColumns();
457 mActualRowCount = mModel->queryResult().rowCount();
459 mStatusLabel->setText( tr(
"Fetched rows: %1/%2 %3 %4 ms" )
461 QLocale().toString( mModel->rowCount( mModel->index( -1, -1 ) ) ),
462 mActualRowCount != -1 ? QLocale().toString( mActualRowCount ) : tr(
"unknown" ),
463 mWasCanceled ? tr(
"(stopped)" ) : QString(),
464 QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() )
466 mFetchedRowsBatchCount += last - first + 1;
467 mProgressBar->setValue( mFetchedRowsBatchCount );
470 mQueryResultsTableView->setModel( mModel.get() );
471 mQueryResultsTableView->show();
472 mResultsContainer->show();
474 connect( mModel.get(), &QgsQueryResultModel::fetchingComplete, mStopButton, [
this] {
476 const QgsHistoryEntry currentHistoryEntry = QgsGui::historyProviderRegistry()->entry( mCurrentHistoryEntryId, ok );
477 QVariantMap entryDetails = currentHistoryEntry.entry;
478 entryDetails.insert( u
"rows"_s, mActualRowCount );
479 entryDetails.insert( u
"time"_s, mQueryResultWatcher.result().queryExecutionTime() );
481 QgsGui::historyProviderRegistry()->updateEntry( mCurrentHistoryEntryId, entryDetails );
482 mProgressBar->hide();
483 mStopButton->setEnabled( false );
489 mStatusLabel->setText( tr(
"SQL command aborted" ) );
490 mProgressBar->hide();
494void QgsQueryResultPanelWidget::showError(
const QString &title,
const QString &message,
bool isSqlError )
496 mStatusLabel->show();
497 mStatusLabel->setText( tr(
"An error occurred while executing the query" ) );
498 mProgressBar->hide();
499 mQueryResultsTableView->hide();
502 mSqlErrorText->show();
503 mSqlErrorText->setText( message );
504 mResultsContainer->show();
508 mMessageBar->pushCritical( title, message );
509 mResultsContainer->hide();
513void QgsQueryResultPanelWidget::tokensReady(
const QStringList &tokens )
515 mSqlEditor->setExtraKeywords( mSqlEditor->extraKeywords() + tokens );
516 mSqlErrorText->setExtraKeywords( mSqlErrorText->extraKeywords() + tokens );
519void QgsQueryResultPanelWidget::copyResults()
521 const int rowCount = mModel->rowCount( QModelIndex() );
522 const int columnCount = mModel->columnCount( QModelIndex() );
523 copyResults( 0, rowCount - 1, 0, columnCount - 1 );
526void QgsQueryResultPanelWidget::copyResults(
int fromRow,
int toRow,
int fromColumn,
int toColumn )
528 QStringList rowStrings;
529 QStringList columnStrings;
531 const int rowCount = mModel->rowCount( QModelIndex() );
532 const int columnCount = mModel->columnCount( QModelIndex() );
534 toRow = std::min( toRow, rowCount - 1 );
535 toColumn = std::min( toColumn, columnCount - 1 );
537 rowStrings.reserve( toRow - fromRow );
540 for (
int col = fromColumn; col <= toColumn; col++ )
542 columnStrings += mModel->headerData( col, Qt::Horizontal, Qt::DisplayRole ).toString();
544 rowStrings += columnStrings.join( QLatin1Char(
'\t' ) );
545 columnStrings.clear();
547 for (
int row = fromRow; row <= toRow; row++ )
549 for (
int col = fromColumn; col <= toColumn; col++ )
551 columnStrings += mModel->data( mModel->index( row, col ), Qt::DisplayRole ).toString();
553 rowStrings += columnStrings.join( QLatin1Char(
'\t' ) );
554 columnStrings.clear();
557 if ( !rowStrings.isEmpty() )
559 const QString text = rowStrings.join( QLatin1Char(
'\n' ) );
560 QString html = u
"<!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>"_s
562 html.replace(
"\t"_L1,
"</td><td>"_L1 ).replace(
"\n"_L1,
"</td></tr><tr><td>"_L1 );
564 QMimeData *mdata =
new QMimeData();
565 mdata->setData( u
"text/html"_s, html.toUtf8() );
566 if ( !text.isEmpty() )
568 mdata->setText( text );
571 if ( QApplication::clipboard()->supportsSelection() )
573 QApplication::clipboard()->setMimeData( mdata, QClipboard::Selection );
575 QApplication::clipboard()->setMimeData( mdata, QClipboard::Clipboard );
581 const thread_local QRegularExpression rx( u
";\\s*$"_s );
582 mSqlVectorLayerOptions.sql = mSqlEditor->text().replace( rx, QString() );
583 mSqlVectorLayerOptions.filter = mFilterLineEdit->text();
584 mSqlVectorLayerOptions.primaryKeyColumns = mPkColumnsComboBox->checkedItems();
585 mSqlVectorLayerOptions.geometryColumn = mGeometryColumnComboBox->currentText();
586 mSqlVectorLayerOptions.layerName = mLayerNameLineEdit->text();
587 mSqlVectorLayerOptions.disableSelectAtId = mAvoidSelectingAsFeatureIdCheckBox->isChecked();
588 QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions options { mSqlVectorLayerOptions };
590 if ( !mPkColumnsCheckBox->isChecked() )
594 if ( !mGeometryColumnCheckBox->isChecked() )
603 mConnection.reset( connection );
610 const QMultiMap<Qgis::SqlKeywordCategory, QStringList> keywordsDict { connection->
sqlDictionary() };
611 QStringList keywords;
612 for (
auto it = keywordsDict.constBegin(); it != keywordsDict.constEnd(); it++ )
614 keywords.append( it.value() );
618 mSqlEditor->setExtraKeywords( keywords );
619 mSqlErrorText->setExtraKeywords( keywords );
622 QThread *apiFetcherWorkerThread =
new QThread();
623 QgsConnectionsApiFetcher *apiFetcher =
new QgsConnectionsApiFetcher( mConnection->uri(), mConnection->providerKey() );
624 apiFetcher->moveToThread( apiFetcherWorkerThread );
625 connect( apiFetcherWorkerThread, &QThread::started, apiFetcher, &QgsConnectionsApiFetcher::fetchTokens );
626 connect( apiFetcher, &QgsConnectionsApiFetcher::tokensReady,
this, &QgsQueryResultPanelWidget::tokensReady );
627 connect( apiFetcher, &QgsConnectionsApiFetcher::fetchingFinished, apiFetcherWorkerThread, [apiFetcher, apiFetcherWorkerThread] {
628 apiFetcherWorkerThread->quit();
629 apiFetcherWorkerThread->wait();
630 apiFetcherWorkerThread->deleteLater();
631 apiFetcher->deleteLater();
634 mApiFetcher = apiFetcher;
635 apiFetcherWorkerThread->start();
641void QgsQueryResultPanelWidget::setQuery(
const QString &sql )
643 mSqlEditor->setText( sql );
646void QgsQueryResultPanelWidget::notify(
const QString &title,
const QString &text,
Qgis::MessageLevel level )
648 mMessageBar->pushMessage( title, text, level );
654 QgsNewNameDialog dlg( QString(), QString(), QStringList(), existingQueryNames );
655 dlg.setWindowTitle( tr(
"Store Query" ) );
656 dlg.setHintString( tr(
"Name for the stored query" ) );
657 dlg.setOverwriteEnabled(
true );
658 dlg.setConflictingNameWarning( tr(
"A stored query with this name already exists, it will be overwritten." ) );
659 dlg.setShowExistingNamesCompleter(
true );
660 if ( dlg.exec() != QDialog::Accepted )
663 const QString name = dlg.name();
664 if ( name.isEmpty() )
685 mQueryWidget =
new QgsQueryResultPanelWidget(
nullptr, connection );
686 mPanelStack->setMainPanel( mQueryWidget );
688 mPresetQueryMenu =
new QMenu(
this );
689 connect( mPresetQueryMenu, &QMenu::aboutToShow,
this, &QgsQueryResultWidget::populatePresetQueryMenu );
691 QToolButton *presetQueryButton =
new QToolButton();
692 presetQueryButton->setMenu( mPresetQueryMenu );
694 presetQueryButton->setPopupMode( QToolButton::InstantPopup );
695 mToolBar->addWidget( presetQueryButton );
697 connect( mActionOpenQuery, &QAction::triggered,
this, &QgsQueryResultWidget::openQuery );
698 connect( mActionSaveQuery, &QAction::triggered,
this, [
this] { saveQuery(
false ); } );
699 connect( mActionSaveQueryAs, &QAction::triggered,
this, [
this] { saveQuery(
true ); } );
701 connect( mActionCut, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::cut );
702 connect( mActionCopy, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::copy );
703 connect( mActionPaste, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::paste );
704 connect( mActionUndo, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::undo );
705 connect( mActionRedo, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::redo );
706 mActionUndo->setEnabled(
false );
707 mActionRedo->setEnabled(
false );
711 connect( mQueryWidget->sqlEditor(), &QgsCodeEditor::modificationChanged,
this, &QgsQueryResultWidget::setHasChanged );
713 connect( mActionShowHistory, &QAction::toggled,
this, &QgsQueryResultWidget::showHistoryPanel );
715 connect( mActionClear, &QAction::triggered,
this, [
this] {
717 mQueryWidget->sqlEditor()->SendScintilla( QsciScintilla::SCI_SETTEXT,
"" );
720 connect( mQueryWidget->sqlEditor(), &QgsCodeEditorSQL::textChanged,
this, &QgsQueryResultWidget::updateButtons );
722 connect( mQueryWidget->sqlEditor(), &QgsCodeEditorSQL::copyAvailable, mActionCut, &QAction::setEnabled );
723 connect( mQueryWidget->sqlEditor(), &QgsCodeEditorSQL::copyAvailable, mActionCopy, &QAction::setEnabled );
725 connect( mQueryWidget, &QgsQueryResultPanelWidget::createSqlVectorLayer,
this, &QgsQueryResultWidget::createSqlVectorLayer );
726 connect( mQueryWidget, &QgsQueryResultPanelWidget::firstResultBatchFetched,
this, &QgsQueryResultWidget::firstResultBatchFetched );
729 setHasChanged(
false );
732QgsQueryResultWidget::~QgsQueryResultWidget()
734 if ( mHistoryWidget )
736 mPanelStack->closePanel( mHistoryWidget );
737 mHistoryWidget->deleteLater();
743 if ( !options.
sql.isEmpty() )
745 setQuery( options.
sql );
747 mQueryWidget->setSqlVectorLayerOptions( options );
750void QgsQueryResultWidget::setWidgetMode( QueryWidgetMode widgetMode )
752 mQueryWidget->setWidgetMode( widgetMode );
755void QgsQueryResultWidget::executeQuery()
757 mQueryWidget->executeQuery();
760void QgsQueryResultWidget::updateButtons()
762 mQueryWidget->updateButtons();
764 const bool isEmpty = mQueryWidget->sqlEditor()->text().isEmpty();
765 mActionClear->setEnabled( !isEmpty );
766 mActionUndo->setEnabled( mQueryWidget->sqlEditor()->isUndoAvailable() );
767 mActionRedo->setEnabled( mQueryWidget->sqlEditor()->isRedoAvailable() );
770void QgsQueryResultWidget::showError(
const QString &title,
const QString &message,
bool isSqlError )
772 mQueryWidget->showError( title, message, isSqlError );
775void QgsQueryResultWidget::tokensReady(
const QStringList & )
778void QgsQueryResultWidget::copyResults()
780 mQueryWidget->copyResults();
783void QgsQueryResultWidget::copyResults(
int fromRow,
int toRow,
int fromColumn,
int toColumn )
785 mQueryWidget->copyResults( fromRow, toRow, fromColumn, toColumn );
788void QgsQueryResultWidget::openQuery()
790 if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() && mHasChangedFileContents )
792 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 )
793 == QMessageBox::StandardButton::No )
797 QString initialDir = settingLastSourceFolder->value();
798 if ( initialDir.isEmpty() )
799 initialDir = QDir::homePath();
801 const QString fileName = QFileDialog::getOpenFileName(
this, tr(
"Open Query" ), initialDir, tr(
"SQL queries (*.sql *.SQL)" ) + u
";;"_s + QObject::tr(
"All files" ) + u
" (*.*)"_s );
803 if ( fileName.isEmpty() )
806 QFileInfo fi( fileName );
807 settingLastSourceFolder->setValue( fi.path() );
809 QgsTemporaryCursorOverride cursor( Qt::CursorShape::WaitCursor );
811 mQueryWidget->codeEditorWidget()->loadFile( fileName );
812 setHasChanged(
false );
815void QgsQueryResultWidget::saveQuery(
bool saveAs )
817 if ( mQueryWidget->codeEditorWidget()->filePath().isEmpty() || saveAs )
819 QString selectedFilter;
821 QString initialDir = settingLastSourceFolder->value();
822 if ( initialDir.isEmpty() )
823 initialDir = QDir::homePath();
825 QString newPath = QFileDialog::getSaveFileName(
this, tr(
"Save Query" ), initialDir, tr(
"SQL queries (*.sql *.SQL)" ) + u
";;"_s + QObject::tr(
"All files" ) + u
" (*.*)"_s, &selectedFilter );
827 if ( !newPath.isEmpty() )
829 QFileInfo fi( newPath );
830 settingLastSourceFolder->setValue( fi.path() );
832 if ( !selectedFilter.contains( u
"*.*)"_s ) )
834 mQueryWidget->codeEditorWidget()->save( newPath );
835 setHasChanged(
false );
838 else if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() )
840 mQueryWidget->codeEditorWidget()->save();
841 setHasChanged(
false );
847 mQueryWidget->setConnection( connection );
851void QgsQueryResultWidget::setQuery(
const QString &sql )
853 mQueryWidget->sqlEditor()->setText( sql );
855 mActionUndo->setEnabled(
false );
856 mActionRedo->setEnabled(
false );
859bool QgsQueryResultWidget::promptUnsavedChanges()
861 if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() && mHasChangedFileContents )
863 const QMessageBox::StandardButton ret = QMessageBox::question(
866 tr(
"There are unsaved changes in this query. Do you want to save those?" ),
867 QMessageBox::StandardButton::Save | QMessageBox::StandardButton::Cancel | QMessageBox::StandardButton::Discard,
868 QMessageBox::StandardButton::Cancel
871 if ( ret == QMessageBox::StandardButton::Save )
876 else if ( ret == QMessageBox::StandardButton::Discard )
892void QgsQueryResultWidget::notify(
const QString &title,
const QString &text,
Qgis::MessageLevel level )
894 mQueryWidget->notify( title, text, level );
898void QgsQueryResultWidget::setHasChanged(
bool hasChanged )
900 mActionSaveQuery->setEnabled( hasChanged );
901 mHasChangedFileContents = hasChanged;
905void QgsQueryResultWidget::updateDialogTitle()
908 if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() )
910 const QFileInfo fi( mQueryWidget->codeEditorWidget()->filePath() );
911 fileName = fi.fileName();
912 if ( mHasChangedFileContents )
914 fileName.prepend(
'*' );
918 emit requestDialogTitleUpdate( fileName );
921void QgsQueryResultWidget::populatePresetQueryMenu()
923 mPresetQueryMenu->clear();
925 QMenu *storeQueryMenu =
new QMenu( tr(
"Store Current Query" ), mPresetQueryMenu );
926 mPresetQueryMenu->addMenu( storeQueryMenu );
927 QAction *storeInProfileAction =
new QAction( tr(
"In User Profile…" ), storeQueryMenu );
928 storeQueryMenu->addAction( storeInProfileAction );
929 storeInProfileAction->setEnabled( !mQueryWidget->sqlEditor()->text().isEmpty() );
931 QAction *storeInProjectAction =
new QAction( tr(
"In Current Project…" ), storeQueryMenu );
932 storeQueryMenu->addAction( storeInProjectAction );
933 storeInProjectAction->setEnabled( !mQueryWidget->sqlEditor()->text().isEmpty() );
938 if ( !storedQueries.isEmpty() )
940 QList< QgsStoredQueryManager::QueryDetails > userProfileQueries;
941 std::copy_if( storedQueries.begin(), storedQueries.end(), std::back_inserter( userProfileQueries ), [](
const QgsStoredQueryManager::QueryDetails &details ) {
942 return details.backend == Qgis::QueryStorageBackend::LocalProfile;
945 QList< QgsStoredQueryManager::QueryDetails > projectQueries;
946 std::copy_if( storedQueries.begin(), storedQueries.end(), std::back_inserter( projectQueries ), [](
const QgsStoredQueryManager::QueryDetails &details ) {
947 return details.backend == Qgis::QueryStorageBackend::CurrentProject;
951 for (
const QgsStoredQueryManager::QueryDetails &query : std::as_const( userProfileQueries ) )
953 QAction *action =
new QAction( query.name, mPresetQueryMenu );
954 mPresetQueryMenu->addAction( action );
955 connect( action, &QAction::triggered,
this, [
this, query] { 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 );
965 for (
const QgsStoredQueryManager::QueryDetails &query : std::as_const( projectQueries ) )
967 QAction *action =
new QAction( query.name, mPresetQueryMenu );
968 mPresetQueryMenu->addAction( action );
969 connect( action, &QAction::triggered,
this, [
this, query] { mQueryWidget->sqlEditor()->insertText( query.definition ); } );
971 if ( projectQueries.empty() )
973 QAction *action =
new QAction( tr(
"No Stored Queries Available" ), mPresetQueryMenu );
974 action->setEnabled(
false );
975 mPresetQueryMenu->addAction( action );
978 mPresetQueryMenu->addSeparator();
980 QMenu *removeQueryMenu =
new QMenu( tr(
"Removed Stored Query" ), mPresetQueryMenu );
981 mPresetQueryMenu->addMenu( removeQueryMenu );
983 for (
const QgsStoredQueryManager::QueryDetails &query : storedQueries )
985 QAction *action =
new QAction( tr(
"%1…" ).arg( query.name ), mPresetQueryMenu );
986 removeQueryMenu->addAction( action );
987 connect( action, &QAction::triggered,
this, [
this, query] {
988 const QMessageBox::StandardButton res
989 = 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 );
990 if ( res == QMessageBox::Yes )
1006 QgsNewNameDialog dlg( QString(), QString(), QStringList(), existingQueryNames );
1007 dlg.setWindowTitle( tr(
"Store Query" ) );
1008 dlg.setHintString( tr(
"Name for the stored query" ) );
1009 dlg.setOverwriteEnabled(
true );
1010 dlg.setConflictingNameWarning( tr(
"A stored query with this name already exists, it will be overwritten." ) );
1011 dlg.setShowExistingNamesCompleter(
true );
1012 if ( dlg.exec() != QDialog::Accepted )
1015 const QString name = dlg.name();
1016 if ( name.isEmpty() )
1027void QgsQueryResultWidget::showHistoryPanel(
bool show )
1031 mHistoryWidget =
new QgsDatabaseQueryHistoryWidget();
1032 mHistoryWidget->setPanelTitle( tr(
"SQL History" ) );
1033 mPanelStack->showPanel( mHistoryWidget );
1036 Q_UNUSED( connectionUri );
1037 Q_UNUSED( provider );
1039 mQueryWidget->sqlEditor()->setText( sql );
1040 mActionUndo->setEnabled(
false );
1041 mActionRedo->setEnabled(
false );
1042 mHistoryWidget->acceptPanel();
1045 else if ( mHistoryWidget )
1047 mPanelStack->closePanel( mHistoryWidget );
1048 mHistoryWidget->deleteLater();
1055void QgsConnectionsApiFetcher::fetchTokens()
1057 if ( mStopFetching )
1059 emit fetchingFinished();
1067 emit fetchingFinished();
1071 if ( !mStopFetching && connection )
1073 mFeedback = std::make_unique<QgsFeedback>();
1074 QStringList schemas;
1079 schemas = connection->
schemas();
1080 emit tokensReady( schemas );
1089 schemas.push_back( QString() );
1092 for (
const auto &schema : std::as_const( schemas ) )
1094 if ( mStopFetching )
1097 emit fetchingFinished();
1101 QStringList tableNames;
1107 if ( mStopFetching )
1110 emit fetchingFinished();
1113 tableNames.push_back( table.tableName() );
1115 emit tokensReady( tableNames );
1123 for (
const auto &table : std::as_const( tableNames ) )
1125 if ( mStopFetching )
1128 emit fetchingFinished();
1132 QStringList fieldNames;
1135 const QgsFields fields( connection->
fields( schema, table, mFeedback.get() ) );
1136 if ( mStopFetching )
1139 emit fetchingFinished();
1143 for (
const auto &field : std::as_const( fields ) )
1145 fieldNames.push_back( field.name() );
1146 if ( mStopFetching )
1149 emit fetchingFinished();
1153 emit tokensReady( fieldNames );
1164 emit fetchingFinished();
1167void QgsConnectionsApiFetcher::stopFetching()
1171 mFeedback->cancel();
1174QgsQueryResultItemDelegate::QgsQueryResultItemDelegate( QObject *parent )
1175 : QStyledItemDelegate( parent )
1178QString QgsQueryResultItemDelegate::displayText(
const QVariant &value,
const QLocale &locale )
const
1181 QString result { QgsExpressionUtils::toLocalizedString( value ) };
1183 if ( result.length() > 255 )
1185 result.truncate( 255 );
1186 result.append( u
"…"_s );
1200 setObjectName( u
"QgsQueryResultDialog"_s );
1203 mWidget =
new QgsQueryResultWidget(
this, connection );
1204 QVBoxLayout *l =
new QVBoxLayout();
1205 l->setContentsMargins( 6, 6, 6, 6 );
1207 QDialogButtonBox *mButtonBox =
new QDialogButtonBox( QDialogButtonBox::StandardButton::Close | QDialogButtonBox::StandardButton::Help );
1208 connect( mButtonBox, &QDialogButtonBox::rejected,
this, &QDialog::close );
1209 connect( mButtonBox, &QDialogButtonBox::helpRequested,
this, [] {
QgsHelp::openHelp( u
"managing_data_source/create_layers.html#execute-sql"_s ); } );
1210 l->addWidget( mWidget );
1211 l->addWidget( mButtonBox );
1216void QgsQueryResultDialog::closeEvent( QCloseEvent *event )
1218 if ( !mWidget->promptUnsavedChanges() )
1233 : mIdentifierName( identifierName )
1235 setObjectName( u
"SQLCommandsDialog"_s );
1239 mWidget =
new QgsQueryResultWidget(
nullptr, connection );
1240 setCentralWidget( mWidget );
1241 mWidget->layout()->setContentsMargins( 6, 6, 6, 6 );
1243 connect( mWidget, &QgsQueryResultWidget::requestDialogTitleUpdate,
this, &QgsQueryResultMainWindow::updateWindowTitle );
1245 updateWindowTitle( QString() );
1248void QgsQueryResultMainWindow::closeEvent( QCloseEvent *event )
1250 if ( !mWidget->promptUnsavedChanges() )
1260void QgsQueryResultMainWindow::updateWindowTitle(
const QString &fileName )
1262 if ( fileName.isEmpty() )
1264 if ( !mIdentifierName.isEmpty() )
1265 setWindowTitle( tr(
"%1 — Execute SQL" ).arg( mIdentifierName ) );
1267 setWindowTitle( tr(
"Execute SQL" ) );
1271 if ( !mIdentifierName.isEmpty() )
1272 setWindowTitle( tr(
"%1 — %2 — Execute SQL" ).arg( fileName, mIdentifierName ) );
1274 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.
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.
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
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(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
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.
QString sql() const
Returns the sql statement entered in the dialog.
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.
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.
@ 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 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.