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