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 );
572 QApplication::clipboard()->setMimeData( mdata, QClipboard::Selection );
574 QApplication::clipboard()->setMimeData( mdata, QClipboard::Clipboard );
580 const thread_local QRegularExpression rx( u
";\\s*$"_s );
581 mSqlVectorLayerOptions.sql = mSqlEditor->text().replace( rx, QString() );
582 mSqlVectorLayerOptions.filter = mFilterLineEdit->text();
583 mSqlVectorLayerOptions.primaryKeyColumns = mPkColumnsComboBox->checkedItems();
584 mSqlVectorLayerOptions.geometryColumn = mGeometryColumnComboBox->currentText();
585 mSqlVectorLayerOptions.layerName = mLayerNameLineEdit->text();
586 mSqlVectorLayerOptions.disableSelectAtId = mAvoidSelectingAsFeatureIdCheckBox->isChecked();
587 QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions options { mSqlVectorLayerOptions };
589 if ( !mPkColumnsCheckBox->isChecked() )
593 if ( !mGeometryColumnCheckBox->isChecked() )
602 mConnection.reset( connection );
609 const QMultiMap<Qgis::SqlKeywordCategory, QStringList> keywordsDict { connection->
sqlDictionary() };
610 QStringList keywords;
611 for (
auto it = keywordsDict.constBegin(); it != keywordsDict.constEnd(); it++ )
613 keywords.append( it.value() );
617 mSqlEditor->setExtraKeywords( keywords );
618 mSqlErrorText->setExtraKeywords( keywords );
621 QThread *apiFetcherWorkerThread =
new QThread();
622 QgsConnectionsApiFetcher *apiFetcher =
new QgsConnectionsApiFetcher( mConnection->uri(), mConnection->providerKey() );
623 apiFetcher->moveToThread( apiFetcherWorkerThread );
624 connect( apiFetcherWorkerThread, &QThread::started, apiFetcher, &QgsConnectionsApiFetcher::fetchTokens );
625 connect( apiFetcher, &QgsConnectionsApiFetcher::tokensReady,
this, &QgsQueryResultPanelWidget::tokensReady );
626 connect( apiFetcher, &QgsConnectionsApiFetcher::fetchingFinished, apiFetcherWorkerThread, [apiFetcher, apiFetcherWorkerThread] {
627 apiFetcherWorkerThread->quit();
628 apiFetcherWorkerThread->wait();
629 apiFetcherWorkerThread->deleteLater();
630 apiFetcher->deleteLater();
633 mApiFetcher = apiFetcher;
634 apiFetcherWorkerThread->start();
640void QgsQueryResultPanelWidget::setQuery(
const QString &sql )
642 mSqlEditor->setText( sql );
645void QgsQueryResultPanelWidget::notify(
const QString &title,
const QString &text,
Qgis::MessageLevel level )
647 mMessageBar->pushMessage( title, text, level );
653 QgsNewNameDialog dlg( QString(), QString(), QStringList(), existingQueryNames );
654 dlg.setWindowTitle( tr(
"Store Query" ) );
655 dlg.setHintString( tr(
"Name for the stored query" ) );
656 dlg.setOverwriteEnabled(
true );
657 dlg.setConflictingNameWarning( tr(
"A stored query with this name already exists, it will be overwritten." ) );
658 dlg.setShowExistingNamesCompleter(
true );
659 if ( dlg.exec() != QDialog::Accepted )
662 const QString name = dlg.name();
663 if ( name.isEmpty() )
684 mQueryWidget =
new QgsQueryResultPanelWidget(
nullptr, connection );
685 mPanelStack->setMainPanel( mQueryWidget );
687 mPresetQueryMenu =
new QMenu(
this );
688 connect( mPresetQueryMenu, &QMenu::aboutToShow,
this, &QgsQueryResultWidget::populatePresetQueryMenu );
690 QToolButton *presetQueryButton =
new QToolButton();
691 presetQueryButton->setMenu( mPresetQueryMenu );
693 presetQueryButton->setPopupMode( QToolButton::InstantPopup );
694 mToolBar->addWidget( presetQueryButton );
696 connect( mActionOpenQuery, &QAction::triggered,
this, &QgsQueryResultWidget::openQuery );
697 connect( mActionSaveQuery, &QAction::triggered,
this, [
this] { saveQuery(
false ); } );
698 connect( mActionSaveQueryAs, &QAction::triggered,
this, [
this] { saveQuery(
true ); } );
700 connect( mActionCut, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::cut );
701 connect( mActionCopy, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::copy );
702 connect( mActionPaste, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::paste );
703 connect( mActionUndo, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::undo );
704 connect( mActionRedo, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::redo );
705 mActionUndo->setEnabled(
false );
706 mActionRedo->setEnabled(
false );
710 connect( mQueryWidget->sqlEditor(), &QgsCodeEditor::modificationChanged,
this, &QgsQueryResultWidget::setHasChanged );
712 connect( mActionShowHistory, &QAction::toggled,
this, &QgsQueryResultWidget::showHistoryPanel );
714 connect( mActionClear, &QAction::triggered,
this, [
this] {
716 mQueryWidget->sqlEditor()->SendScintilla( QsciScintilla::SCI_SETTEXT,
"" );
719 connect( mQueryWidget->sqlEditor(), &QgsCodeEditorSQL::textChanged,
this, &QgsQueryResultWidget::updateButtons );
721 connect( mQueryWidget->sqlEditor(), &QgsCodeEditorSQL::copyAvailable, mActionCut, &QAction::setEnabled );
722 connect( mQueryWidget->sqlEditor(), &QgsCodeEditorSQL::copyAvailable, mActionCopy, &QAction::setEnabled );
724 connect( mQueryWidget, &QgsQueryResultPanelWidget::createSqlVectorLayer,
this, &QgsQueryResultWidget::createSqlVectorLayer );
725 connect( mQueryWidget, &QgsQueryResultPanelWidget::firstResultBatchFetched,
this, &QgsQueryResultWidget::firstResultBatchFetched );
728 setHasChanged(
false );
731QgsQueryResultWidget::~QgsQueryResultWidget()
733 if ( mHistoryWidget )
735 mPanelStack->closePanel( mHistoryWidget );
736 mHistoryWidget->deleteLater();
742 if ( !options.
sql.isEmpty() )
744 setQuery( options.
sql );
746 mQueryWidget->setSqlVectorLayerOptions( options );
749void QgsQueryResultWidget::setWidgetMode( QueryWidgetMode widgetMode )
751 mQueryWidget->setWidgetMode( widgetMode );
754void QgsQueryResultWidget::executeQuery()
756 mQueryWidget->executeQuery();
759void QgsQueryResultWidget::updateButtons()
761 mQueryWidget->updateButtons();
763 const bool isEmpty = mQueryWidget->sqlEditor()->text().isEmpty();
764 mActionClear->setEnabled( !isEmpty );
765 mActionUndo->setEnabled( mQueryWidget->sqlEditor()->isUndoAvailable() );
766 mActionRedo->setEnabled( mQueryWidget->sqlEditor()->isRedoAvailable() );
769void QgsQueryResultWidget::showError(
const QString &title,
const QString &message,
bool isSqlError )
771 mQueryWidget->showError( title, message, isSqlError );
774void QgsQueryResultWidget::tokensReady(
const QStringList & )
777void QgsQueryResultWidget::copyResults()
779 mQueryWidget->copyResults();
782void QgsQueryResultWidget::copyResults(
int fromRow,
int toRow,
int fromColumn,
int toColumn )
784 mQueryWidget->copyResults( fromRow, toRow, fromColumn, toColumn );
787void QgsQueryResultWidget::openQuery()
789 if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() && mHasChangedFileContents )
791 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 )
792 == QMessageBox::StandardButton::No )
796 QString initialDir = settingLastSourceFolder->value();
797 if ( initialDir.isEmpty() )
798 initialDir = QDir::homePath();
800 const QString fileName = QFileDialog::getOpenFileName(
this, tr(
"Open Query" ), initialDir, tr(
"SQL queries (*.sql *.SQL)" ) + u
";;"_s + QObject::tr(
"All files" ) + u
" (*.*)"_s );
802 if ( fileName.isEmpty() )
805 QFileInfo fi( fileName );
806 settingLastSourceFolder->setValue( fi.path() );
808 QgsTemporaryCursorOverride cursor( Qt::CursorShape::WaitCursor );
810 mQueryWidget->codeEditorWidget()->loadFile( fileName );
811 setHasChanged(
false );
814void QgsQueryResultWidget::saveQuery(
bool saveAs )
816 if ( mQueryWidget->codeEditorWidget()->filePath().isEmpty() || saveAs )
818 QString selectedFilter;
820 QString initialDir = settingLastSourceFolder->value();
821 if ( initialDir.isEmpty() )
822 initialDir = QDir::homePath();
824 QString newPath = QFileDialog::getSaveFileName(
this, tr(
"Save Query" ), initialDir, tr(
"SQL queries (*.sql *.SQL)" ) + u
";;"_s + QObject::tr(
"All files" ) + u
" (*.*)"_s, &selectedFilter );
826 if ( !newPath.isEmpty() )
828 QFileInfo fi( newPath );
829 settingLastSourceFolder->setValue( fi.path() );
831 if ( !selectedFilter.contains( u
"*.*)"_s ) )
833 mQueryWidget->codeEditorWidget()->save( newPath );
834 setHasChanged(
false );
837 else if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() )
839 mQueryWidget->codeEditorWidget()->save();
840 setHasChanged(
false );
846 mQueryWidget->setConnection( connection );
850void QgsQueryResultWidget::setQuery(
const QString &sql )
852 mQueryWidget->sqlEditor()->setText( sql );
854 mActionUndo->setEnabled(
false );
855 mActionRedo->setEnabled(
false );
858bool QgsQueryResultWidget::promptUnsavedChanges()
860 if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() && mHasChangedFileContents )
862 const QMessageBox::StandardButton ret = QMessageBox::question(
865 tr(
"There are unsaved changes in this query. Do you want to save those?" ),
866 QMessageBox::StandardButton::Save | QMessageBox::StandardButton::Cancel | QMessageBox::StandardButton::Discard,
867 QMessageBox::StandardButton::Cancel
870 if ( ret == QMessageBox::StandardButton::Save )
875 else if ( ret == QMessageBox::StandardButton::Discard )
891void QgsQueryResultWidget::notify(
const QString &title,
const QString &text,
Qgis::MessageLevel level )
893 mQueryWidget->notify( title, text, level );
897void QgsQueryResultWidget::setHasChanged(
bool hasChanged )
899 mActionSaveQuery->setEnabled( hasChanged );
900 mHasChangedFileContents = hasChanged;
904void QgsQueryResultWidget::updateDialogTitle()
907 if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() )
909 const QFileInfo fi( mQueryWidget->codeEditorWidget()->filePath() );
910 fileName = fi.fileName();
911 if ( mHasChangedFileContents )
913 fileName.prepend(
'*' );
917 emit requestDialogTitleUpdate( fileName );
920void QgsQueryResultWidget::populatePresetQueryMenu()
922 mPresetQueryMenu->clear();
924 QMenu *storeQueryMenu =
new QMenu( tr(
"Store Current Query" ), mPresetQueryMenu );
925 mPresetQueryMenu->addMenu( storeQueryMenu );
926 QAction *storeInProfileAction =
new QAction( tr(
"In User Profile…" ), storeQueryMenu );
927 storeQueryMenu->addAction( storeInProfileAction );
928 storeInProfileAction->setEnabled( !mQueryWidget->sqlEditor()->text().isEmpty() );
930 QAction *storeInProjectAction =
new QAction( tr(
"In Current Project…" ), storeQueryMenu );
931 storeQueryMenu->addAction( storeInProjectAction );
932 storeInProjectAction->setEnabled( !mQueryWidget->sqlEditor()->text().isEmpty() );
937 if ( !storedQueries.isEmpty() )
939 QList< QgsStoredQueryManager::QueryDetails > userProfileQueries;
940 std::copy_if( storedQueries.begin(), storedQueries.end(), std::back_inserter( userProfileQueries ), [](
const QgsStoredQueryManager::QueryDetails &details ) {
941 return details.backend == Qgis::QueryStorageBackend::LocalProfile;
944 QList< QgsStoredQueryManager::QueryDetails > projectQueries;
945 std::copy_if( storedQueries.begin(), storedQueries.end(), std::back_inserter( projectQueries ), [](
const QgsStoredQueryManager::QueryDetails &details ) {
946 return details.backend == Qgis::QueryStorageBackend::CurrentProject;
950 for (
const QgsStoredQueryManager::QueryDetails &query : std::as_const( userProfileQueries ) )
952 QAction *action =
new QAction( query.name, mPresetQueryMenu );
953 mPresetQueryMenu->addAction( action );
954 connect( action, &QAction::triggered,
this, [
this, query] { mQueryWidget->sqlEditor()->insertText( query.definition ); } );
956 if ( userProfileQueries.empty() )
958 QAction *action =
new QAction( tr(
"No Stored Queries Available" ), mPresetQueryMenu );
959 action->setEnabled(
false );
960 mPresetQueryMenu->addAction( action );
964 for (
const QgsStoredQueryManager::QueryDetails &query : std::as_const( projectQueries ) )
966 QAction *action =
new QAction( query.name, mPresetQueryMenu );
967 mPresetQueryMenu->addAction( action );
968 connect( action, &QAction::triggered,
this, [
this, query] { mQueryWidget->sqlEditor()->insertText( query.definition ); } );
970 if ( projectQueries.empty() )
972 QAction *action =
new QAction( tr(
"No Stored Queries Available" ), mPresetQueryMenu );
973 action->setEnabled(
false );
974 mPresetQueryMenu->addAction( action );
977 mPresetQueryMenu->addSeparator();
979 QMenu *removeQueryMenu =
new QMenu( tr(
"Removed Stored Query" ), mPresetQueryMenu );
980 mPresetQueryMenu->addMenu( removeQueryMenu );
982 for (
const QgsStoredQueryManager::QueryDetails &query : storedQueries )
984 QAction *action =
new QAction( tr(
"%1…" ).arg( query.name ), mPresetQueryMenu );
985 removeQueryMenu->addAction( action );
986 connect( action, &QAction::triggered,
this, [
this, query] {
987 const QMessageBox::StandardButton res
988 = 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 );
989 if ( res == QMessageBox::Yes )
1005 QgsNewNameDialog dlg( QString(), QString(), QStringList(), existingQueryNames );
1006 dlg.setWindowTitle( tr(
"Store Query" ) );
1007 dlg.setHintString( tr(
"Name for the stored query" ) );
1008 dlg.setOverwriteEnabled(
true );
1009 dlg.setConflictingNameWarning( tr(
"A stored query with this name already exists, it will be overwritten." ) );
1010 dlg.setShowExistingNamesCompleter(
true );
1011 if ( dlg.exec() != QDialog::Accepted )
1014 const QString name = dlg.name();
1015 if ( name.isEmpty() )
1026void QgsQueryResultWidget::showHistoryPanel(
bool show )
1030 mHistoryWidget =
new QgsDatabaseQueryHistoryWidget();
1031 mHistoryWidget->setPanelTitle( tr(
"SQL History" ) );
1032 mPanelStack->showPanel( mHistoryWidget );
1035 Q_UNUSED( connectionUri );
1036 Q_UNUSED( provider );
1038 mQueryWidget->sqlEditor()->setText( sql );
1039 mActionUndo->setEnabled(
false );
1040 mActionRedo->setEnabled(
false );
1041 mHistoryWidget->acceptPanel();
1044 else if ( mHistoryWidget )
1046 mPanelStack->closePanel( mHistoryWidget );
1047 mHistoryWidget->deleteLater();
1054void QgsConnectionsApiFetcher::fetchTokens()
1056 if ( mStopFetching )
1058 emit fetchingFinished();
1066 emit fetchingFinished();
1070 if ( !mStopFetching && connection )
1072 mFeedback = std::make_unique<QgsFeedback>();
1073 QStringList schemas;
1078 schemas = connection->
schemas();
1079 emit tokensReady( schemas );
1088 schemas.push_back( QString() );
1091 for (
const auto &schema : std::as_const( schemas ) )
1093 if ( mStopFetching )
1096 emit fetchingFinished();
1100 QStringList tableNames;
1106 if ( mStopFetching )
1109 emit fetchingFinished();
1112 tableNames.push_back( table.tableName() );
1114 emit tokensReady( tableNames );
1122 for (
const auto &table : std::as_const( tableNames ) )
1124 if ( mStopFetching )
1127 emit fetchingFinished();
1131 QStringList fieldNames;
1134 const QgsFields fields( connection->
fields( schema, table, mFeedback.get() ) );
1135 if ( mStopFetching )
1138 emit fetchingFinished();
1142 for (
const auto &field : std::as_const( fields ) )
1144 fieldNames.push_back( field.name() );
1145 if ( mStopFetching )
1148 emit fetchingFinished();
1152 emit tokensReady( fieldNames );
1163 emit fetchingFinished();
1166void QgsConnectionsApiFetcher::stopFetching()
1170 mFeedback->cancel();
1173QgsQueryResultItemDelegate::QgsQueryResultItemDelegate( QObject *parent )
1174 : QStyledItemDelegate( parent )
1177QString QgsQueryResultItemDelegate::displayText(
const QVariant &value,
const QLocale &locale )
const
1180 QString result { QgsExpressionUtils::toLocalizedString( value ) };
1182 if ( result.length() > 255 )
1184 result.truncate( 255 );
1185 result.append( u
"…"_s );
1199 setObjectName( u
"QgsQueryResultDialog"_s );
1202 mWidget =
new QgsQueryResultWidget(
this, connection );
1203 QVBoxLayout *l =
new QVBoxLayout();
1204 l->setContentsMargins( 6, 6, 6, 6 );
1206 QDialogButtonBox *mButtonBox =
new QDialogButtonBox( QDialogButtonBox::StandardButton::Close | QDialogButtonBox::StandardButton::Help );
1207 connect( mButtonBox, &QDialogButtonBox::rejected,
this, &QDialog::close );
1208 connect( mButtonBox, &QDialogButtonBox::helpRequested,
this, [] {
QgsHelp::openHelp( u
"managing_data_source/create_layers.html#execute-sql"_s ); } );
1209 l->addWidget( mWidget );
1210 l->addWidget( mButtonBox );
1215void QgsQueryResultDialog::closeEvent( QCloseEvent *event )
1217 if ( !mWidget->promptUnsavedChanges() )
1232 : mIdentifierName( identifierName )
1234 setObjectName( u
"SQLCommandsDialog"_s );
1238 mWidget =
new QgsQueryResultWidget(
nullptr, connection );
1239 setCentralWidget( mWidget );
1240 mWidget->layout()->setContentsMargins( 6, 6, 6, 6 );
1242 connect( mWidget, &QgsQueryResultWidget::requestDialogTitleUpdate,
this, &QgsQueryResultMainWindow::updateWindowTitle );
1244 updateWindowTitle( QString() );
1247void QgsQueryResultMainWindow::closeEvent( QCloseEvent *event )
1249 if ( !mWidget->promptUnsavedChanges() )
1259void QgsQueryResultMainWindow::updateWindowTitle(
const QString &fileName )
1261 if ( fileName.isEmpty() )
1263 if ( !mIdentifierName.isEmpty() )
1264 setWindowTitle( tr(
"%1 — Execute SQL" ).arg( mIdentifierName ) );
1266 setWindowTitle( tr(
"Execute SQL" ) );
1270 if ( !mIdentifierName.isEmpty() )
1271 setWindowTitle( tr(
"%1 — %2 — Execute SQL" ).arg( fileName, mIdentifierName ) );
1273 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.