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] {
129 mExecuteButton->setText( mSqlEditor->selectedText().isEmpty() ? tr(
"Execute" ) : tr(
"Execute Selection" ) );
131 connect( mFilterToolButton, &QToolButton::pressed,
this, [
this] {
136 std::unique_ptr<QgsVectorLayer> vlayer { mConnection->createSqlVectorLayer( sqlVectorLayerOptions() ) };
138 if ( builder.exec() == QDialog::Accepted )
140 mFilterLineEdit->setText( builder.
sql() );
145 mMessageBar->pushCritical( tr(
"Error opening filter dialog" ), tr(
"There was an error while preparing SQL filter dialog: %1." ).arg( ex.
what() ) );
151 mStatusLabel->hide();
152 mSqlErrorText->hide();
154 mLoadAsNewLayerGroupBox->setCollapsed(
true );
161 mPkColumnsCheckBox->setVisible( showPkConfig );
162 mPkColumnsComboBox->setVisible( showPkConfig );
165 mGeometryColumnCheckBox->setVisible( showGeometryColumnConfig );
166 mGeometryColumnComboBox->setVisible( showGeometryColumnConfig );
169 mFilterLabel->setVisible( showFilterConfig );
170 mFilterToolButton->setVisible( showFilterConfig );
171 mFilterLineEdit->setVisible( showFilterConfig );
174 mAvoidSelectingAsFeatureIdCheckBox->setVisible( showDisableSelectAtId );
178 QShortcut *copySelection =
new QShortcut( QKeySequence::Copy, mQueryResultsTableView );
179 connect( copySelection, &QShortcut::activated,
this, &QgsQueryResultPanelWidget::copySelection );
181 setConnection( connection );
184QgsQueryResultPanelWidget::~QgsQueryResultPanelWidget()
187 cancelRunningQuery();
197 return mCodeEditorWidget;
202 mSqlVectorLayerOptions = options;
203 if ( !options.
sql.isEmpty() )
205 setQuery( options.
sql );
209 mPkColumnsComboBox->setCheckedItems( {} );
214 mGeometryColumnCheckBox->setChecked( !options.
geometryColumn.isEmpty() );
215 mGeometryColumnComboBox->clear();
218 mGeometryColumnComboBox->setCurrentText( options.
geometryColumn );
220 mFilterLineEdit->setText( options.
filter );
221 mLayerNameLineEdit->setText( options.
layerName );
224void QgsQueryResultPanelWidget::setWidgetMode( QgsQueryResultWidget::QueryWidgetMode widgetMode )
226 mQueryWidgetMode = widgetMode;
227 switch ( widgetMode )
229 case QgsQueryResultWidget::QueryWidgetMode::SqlQueryMode:
230 mLoadAsNewLayerGroupBox->setTitle( tr(
"Load as New Layer" ) );
231 mLoadLayerPushButton->setText( tr(
"Load Layer" ) );
232 mLoadAsNewLayerGroupBox->setCollapsed(
true );
234 case QgsQueryResultWidget::QueryWidgetMode::QueryLayerUpdateMode:
235 mLoadAsNewLayerGroupBox->setTitle( tr(
"Update Query Layer" ) );
236 mLoadLayerPushButton->setText( tr(
"Update Layer" ) );
237 mLoadAsNewLayerGroupBox->setCollapsed(
false );
242void QgsQueryResultPanelWidget::executeQuery()
244 mQueryResultsTableView->hide();
245 mSqlErrorText->hide();
246 mResultsContainer->hide();
247 mFirstRowFetched =
false;
249 cancelRunningQuery();
252 const QString sql { mSqlEditor->selectedText().isEmpty() ? mSqlEditor->text() : mSqlEditor->selectedText() };
257 { u
"provider"_s, mConnection->providerKey() },
258 { u
"connection"_s, mConnection->uri() },
262 mWasCanceled =
false;
263 mFeedback = std::make_unique<QgsFeedback>();
264 mStopButton->setEnabled(
true );
265 mStatusLabel->show();
266 mStatusLabel->setText( tr(
"Executing query…" ) );
267 mProgressBar->show();
268 mProgressBar->setRange( 0, 0 );
269 mSqlErrorMessage.clear();
271 connect( mStopButton, &QPushButton::pressed, mFeedback.get(), [
this] {
272 mStatusLabel->setText( tr(
"Stopped" ) );
274 mProgressBar->hide();
279 connect( &mQueryResultWatcher, &QFutureWatcher<QgsAbstractDatabaseProviderConnection::QueryResult>::finished,
this, &QgsQueryResultPanelWidget::startFetching, Qt::ConnectionType::UniqueConnection );
281 QFuture<QgsAbstractDatabaseProviderConnection::QueryResult> future = QtConcurrent::run( [
this, sql]() -> QgsAbstractDatabaseProviderConnection::QueryResult {
284 return mConnection->execSql( sql, mFeedback.get() );
286 catch ( QgsProviderConnectionException &ex )
288 mSqlErrorMessage = ex.
what();
289 return QgsAbstractDatabaseProviderConnection::QueryResult();
292 mQueryResultWatcher.setFuture( future );
296 showError( tr(
"Connection error" ), tr(
"Cannot execute query: connection to the database is not available." ) );
300void QgsQueryResultPanelWidget::updateButtons()
302 mFilterLineEdit->setEnabled( mFirstRowFetched );
303 mFilterToolButton->setEnabled( mFirstRowFetched );
304 const bool isEmpty = mSqlEditor->text().isEmpty();
305 mExecuteButton->setEnabled( !isEmpty );
307 mLoadAsNewLayerGroupBox->setEnabled(
308 mSqlErrorMessage.isEmpty() && mFirstRowFetched
312void QgsQueryResultPanelWidget::showCellContextMenu( QPoint point )
314 const QModelIndex modelIndex = mQueryResultsTableView->indexAt( point );
315 if ( modelIndex.isValid() )
317 QMenu *menu =
new QMenu();
318 menu->setAttribute( Qt::WA_DeleteOnClose );
320 menu->addAction(
QgsApplication::getThemeIcon(
"mActionEditCopy.svg" ), tr(
"Copy" ),
this, [
this] { copySelection(); }, QKeySequence::Copy );
322 menu->exec( mQueryResultsTableView->viewport()->mapToGlobal( point ) );
326void QgsQueryResultPanelWidget::copySelection()
328 const QModelIndexList selection = mQueryResultsTableView->selectionModel()->selectedIndexes();
329 if ( selection.empty() )
336 for (
const QModelIndex &index : selection )
338 if ( minRow == -1 || index.row() < minRow )
339 minRow = index.row();
340 if ( maxRow == -1 || index.row() > maxRow )
341 maxRow = index.row();
342 if ( minCol == -1 || index.column() < minCol )
343 minCol = index.column();
344 if ( maxCol == -1 || index.column() > maxCol )
345 maxCol = index.column();
348 if ( minRow == maxRow && minCol == maxCol )
351 const QString text = mModel->data( selection.at( 0 ), Qt::DisplayRole ).toString();
352 QApplication::clipboard()->setText( text );
356 copyResults( minRow, maxRow, minCol, maxCol );
360void QgsQueryResultPanelWidget::updateSqlLayerColumns()
365 mFilterToolButton->setEnabled(
true );
366 mFilterLineEdit->setEnabled(
true );
367 mPkColumnsComboBox->clear();
368 mGeometryColumnComboBox->clear();
369 const bool hasPkInformation { !mSqlVectorLayerOptions.primaryKeyColumns.isEmpty() };
370 const bool hasGeomColInformation { !mSqlVectorLayerOptions.geometryColumn.isEmpty() };
371 static const QStringList geomColCandidates { u
"geom"_s, u
"geometry"_s, u
"the_geom"_s };
372 const QStringList constCols { mModel->columns() };
373 for (
const QString &
c : constCols )
375 const bool pkCheckedState = hasPkInformation ? mSqlVectorLayerOptions.primaryKeyColumns.contains(
c ) :
c.contains( u
"id"_s, Qt::CaseSensitivity::CaseInsensitive );
377 mPkColumnsComboBox->addItemWithCheckState(
c, pkCheckedState && mPkColumnsComboBox->checkedItems().isEmpty() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked );
378 mGeometryColumnComboBox->addItem(
c );
379 if ( !hasGeomColInformation && geomColCandidates.contains(
c, Qt::CaseSensitivity::CaseInsensitive ) )
381 mGeometryColumnComboBox->setCurrentText(
c );
384 mPkColumnsCheckBox->setChecked( hasPkInformation );
385 mGeometryColumnCheckBox->setChecked( hasGeomColInformation );
386 if ( hasGeomColInformation )
388 mGeometryColumnComboBox->setCurrentText( mSqlVectorLayerOptions.geometryColumn );
392void QgsQueryResultPanelWidget::cancelRunningQuery()
401 if ( mQueryResultWatcher.isRunning() )
403 mQueryResultWatcher.waitForFinished();
407void QgsQueryResultPanelWidget::cancelApiFetcher()
411 mApiFetcher->stopFetching();
416void QgsQueryResultPanelWidget::startFetching()
420 if ( !mSqlErrorMessage.isEmpty() )
422 showError( tr(
"SQL error" ), mSqlErrorMessage,
true );
428 mStatusLabel->setText( u
"Query executed successfully (%1 rows, %2 ms)"_s
429 .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" )
460 .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() ) ) );
461 mFetchedRowsBatchCount += last - first + 1;
462 mProgressBar->setValue( mFetchedRowsBatchCount );
465 mQueryResultsTableView->setModel( mModel.get() );
466 mQueryResultsTableView->show();
467 mResultsContainer->show();
469 connect( mModel.get(), &QgsQueryResultModel::fetchingComplete, mStopButton, [
this] {
471 const QgsHistoryEntry currentHistoryEntry = QgsGui::historyProviderRegistry()->entry( mCurrentHistoryEntryId, ok );
472 QVariantMap entryDetails = currentHistoryEntry.entry;
473 entryDetails.insert( u
"rows"_s, mActualRowCount );
474 entryDetails.insert( u
"time"_s, mQueryResultWatcher.result().queryExecutionTime() );
476 QgsGui::historyProviderRegistry()->updateEntry( mCurrentHistoryEntryId, entryDetails );
477 mProgressBar->hide();
478 mStopButton->setEnabled( false );
484 mStatusLabel->setText( tr(
"SQL command aborted" ) );
485 mProgressBar->hide();
489void QgsQueryResultPanelWidget::showError(
const QString &title,
const QString &message,
bool isSqlError )
491 mStatusLabel->show();
492 mStatusLabel->setText( tr(
"An error occurred while executing the query" ) );
493 mProgressBar->hide();
494 mQueryResultsTableView->hide();
497 mSqlErrorText->show();
498 mSqlErrorText->setText( message );
499 mResultsContainer->show();
503 mMessageBar->pushCritical( title, message );
504 mResultsContainer->hide();
508void QgsQueryResultPanelWidget::tokensReady(
const QStringList &tokens )
510 mSqlEditor->setExtraKeywords( mSqlEditor->extraKeywords() + tokens );
511 mSqlErrorText->setExtraKeywords( mSqlErrorText->extraKeywords() + tokens );
514void QgsQueryResultPanelWidget::copyResults()
516 const int rowCount = mModel->rowCount( QModelIndex() );
517 const int columnCount = mModel->columnCount( QModelIndex() );
518 copyResults( 0, rowCount - 1, 0, columnCount - 1 );
521void QgsQueryResultPanelWidget::copyResults(
int fromRow,
int toRow,
int fromColumn,
int toColumn )
523 QStringList rowStrings;
524 QStringList columnStrings;
526 const int rowCount = mModel->rowCount( QModelIndex() );
527 const int columnCount = mModel->columnCount( QModelIndex() );
529 toRow = std::min( toRow, rowCount - 1 );
530 toColumn = std::min( toColumn, columnCount - 1 );
532 rowStrings.reserve( toRow - fromRow );
535 for (
int col = fromColumn; col <= toColumn; col++ )
537 columnStrings += mModel->headerData( col, Qt::Horizontal, Qt::DisplayRole ).toString();
539 rowStrings += columnStrings.join( QLatin1Char(
'\t' ) );
540 columnStrings.clear();
542 for (
int row = fromRow; row <= toRow; row++ )
544 for (
int col = fromColumn; col <= toColumn; col++ )
546 columnStrings += mModel->data( mModel->index( row, col ), Qt::DisplayRole ).toString();
548 rowStrings += columnStrings.join( QLatin1Char(
'\t' ) );
549 columnStrings.clear();
552 if ( !rowStrings.isEmpty() )
554 const QString text = rowStrings.join( QLatin1Char(
'\n' ) );
555 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.arg( text );
556 html.replace(
"\t"_L1,
"</td><td>"_L1 ).replace(
"\n"_L1,
"</td></tr><tr><td>"_L1 );
558 QMimeData *mdata =
new QMimeData();
559 mdata->setData( u
"text/html"_s, html.toUtf8() );
560 if ( !text.isEmpty() )
562 mdata->setText( text );
566 QApplication::clipboard()->setMimeData( mdata, QClipboard::Selection );
568 QApplication::clipboard()->setMimeData( mdata, QClipboard::Clipboard );
574 const thread_local QRegularExpression rx( u
";\\s*$"_s );
575 mSqlVectorLayerOptions.sql = mSqlEditor->text().replace( rx, QString() );
576 mSqlVectorLayerOptions.filter = mFilterLineEdit->text();
577 mSqlVectorLayerOptions.primaryKeyColumns = mPkColumnsComboBox->checkedItems();
578 mSqlVectorLayerOptions.geometryColumn = mGeometryColumnComboBox->currentText();
579 mSqlVectorLayerOptions.layerName = mLayerNameLineEdit->text();
580 mSqlVectorLayerOptions.disableSelectAtId = mAvoidSelectingAsFeatureIdCheckBox->isChecked();
581 QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions options { mSqlVectorLayerOptions };
583 if ( !mPkColumnsCheckBox->isChecked() )
587 if ( !mGeometryColumnCheckBox->isChecked() )
596 mConnection.reset( connection );
603 const QMultiMap<Qgis::SqlKeywordCategory, QStringList> keywordsDict { connection->
sqlDictionary() };
604 QStringList keywords;
605 for (
auto it = keywordsDict.constBegin(); it != keywordsDict.constEnd(); it++ )
607 keywords.append( it.value() );
611 mSqlEditor->setExtraKeywords( keywords );
612 mSqlErrorText->setExtraKeywords( keywords );
615 QThread *apiFetcherWorkerThread =
new QThread();
616 QgsConnectionsApiFetcher *apiFetcher =
new QgsConnectionsApiFetcher( mConnection->uri(), mConnection->providerKey() );
617 apiFetcher->moveToThread( apiFetcherWorkerThread );
618 connect( apiFetcherWorkerThread, &QThread::started, apiFetcher, &QgsConnectionsApiFetcher::fetchTokens );
619 connect( apiFetcher, &QgsConnectionsApiFetcher::tokensReady,
this, &QgsQueryResultPanelWidget::tokensReady );
620 connect( apiFetcher, &QgsConnectionsApiFetcher::fetchingFinished, apiFetcherWorkerThread, [apiFetcher, apiFetcherWorkerThread] {
621 apiFetcherWorkerThread->quit();
622 apiFetcherWorkerThread->wait();
623 apiFetcherWorkerThread->deleteLater();
624 apiFetcher->deleteLater();
627 mApiFetcher = apiFetcher;
628 apiFetcherWorkerThread->start();
634void QgsQueryResultPanelWidget::setQuery(
const QString &sql )
636 mSqlEditor->setText( sql );
639void QgsQueryResultPanelWidget::notify(
const QString &title,
const QString &text,
Qgis::MessageLevel level )
641 mMessageBar->pushMessage( title, text, level );
647 QgsNewNameDialog dlg(
653 dlg.setWindowTitle( tr(
"Store Query" ) );
654 dlg.setHintString( tr(
"Name for the stored query" ) );
655 dlg.setOverwriteEnabled(
true );
656 dlg.setConflictingNameWarning( tr(
"A stored query with this name already exists, it will be overwritten." ) );
657 dlg.setShowExistingNamesCompleter(
true );
658 if ( dlg.exec() != QDialog::Accepted )
661 const QString name = dlg.name();
662 if ( name.isEmpty() )
683 mQueryWidget =
new QgsQueryResultPanelWidget(
nullptr, connection );
684 mPanelStack->setMainPanel( mQueryWidget );
686 mPresetQueryMenu =
new QMenu(
this );
687 connect( mPresetQueryMenu, &QMenu::aboutToShow,
this, &QgsQueryResultWidget::populatePresetQueryMenu );
689 QToolButton *presetQueryButton =
new QToolButton();
690 presetQueryButton->setMenu( mPresetQueryMenu );
692 presetQueryButton->setPopupMode( QToolButton::InstantPopup );
693 mToolBar->addWidget( presetQueryButton );
695 connect( mActionOpenQuery, &QAction::triggered,
this, &QgsQueryResultWidget::openQuery );
696 connect( mActionSaveQuery, &QAction::triggered,
this, [
this] { saveQuery(
false ); } );
697 connect( mActionSaveQueryAs, &QAction::triggered,
this, [
this] { saveQuery(
true ); } );
699 connect( mActionCut, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::cut );
700 connect( mActionCopy, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::copy );
701 connect( mActionPaste, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::paste );
702 connect( mActionUndo, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::undo );
703 connect( mActionRedo, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::redo );
704 mActionUndo->setEnabled(
false );
705 mActionRedo->setEnabled(
false );
709 connect( mQueryWidget->sqlEditor(), &QgsCodeEditor::modificationChanged,
this, &QgsQueryResultWidget::setHasChanged );
711 connect( mActionShowHistory, &QAction::toggled,
this, &QgsQueryResultWidget::showHistoryPanel );
713 connect( mActionClear, &QAction::triggered,
this, [
this] {
715 mQueryWidget->sqlEditor()->SendScintilla( QsciScintilla::SCI_SETTEXT,
"" );
718 connect( mQueryWidget->sqlEditor(), &QgsCodeEditorSQL::textChanged,
this, &QgsQueryResultWidget::updateButtons );
720 connect( mQueryWidget->sqlEditor(), &QgsCodeEditorSQL::copyAvailable, mActionCut, &QAction::setEnabled );
721 connect( mQueryWidget->sqlEditor(), &QgsCodeEditorSQL::copyAvailable, mActionCopy, &QAction::setEnabled );
723 connect( mQueryWidget, &QgsQueryResultPanelWidget::createSqlVectorLayer,
this, &QgsQueryResultWidget::createSqlVectorLayer );
724 connect( mQueryWidget, &QgsQueryResultPanelWidget::firstResultBatchFetched,
this, &QgsQueryResultWidget::firstResultBatchFetched );
727 setHasChanged(
false );
730QgsQueryResultWidget::~QgsQueryResultWidget()
732 if ( mHistoryWidget )
734 mPanelStack->closePanel( mHistoryWidget );
735 mHistoryWidget->deleteLater();
741 if ( !options.
sql.isEmpty() )
743 setQuery( options.
sql );
745 mQueryWidget->setSqlVectorLayerOptions( options );
748void QgsQueryResultWidget::setWidgetMode( QueryWidgetMode widgetMode )
750 mQueryWidget->setWidgetMode( widgetMode );
753void QgsQueryResultWidget::executeQuery()
755 mQueryWidget->executeQuery();
758void QgsQueryResultWidget::updateButtons()
760 mQueryWidget->updateButtons();
762 const bool isEmpty = mQueryWidget->sqlEditor()->text().isEmpty();
763 mActionClear->setEnabled( !isEmpty );
764 mActionUndo->setEnabled( mQueryWidget->sqlEditor()->isUndoAvailable() );
765 mActionRedo->setEnabled( mQueryWidget->sqlEditor()->isRedoAvailable() );
768void QgsQueryResultWidget::showError(
const QString &title,
const QString &message,
bool isSqlError )
770 mQueryWidget->showError( title, message, isSqlError );
773void 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 ) == QMessageBox::StandardButton::No )
795 QString initialDir = settingLastSourceFolder->value();
796 if ( initialDir.isEmpty() )
797 initialDir = QDir::homePath();
799 const QString fileName = QFileDialog::getOpenFileName(
this, tr(
"Open Query" ), initialDir, tr(
"SQL queries (*.sql *.SQL)" ) + u
";;"_s + QObject::tr(
"All files" ) + u
" (*.*)"_s );
801 if ( fileName.isEmpty() )
804 QFileInfo fi( fileName );
805 settingLastSourceFolder->setValue( fi.path() );
807 QgsTemporaryCursorOverride cursor( Qt::CursorShape::WaitCursor );
809 mQueryWidget->codeEditorWidget()->loadFile( fileName );
810 setHasChanged(
false );
813void QgsQueryResultWidget::saveQuery(
bool saveAs )
815 if ( mQueryWidget->codeEditorWidget()->filePath().isEmpty() || saveAs )
817 QString selectedFilter;
819 QString initialDir = settingLastSourceFolder->value();
820 if ( initialDir.isEmpty() )
821 initialDir = QDir::homePath();
823 QString newPath = QFileDialog::getSaveFileName(
827 tr(
"SQL queries (*.sql *.SQL)" ) + u
";;"_s + QObject::tr(
"All files" ) + u
" (*.*)"_s,
831 if ( !newPath.isEmpty() )
833 QFileInfo fi( newPath );
834 settingLastSourceFolder->setValue( fi.path() );
836 if ( !selectedFilter.contains( u
"*.*)"_s ) )
838 mQueryWidget->codeEditorWidget()->save( newPath );
839 setHasChanged(
false );
842 else if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() )
844 mQueryWidget->codeEditorWidget()->save();
845 setHasChanged(
false );
851 mQueryWidget->setConnection( connection );
855void QgsQueryResultWidget::setQuery(
const QString &sql )
857 mQueryWidget->sqlEditor()->setText( sql );
859 mActionUndo->setEnabled(
false );
860 mActionRedo->setEnabled(
false );
863bool QgsQueryResultWidget::promptUnsavedChanges()
865 if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() && mHasChangedFileContents )
867 const QMessageBox::StandardButton ret = QMessageBox::question(
871 "There are unsaved changes in this query. Do you want to save those?"
873 QMessageBox::StandardButton::Save
874 | QMessageBox::StandardButton::Cancel
875 | QMessageBox::StandardButton::Discard,
876 QMessageBox::StandardButton::Cancel
879 if ( ret == QMessageBox::StandardButton::Save )
884 else if ( ret == QMessageBox::StandardButton::Discard )
900void QgsQueryResultWidget::notify(
const QString &title,
const QString &text,
Qgis::MessageLevel level )
902 mQueryWidget->notify( title, text, level );
906void QgsQueryResultWidget::setHasChanged(
bool hasChanged )
908 mActionSaveQuery->setEnabled( hasChanged );
909 mHasChangedFileContents = hasChanged;
913void QgsQueryResultWidget::updateDialogTitle()
916 if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() )
918 const QFileInfo fi( mQueryWidget->codeEditorWidget()->filePath() );
919 fileName = fi.fileName();
920 if ( mHasChangedFileContents )
922 fileName.prepend(
'*' );
926 emit requestDialogTitleUpdate( fileName );
929void QgsQueryResultWidget::populatePresetQueryMenu()
931 mPresetQueryMenu->clear();
933 QMenu *storeQueryMenu =
new QMenu( tr(
"Store Current Query" ), mPresetQueryMenu );
934 mPresetQueryMenu->addMenu( storeQueryMenu );
935 QAction *storeInProfileAction =
new QAction( tr(
"In User Profile…" ), storeQueryMenu );
936 storeQueryMenu->addAction( storeInProfileAction );
937 storeInProfileAction->setEnabled( !mQueryWidget->sqlEditor()->text().isEmpty() );
938 connect( storeInProfileAction, &QAction::triggered,
this, [
this] {
941 QAction *storeInProjectAction =
new QAction( tr(
"In Current Project…" ), storeQueryMenu );
942 storeQueryMenu->addAction( storeInProjectAction );
943 storeInProjectAction->setEnabled( !mQueryWidget->sqlEditor()->text().isEmpty() );
944 connect( storeInProjectAction, &QAction::triggered,
this, [
this] {
950 if ( !storedQueries.isEmpty() )
952 QList< QgsStoredQueryManager::QueryDetails > userProfileQueries;
953 std::copy_if( storedQueries.begin(), storedQueries.end(), std::back_inserter( userProfileQueries ), [](
const QgsStoredQueryManager::QueryDetails &details ) {
954 return details.backend == Qgis::QueryStorageBackend::LocalProfile;
957 QList< QgsStoredQueryManager::QueryDetails > projectQueries;
958 std::copy_if( storedQueries.begin(), storedQueries.end(), std::back_inserter( projectQueries ), [](
const QgsStoredQueryManager::QueryDetails &details ) {
959 return details.backend == Qgis::QueryStorageBackend::CurrentProject;
963 for (
const QgsStoredQueryManager::QueryDetails &query : std::as_const( userProfileQueries ) )
965 QAction *action =
new QAction( query.name, mPresetQueryMenu );
966 mPresetQueryMenu->addAction( action );
967 connect( action, &QAction::triggered,
this, [
this, query] {
968 mQueryWidget->sqlEditor()->insertText( query.definition );
971 if ( userProfileQueries.empty() )
973 QAction *action =
new QAction( tr(
"No Stored Queries Available" ), mPresetQueryMenu );
974 action->setEnabled(
false );
975 mPresetQueryMenu->addAction( action );
979 for (
const QgsStoredQueryManager::QueryDetails &query : std::as_const( projectQueries ) )
981 QAction *action =
new QAction( query.name, mPresetQueryMenu );
982 mPresetQueryMenu->addAction( action );
983 connect( action, &QAction::triggered,
this, [
this, query] {
984 mQueryWidget->sqlEditor()->insertText( query.definition );
987 if ( projectQueries.empty() )
989 QAction *action =
new QAction( tr(
"No Stored Queries Available" ), mPresetQueryMenu );
990 action->setEnabled(
false );
991 mPresetQueryMenu->addAction( action );
994 mPresetQueryMenu->addSeparator();
996 QMenu *removeQueryMenu =
new QMenu( tr(
"Removed Stored Query" ), mPresetQueryMenu );
997 mPresetQueryMenu->addMenu( removeQueryMenu );
999 for (
const QgsStoredQueryManager::QueryDetails &query : storedQueries )
1001 QAction *action =
new QAction( tr(
"%1…" ).arg( query.name ), mPresetQueryMenu );
1002 removeQueryMenu->addAction( action );
1003 connect( action, &QAction::triggered,
this, [
this, query] {
1004 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 );
1005 if ( res == QMessageBox::Yes )
1021 QgsNewNameDialog dlg(
1027 dlg.setWindowTitle( tr(
"Store Query" ) );
1028 dlg.setHintString( tr(
"Name for the stored query" ) );
1029 dlg.setOverwriteEnabled(
true );
1030 dlg.setConflictingNameWarning( tr(
"A stored query with this name already exists, it will be overwritten." ) );
1031 dlg.setShowExistingNamesCompleter(
true );
1032 if ( dlg.exec() != QDialog::Accepted )
1035 const QString name = dlg.name();
1036 if ( name.isEmpty() )
1047void QgsQueryResultWidget::showHistoryPanel(
bool show )
1051 mHistoryWidget =
new QgsDatabaseQueryHistoryWidget();
1052 mHistoryWidget->setPanelTitle( tr(
"SQL History" ) );
1053 mPanelStack->showPanel( mHistoryWidget );
1056 Q_UNUSED( connectionUri );
1057 Q_UNUSED( provider );
1059 mQueryWidget->sqlEditor()->setText( sql );
1060 mActionUndo->setEnabled(
false );
1061 mActionRedo->setEnabled(
false );
1062 mHistoryWidget->acceptPanel();
1065 else if ( mHistoryWidget )
1067 mPanelStack->closePanel( mHistoryWidget );
1068 mHistoryWidget->deleteLater();
1075void QgsConnectionsApiFetcher::fetchTokens()
1077 if ( mStopFetching )
1079 emit fetchingFinished();
1087 emit fetchingFinished();
1091 if ( !mStopFetching && connection )
1093 mFeedback = std::make_unique<QgsFeedback>();
1094 QStringList schemas;
1099 schemas = connection->
schemas();
1100 emit tokensReady( schemas );
1109 schemas.push_back( QString() );
1112 for (
const auto &schema : std::as_const( schemas ) )
1114 if ( mStopFetching )
1117 emit fetchingFinished();
1121 QStringList tableNames;
1127 if ( mStopFetching )
1130 emit fetchingFinished();
1133 tableNames.push_back( table.tableName() );
1135 emit tokensReady( tableNames );
1143 for (
const auto &table : std::as_const( tableNames ) )
1145 if ( mStopFetching )
1148 emit fetchingFinished();
1152 QStringList fieldNames;
1155 const QgsFields fields( connection->
fields( schema, table, mFeedback.get() ) );
1156 if ( mStopFetching )
1159 emit fetchingFinished();
1163 for (
const auto &field : std::as_const( fields ) )
1165 fieldNames.push_back( field.name() );
1166 if ( mStopFetching )
1169 emit fetchingFinished();
1173 emit tokensReady( fieldNames );
1184 emit fetchingFinished();
1187void QgsConnectionsApiFetcher::stopFetching()
1191 mFeedback->cancel();
1194QgsQueryResultItemDelegate::QgsQueryResultItemDelegate( QObject *parent )
1195 : QStyledItemDelegate( parent )
1199QString QgsQueryResultItemDelegate::displayText(
const QVariant &value,
const QLocale &locale )
const
1202 QString result { QgsExpressionUtils::toLocalizedString( value ) };
1204 if ( result.length() > 255 )
1206 result.truncate( 255 );
1207 result.append( u
"…"_s );
1221 setObjectName( u
"QgsQueryResultDialog"_s );
1224 mWidget =
new QgsQueryResultWidget(
this, connection );
1225 QVBoxLayout *l =
new QVBoxLayout();
1226 l->setContentsMargins( 6, 6, 6, 6 );
1228 QDialogButtonBox *mButtonBox =
new QDialogButtonBox( QDialogButtonBox::StandardButton::Close | QDialogButtonBox::StandardButton::Help );
1229 connect( mButtonBox, &QDialogButtonBox::rejected,
this, &QDialog::close );
1230 connect( mButtonBox, &QDialogButtonBox::helpRequested,
this, [] {
1233 l->addWidget( mWidget );
1234 l->addWidget( mButtonBox );
1239void QgsQueryResultDialog::closeEvent( QCloseEvent *event )
1241 if ( !mWidget->promptUnsavedChanges() )
1256 : mIdentifierName( identifierName )
1258 setObjectName( u
"SQLCommandsDialog"_s );
1262 mWidget =
new QgsQueryResultWidget(
nullptr, connection );
1263 setCentralWidget( mWidget );
1264 mWidget->layout()->setContentsMargins( 6, 6, 6, 6 );
1266 connect( mWidget, &QgsQueryResultWidget::requestDialogTitleUpdate,
this, &QgsQueryResultMainWindow::updateWindowTitle );
1268 updateWindowTitle( QString() );
1271void QgsQueryResultMainWindow::closeEvent( QCloseEvent *event )
1273 if ( !mWidget->promptUnsavedChanges() )
1283void QgsQueryResultMainWindow::updateWindowTitle(
const QString &fileName )
1285 if ( fileName.isEmpty() )
1287 if ( !mIdentifierName.isEmpty() )
1288 setWindowTitle( tr(
"%1 — Execute SQL" ).arg( mIdentifierName ) );
1290 setWindowTitle( tr(
"Execute SQL" ) );
1294 if ( !mIdentifierName.isEmpty() )
1295 setWindowTitle( tr(
"%1 — %2 — Execute SQL" ).arg( fileName, mIdentifierName ) );
1297 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())
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.