40#include <QDialogButtonBox>
42#include <QInputDialog>
46#include "moc_qgsqueryresultwidget.cpp"
49const QgsSettingsEntryString *QgsQueryResultWidget::settingLastSourceFolder =
new QgsSettingsEntryString( QStringLiteral(
"last-source-folder" ), sTreeSqlQueries, QString(), QStringLiteral(
"Last used folder for SQL source files" ) );
66 splitter->setCollapsible( 0,
false );
67 splitter->setCollapsible( 1,
false );
69 splitter->restoreState( settings.
value( QStringLiteral(
"Windows/QueryResult/SplitState" ) ).toByteArray() );
71 connect( splitter, &QSplitter::splitterMoved,
this, [
this] {
73 settings.
setValue( QStringLiteral(
"Windows/QueryResult/SplitState" ), splitter->saveState() );
77 mainLayout->setSpacing( 6 );
78 progressLayout->setSpacing( 6 );
80 mResultsContainer->hide();
81 mQueryResultsTableView->hide();
82 mQueryResultsTableView->setItemDelegate(
new QgsQueryResultItemDelegate( mQueryResultsTableView ) );
83 mQueryResultsTableView->setContextMenuPolicy( Qt::CustomContextMenu );
84 connect( mQueryResultsTableView, &QTableView::customContextMenuRequested,
this, &QgsQueryResultPanelWidget::showCellContextMenu );
90 QVBoxLayout *vl =
new QVBoxLayout();
91 vl->setContentsMargins( 0, 0, 0, 0 );
92 vl->addWidget( mCodeEditorWidget );
93 mSqlEditorContainer->setLayout( vl );
95 connect( mExecuteButton, &QPushButton::pressed,
this, &QgsQueryResultPanelWidget::executeQuery );
97 connect( mLoadLayerPushButton, &QPushButton::pressed,
this, [
this] {
105 const bool res = mConnection->validateSqlVectorLayer( options, message );
108 mMessageBar->pushCritical( QString(), message );
112 emit createSqlVectorLayer( mConnection->providerKey(), mConnection->uri(), options );
117 mMessageBar->pushCritical( tr(
"Error validating query" ), e.
what() );
121 connect( mSqlEditor, &QgsCodeEditorSQL::textChanged,
this, &QgsQueryResultPanelWidget::updateButtons );
123 connect( mSqlEditor, &QgsCodeEditorSQL::selectionChanged,
this, [
this] {
124 mExecuteButton->setText( mSqlEditor->selectedText().isEmpty() ? tr(
"Execute" ) : tr(
"Execute Selection" ) );
126 connect( mFilterToolButton, &QToolButton::pressed,
this, [
this] {
131 std::unique_ptr<QgsVectorLayer> vlayer { mConnection->createSqlVectorLayer( sqlVectorLayerOptions() ) };
133 if ( builder.exec() == QDialog::Accepted )
135 mFilterLineEdit->setText( builder.
sql() );
140 mMessageBar->pushCritical( tr(
"Error opening filter dialog" ), tr(
"There was an error while preparing SQL filter dialog: %1." ).arg( ex.
what() ) );
146 mStatusLabel->hide();
147 mSqlErrorText->hide();
149 mLoadAsNewLayerGroupBox->setCollapsed(
true );
156 mPkColumnsCheckBox->setVisible( showPkConfig );
157 mPkColumnsComboBox->setVisible( showPkConfig );
160 mGeometryColumnCheckBox->setVisible( showGeometryColumnConfig );
161 mGeometryColumnComboBox->setVisible( showGeometryColumnConfig );
164 mFilterLabel->setVisible( showFilterConfig );
165 mFilterToolButton->setVisible( showFilterConfig );
166 mFilterLineEdit->setVisible( showFilterConfig );
169 mAvoidSelectingAsFeatureIdCheckBox->setVisible( showDisableSelectAtId );
173 QShortcut *copySelection =
new QShortcut( QKeySequence::Copy, mQueryResultsTableView );
174 connect( copySelection, &QShortcut::activated,
this, &QgsQueryResultPanelWidget::copySelection );
176 setConnection( connection );
179QgsQueryResultPanelWidget::~QgsQueryResultPanelWidget()
182 cancelRunningQuery();
192 return mCodeEditorWidget;
197 mSqlVectorLayerOptions = options;
198 if ( !options.
sql.isEmpty() )
200 setQuery( options.
sql );
204 mPkColumnsComboBox->setCheckedItems( {} );
209 mGeometryColumnCheckBox->setChecked( !options.
geometryColumn.isEmpty() );
210 mGeometryColumnComboBox->clear();
213 mGeometryColumnComboBox->setCurrentText( options.
geometryColumn );
215 mFilterLineEdit->setText( options.
filter );
216 mLayerNameLineEdit->setText( options.
layerName );
219void QgsQueryResultPanelWidget::setWidgetMode( QgsQueryResultWidget::QueryWidgetMode widgetMode )
221 mQueryWidgetMode = widgetMode;
222 switch ( widgetMode )
224 case QgsQueryResultWidget::QueryWidgetMode::SqlQueryMode:
225 mLoadAsNewLayerGroupBox->setTitle( tr(
"Load as New Layer" ) );
226 mLoadLayerPushButton->setText( tr(
"Load Layer" ) );
227 mLoadAsNewLayerGroupBox->setCollapsed(
true );
229 case QgsQueryResultWidget::QueryWidgetMode::QueryLayerUpdateMode:
230 mLoadAsNewLayerGroupBox->setTitle( tr(
"Update Query Layer" ) );
231 mLoadLayerPushButton->setText( tr(
"Update Layer" ) );
232 mLoadAsNewLayerGroupBox->setCollapsed(
false );
237void QgsQueryResultPanelWidget::executeQuery()
239 mQueryResultsTableView->hide();
240 mSqlErrorText->hide();
241 mResultsContainer->hide();
242 mFirstRowFetched =
false;
244 cancelRunningQuery();
247 const QString sql { mSqlEditor->selectedText().isEmpty() ? mSqlEditor->text() : mSqlEditor->selectedText() };
251 { QStringLiteral(
"query" ), sql },
252 { QStringLiteral(
"provider" ), mConnection->providerKey() },
253 { QStringLiteral(
"connection" ), mConnection->uri() },
257 mWasCanceled =
false;
258 mFeedback = std::make_unique<QgsFeedback>();
259 mStopButton->setEnabled(
true );
260 mStatusLabel->show();
261 mStatusLabel->setText( tr(
"Executing query…" ) );
262 mProgressBar->show();
263 mProgressBar->setRange( 0, 0 );
264 mSqlErrorMessage.clear();
266 connect( mStopButton, &QPushButton::pressed, mFeedback.get(), [
this] {
267 mStatusLabel->setText( tr(
"Stopped" ) );
269 mProgressBar->hide();
274 connect( &mQueryResultWatcher, &QFutureWatcher<QgsAbstractDatabaseProviderConnection::QueryResult>::finished,
this, &QgsQueryResultPanelWidget::startFetching, Qt::ConnectionType::UniqueConnection );
276 QFuture<QgsAbstractDatabaseProviderConnection::QueryResult> future = QtConcurrent::run( [
this, sql]() -> QgsAbstractDatabaseProviderConnection::QueryResult {
279 return mConnection->execSql( sql, mFeedback.get() );
281 catch ( QgsProviderConnectionException &ex )
283 mSqlErrorMessage = ex.
what();
284 return QgsAbstractDatabaseProviderConnection::QueryResult();
287 mQueryResultWatcher.setFuture( future );
291 showError( tr(
"Connection error" ), tr(
"Cannot execute query: connection to the database is not available." ) );
295void QgsQueryResultPanelWidget::updateButtons()
297 mFilterLineEdit->setEnabled( mFirstRowFetched );
298 mFilterToolButton->setEnabled( mFirstRowFetched );
299 const bool isEmpty = mSqlEditor->text().isEmpty();
300 mExecuteButton->setEnabled( !isEmpty );
302 mLoadAsNewLayerGroupBox->setEnabled(
303 mSqlErrorMessage.isEmpty() && mFirstRowFetched
307void QgsQueryResultPanelWidget::showCellContextMenu( QPoint point )
309 const QModelIndex modelIndex = mQueryResultsTableView->indexAt( point );
310 if ( modelIndex.isValid() )
312 QMenu *menu =
new QMenu();
313 menu->setAttribute( Qt::WA_DeleteOnClose );
315 menu->addAction(
QgsApplication::getThemeIcon(
"mActionEditCopy.svg" ), tr(
"Copy" ),
this, [
this] { copySelection(); }, QKeySequence::Copy );
317 menu->exec( mQueryResultsTableView->viewport()->mapToGlobal( point ) );
321void QgsQueryResultPanelWidget::copySelection()
323 const QModelIndexList selection = mQueryResultsTableView->selectionModel()->selectedIndexes();
324 if ( selection.empty() )
331 for (
const QModelIndex &index : selection )
333 if ( minRow == -1 || index.row() < minRow )
334 minRow = index.row();
335 if ( maxRow == -1 || index.row() > maxRow )
336 maxRow = index.row();
337 if ( minCol == -1 || index.column() < minCol )
338 minCol = index.column();
339 if ( maxCol == -1 || index.column() > maxCol )
340 maxCol = index.column();
343 if ( minRow == maxRow && minCol == maxCol )
346 const QString text = mModel->data( selection.at( 0 ), Qt::DisplayRole ).toString();
347 QApplication::clipboard()->setText( text );
351 copyResults( minRow, maxRow, minCol, maxCol );
355void QgsQueryResultPanelWidget::updateSqlLayerColumns()
360 mFilterToolButton->setEnabled(
true );
361 mFilterLineEdit->setEnabled(
true );
362 mPkColumnsComboBox->clear();
363 mGeometryColumnComboBox->clear();
364 const bool hasPkInformation { !mSqlVectorLayerOptions.primaryKeyColumns.isEmpty() };
365 const bool hasGeomColInformation { !mSqlVectorLayerOptions.geometryColumn.isEmpty() };
366 static const QStringList geomColCandidates { QStringLiteral(
"geom" ), QStringLiteral(
"geometry" ), QStringLiteral(
"the_geom" ) };
367 const QStringList constCols { mModel->columns() };
368 for (
const QString &
c : constCols )
370 const bool pkCheckedState = hasPkInformation ? mSqlVectorLayerOptions.primaryKeyColumns.contains(
c ) :
c.contains( QStringLiteral(
"id" ), Qt::CaseSensitivity::CaseInsensitive );
372 mPkColumnsComboBox->addItemWithCheckState(
c, pkCheckedState && mPkColumnsComboBox->checkedItems().isEmpty() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked );
373 mGeometryColumnComboBox->addItem(
c );
374 if ( !hasGeomColInformation && geomColCandidates.contains(
c, Qt::CaseSensitivity::CaseInsensitive ) )
376 mGeometryColumnComboBox->setCurrentText(
c );
379 mPkColumnsCheckBox->setChecked( hasPkInformation );
380 mGeometryColumnCheckBox->setChecked( hasGeomColInformation );
381 if ( hasGeomColInformation )
383 mGeometryColumnComboBox->setCurrentText( mSqlVectorLayerOptions.geometryColumn );
387void QgsQueryResultPanelWidget::cancelRunningQuery()
396 if ( mQueryResultWatcher.isRunning() )
398 mQueryResultWatcher.waitForFinished();
402void QgsQueryResultPanelWidget::cancelApiFetcher()
406 mApiFetcher->stopFetching();
411void QgsQueryResultPanelWidget::startFetching()
415 if ( !mSqlErrorMessage.isEmpty() )
417 showError( tr(
"SQL error" ), mSqlErrorMessage,
true );
423 mStatusLabel->setText( QStringLiteral(
"Query executed successfully (%1 rows, %2 ms)" )
424 .arg( QLocale().toString( mQueryResultWatcher.result().rowCount() ), QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() ) ) );
428 mStatusLabel->setText( QStringLiteral(
"Query executed successfully (%1 s)" ).arg( QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() ) ) );
430 mProgressBar->hide();
431 mModel = std::make_unique<QgsQueryResultModel>( mQueryResultWatcher.result() );
437 connect( mModel.get(), &QgsQueryResultModel::fetchMoreRows,
this, [
this](
long long maxRows ) {
438 mFetchedRowsBatchCount = 0;
439 mProgressBar->setRange( 0, maxRows );
440 mProgressBar->show();
443 connect( mModel.get(), &QgsQueryResultModel::rowsInserted,
this, [
this](
const QModelIndex &,
int first,
int last ) {
444 if ( !mFirstRowFetched )
446 emit firstResultBatchFetched();
447 mFirstRowFetched = true;
448 mQueryResultsTableView->show();
449 mResultsContainer->show();
451 updateSqlLayerColumns();
452 mActualRowCount = mModel->queryResult().rowCount();
454 mStatusLabel->setText( tr(
"Fetched rows: %1/%2 %3 %4 ms" )
455 .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() ) ) );
456 mFetchedRowsBatchCount += last - first + 1;
457 mProgressBar->setValue( mFetchedRowsBatchCount );
460 mQueryResultsTableView->setModel( mModel.get() );
461 mQueryResultsTableView->show();
462 mResultsContainer->show();
464 connect( mModel.get(), &QgsQueryResultModel::fetchingComplete, mStopButton, [
this] {
466 const QgsHistoryEntry currentHistoryEntry = QgsGui::historyProviderRegistry()->entry( mCurrentHistoryEntryId, ok );
467 QVariantMap entryDetails = currentHistoryEntry.entry;
468 entryDetails.insert( QStringLiteral(
"rows" ), mActualRowCount );
469 entryDetails.insert( QStringLiteral(
"time" ), mQueryResultWatcher.result().queryExecutionTime() );
471 QgsGui::historyProviderRegistry()->updateEntry( mCurrentHistoryEntryId, entryDetails );
472 mProgressBar->hide();
473 mStopButton->setEnabled( false );
479 mStatusLabel->setText( tr(
"SQL command aborted" ) );
480 mProgressBar->hide();
484void QgsQueryResultPanelWidget::showError(
const QString &title,
const QString &message,
bool isSqlError )
486 mStatusLabel->show();
487 mStatusLabel->setText( tr(
"An error occurred while executing the query" ) );
488 mProgressBar->hide();
489 mQueryResultsTableView->hide();
492 mSqlErrorText->show();
493 mSqlErrorText->setText( message );
494 mResultsContainer->show();
498 mMessageBar->pushCritical( title, message );
499 mResultsContainer->hide();
503void QgsQueryResultPanelWidget::tokensReady(
const QStringList &tokens )
505 mSqlEditor->setExtraKeywords( mSqlEditor->extraKeywords() + tokens );
506 mSqlErrorText->setExtraKeywords( mSqlErrorText->extraKeywords() + tokens );
509void QgsQueryResultPanelWidget::copyResults()
511 const int rowCount = mModel->rowCount( QModelIndex() );
512 const int columnCount = mModel->columnCount( QModelIndex() );
513 copyResults( 0, rowCount - 1, 0, columnCount - 1 );
516void QgsQueryResultPanelWidget::copyResults(
int fromRow,
int toRow,
int fromColumn,
int toColumn )
518 QStringList rowStrings;
519 QStringList columnStrings;
521 const int rowCount = mModel->rowCount( QModelIndex() );
522 const int columnCount = mModel->columnCount( QModelIndex() );
524 toRow = std::min( toRow, rowCount - 1 );
525 toColumn = std::min( toColumn, columnCount - 1 );
527 rowStrings.reserve( toRow - fromRow );
530 for (
int col = fromColumn; col <= toColumn; col++ )
532 columnStrings += mModel->headerData( col, Qt::Horizontal, Qt::DisplayRole ).toString();
534 rowStrings += columnStrings.join( QLatin1Char(
'\t' ) );
535 columnStrings.clear();
537 for (
int row = fromRow; row <= toRow; row++ )
539 for (
int col = fromColumn; col <= toColumn; col++ )
541 columnStrings += mModel->data( mModel->index( row, col ), Qt::DisplayRole ).toString();
543 rowStrings += columnStrings.join( QLatin1Char(
'\t' ) );
544 columnStrings.clear();
547 if ( !rowStrings.isEmpty() )
549 const QString text = rowStrings.join( QLatin1Char(
'\n' ) );
550 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 );
551 html.replace( QLatin1String(
"\t" ), QLatin1String(
"</td><td>" ) ).replace( QLatin1String(
"\n" ), QLatin1String(
"</td></tr><tr><td>" ) );
553 QMimeData *mdata =
new QMimeData();
554 mdata->setData( QStringLiteral(
"text/html" ), html.toUtf8() );
555 if ( !text.isEmpty() )
557 mdata->setText( text );
561 QApplication::clipboard()->setMimeData( mdata, QClipboard::Selection );
563 QApplication::clipboard()->setMimeData( mdata, QClipboard::Clipboard );
569 const thread_local QRegularExpression rx( QStringLiteral(
";\\s*$" ) );
570 mSqlVectorLayerOptions.sql = mSqlEditor->text().replace( rx, QString() );
571 mSqlVectorLayerOptions.filter = mFilterLineEdit->text();
572 mSqlVectorLayerOptions.primaryKeyColumns = mPkColumnsComboBox->checkedItems();
573 mSqlVectorLayerOptions.geometryColumn = mGeometryColumnComboBox->currentText();
574 mSqlVectorLayerOptions.layerName = mLayerNameLineEdit->text();
575 mSqlVectorLayerOptions.disableSelectAtId = mAvoidSelectingAsFeatureIdCheckBox->isChecked();
576 QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions options { mSqlVectorLayerOptions };
578 if ( !mPkColumnsCheckBox->isChecked() )
582 if ( !mGeometryColumnCheckBox->isChecked() )
591 mConnection.reset( connection );
598 const QMultiMap<Qgis::SqlKeywordCategory, QStringList> keywordsDict { connection->
sqlDictionary() };
599 QStringList keywords;
600 for (
auto it = keywordsDict.constBegin(); it != keywordsDict.constEnd(); it++ )
602 keywords.append( it.value() );
606 mSqlEditor->setExtraKeywords( keywords );
607 mSqlErrorText->setExtraKeywords( keywords );
610 QThread *apiFetcherWorkerThread =
new QThread();
611 QgsConnectionsApiFetcher *apiFetcher =
new QgsConnectionsApiFetcher( mConnection->uri(), mConnection->providerKey() );
612 apiFetcher->moveToThread( apiFetcherWorkerThread );
613 connect( apiFetcherWorkerThread, &QThread::started, apiFetcher, &QgsConnectionsApiFetcher::fetchTokens );
614 connect( apiFetcher, &QgsConnectionsApiFetcher::tokensReady,
this, &QgsQueryResultPanelWidget::tokensReady );
615 connect( apiFetcher, &QgsConnectionsApiFetcher::fetchingFinished, apiFetcherWorkerThread, [apiFetcher, apiFetcherWorkerThread] {
616 apiFetcherWorkerThread->quit();
617 apiFetcherWorkerThread->wait();
618 apiFetcherWorkerThread->deleteLater();
619 apiFetcher->deleteLater();
622 mApiFetcher = apiFetcher;
623 apiFetcherWorkerThread->start();
629void QgsQueryResultPanelWidget::setQuery(
const QString &sql )
631 mSqlEditor->setText( sql );
634void QgsQueryResultPanelWidget::notify(
const QString &title,
const QString &text,
Qgis::MessageLevel level )
636 mMessageBar->pushMessage( title, text, level );
642 QgsNewNameDialog dlg(
648 dlg.setWindowTitle( tr(
"Store Query" ) );
649 dlg.setHintString( tr(
"Name for the stored query" ) );
650 dlg.setOverwriteEnabled(
true );
651 dlg.setConflictingNameWarning( tr(
"A stored query with this name already exists, it will be overwritten." ) );
652 dlg.setShowExistingNamesCompleter(
true );
653 if ( dlg.exec() != QDialog::Accepted )
656 const QString name = dlg.name();
657 if ( name.isEmpty() )
678 mQueryWidget =
new QgsQueryResultPanelWidget(
nullptr, connection );
679 mPanelStack->setMainPanel( mQueryWidget );
681 mPresetQueryMenu =
new QMenu(
this );
682 connect( mPresetQueryMenu, &QMenu::aboutToShow,
this, &QgsQueryResultWidget::populatePresetQueryMenu );
684 QToolButton *presetQueryButton =
new QToolButton();
685 presetQueryButton->setMenu( mPresetQueryMenu );
687 presetQueryButton->setPopupMode( QToolButton::InstantPopup );
688 mToolBar->addWidget( presetQueryButton );
690 connect( mActionOpenQuery, &QAction::triggered,
this, &QgsQueryResultWidget::openQuery );
691 connect( mActionSaveQuery, &QAction::triggered,
this, [
this] { saveQuery(
false ); } );
692 connect( mActionSaveQueryAs, &QAction::triggered,
this, [
this] { saveQuery(
true ); } );
694 connect( mActionCut, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::cut );
695 connect( mActionCopy, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::copy );
696 connect( mActionPaste, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::paste );
697 connect( mActionUndo, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::undo );
698 connect( mActionRedo, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::redo );
699 mActionUndo->setEnabled(
false );
700 mActionRedo->setEnabled(
false );
704 connect( mQueryWidget->sqlEditor(), &QgsCodeEditor::modificationChanged,
this, &QgsQueryResultWidget::setHasChanged );
706 connect( mActionShowHistory, &QAction::toggled,
this, &QgsQueryResultWidget::showHistoryPanel );
708 connect( mActionClear, &QAction::triggered,
this, [
this] {
710 mQueryWidget->sqlEditor()->SendScintilla( QsciScintilla::SCI_SETTEXT,
"" );
713 connect( mQueryWidget->sqlEditor(), &QgsCodeEditorSQL::textChanged,
this, &QgsQueryResultWidget::updateButtons );
715 connect( mQueryWidget->sqlEditor(), &QgsCodeEditorSQL::copyAvailable, mActionCut, &QAction::setEnabled );
716 connect( mQueryWidget->sqlEditor(), &QgsCodeEditorSQL::copyAvailable, mActionCopy, &QAction::setEnabled );
718 connect( mQueryWidget, &QgsQueryResultPanelWidget::createSqlVectorLayer,
this, &QgsQueryResultWidget::createSqlVectorLayer );
719 connect( mQueryWidget, &QgsQueryResultPanelWidget::firstResultBatchFetched,
this, &QgsQueryResultWidget::firstResultBatchFetched );
722 setHasChanged(
false );
725QgsQueryResultWidget::~QgsQueryResultWidget()
727 if ( mHistoryWidget )
729 mPanelStack->closePanel( mHistoryWidget );
730 mHistoryWidget->deleteLater();
736 if ( !options.
sql.isEmpty() )
738 setQuery( options.
sql );
740 mQueryWidget->setSqlVectorLayerOptions( options );
743void QgsQueryResultWidget::setWidgetMode( QueryWidgetMode widgetMode )
745 mQueryWidget->setWidgetMode( widgetMode );
748void QgsQueryResultWidget::executeQuery()
750 mQueryWidget->executeQuery();
753void QgsQueryResultWidget::updateButtons()
755 mQueryWidget->updateButtons();
757 const bool isEmpty = mQueryWidget->sqlEditor()->text().isEmpty();
758 mActionClear->setEnabled( !isEmpty );
759 mActionUndo->setEnabled( mQueryWidget->sqlEditor()->isUndoAvailable() );
760 mActionRedo->setEnabled( mQueryWidget->sqlEditor()->isRedoAvailable() );
763void QgsQueryResultWidget::showError(
const QString &title,
const QString &message,
bool isSqlError )
765 mQueryWidget->showError( title, message, isSqlError );
768void QgsQueryResultWidget::tokensReady(
const QStringList & )
772void QgsQueryResultWidget::copyResults()
774 mQueryWidget->copyResults();
777void QgsQueryResultWidget::copyResults(
int fromRow,
int toRow,
int fromColumn,
int toColumn )
779 mQueryWidget->copyResults( fromRow, toRow, fromColumn, toColumn );
782void QgsQueryResultWidget::openQuery()
784 if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() && mHasChangedFileContents )
786 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 )
790 QString initialDir = settingLastSourceFolder->value();
791 if ( initialDir.isEmpty() )
792 initialDir = QDir::homePath();
794 const QString fileName = QFileDialog::getOpenFileName(
this, tr(
"Open Query" ), initialDir, tr(
"SQL queries (*.sql *.SQL)" ) + QStringLiteral(
";;" ) + QObject::tr(
"All files" ) + QStringLiteral(
" (*.*)" ) );
796 if ( fileName.isEmpty() )
799 QFileInfo fi( fileName );
800 settingLastSourceFolder->setValue( fi.path() );
802 QgsTemporaryCursorOverride cursor( Qt::CursorShape::WaitCursor );
804 mQueryWidget->codeEditorWidget()->loadFile( fileName );
805 setHasChanged(
false );
808void QgsQueryResultWidget::saveQuery(
bool saveAs )
810 if ( mQueryWidget->codeEditorWidget()->filePath().isEmpty() || saveAs )
812 QString selectedFilter;
814 QString initialDir = settingLastSourceFolder->value();
815 if ( initialDir.isEmpty() )
816 initialDir = QDir::homePath();
818 QString newPath = QFileDialog::getSaveFileName(
822 tr(
"SQL queries (*.sql *.SQL)" ) + QStringLiteral(
";;" ) + QObject::tr(
"All files" ) + QStringLiteral(
" (*.*)" ),
826 if ( !newPath.isEmpty() )
828 QFileInfo fi( newPath );
829 settingLastSourceFolder->setValue( fi.path() );
831 if ( !selectedFilter.contains( QStringLiteral(
"*.*)" ) ) )
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(
866 "There are unsaved changes in this query. Do you want to save those?"
868 QMessageBox::StandardButton::Save
869 | QMessageBox::StandardButton::Cancel
870 | QMessageBox::StandardButton::Discard,
871 QMessageBox::StandardButton::Cancel
874 if ( ret == QMessageBox::StandardButton::Save )
879 else if ( ret == QMessageBox::StandardButton::Discard )
895void QgsQueryResultWidget::notify(
const QString &title,
const QString &text,
Qgis::MessageLevel level )
897 mQueryWidget->notify( title, text, level );
901void QgsQueryResultWidget::setHasChanged(
bool hasChanged )
903 mActionSaveQuery->setEnabled( hasChanged );
904 mHasChangedFileContents = hasChanged;
908void QgsQueryResultWidget::updateDialogTitle()
911 if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() )
913 const QFileInfo fi( mQueryWidget->codeEditorWidget()->filePath() );
914 fileName = fi.fileName();
915 if ( mHasChangedFileContents )
917 fileName.prepend(
'*' );
921 emit requestDialogTitleUpdate( fileName );
924void QgsQueryResultWidget::populatePresetQueryMenu()
926 mPresetQueryMenu->clear();
928 QMenu *storeQueryMenu =
new QMenu( tr(
"Store Current Query" ), mPresetQueryMenu );
929 mPresetQueryMenu->addMenu( storeQueryMenu );
930 QAction *storeInProfileAction =
new QAction( tr(
"In User Profile…" ), storeQueryMenu );
931 storeQueryMenu->addAction( storeInProfileAction );
932 storeInProfileAction->setEnabled( !mQueryWidget->sqlEditor()->text().isEmpty() );
933 connect( storeInProfileAction, &QAction::triggered,
this, [
this] {
936 QAction *storeInProjectAction =
new QAction( tr(
"In Current Project…" ), storeQueryMenu );
937 storeQueryMenu->addAction( storeInProjectAction );
938 storeInProjectAction->setEnabled( !mQueryWidget->sqlEditor()->text().isEmpty() );
939 connect( storeInProjectAction, &QAction::triggered,
this, [
this] {
945 if ( !storedQueries.isEmpty() )
947 QList< QgsStoredQueryManager::QueryDetails > userProfileQueries;
948 std::copy_if( storedQueries.begin(), storedQueries.end(), std::back_inserter( userProfileQueries ), [](
const QgsStoredQueryManager::QueryDetails &details ) {
949 return details.backend == Qgis::QueryStorageBackend::LocalProfile;
952 QList< QgsStoredQueryManager::QueryDetails > projectQueries;
953 std::copy_if( storedQueries.begin(), storedQueries.end(), std::back_inserter( projectQueries ), [](
const QgsStoredQueryManager::QueryDetails &details ) {
954 return details.backend == Qgis::QueryStorageBackend::CurrentProject;
958 for (
const QgsStoredQueryManager::QueryDetails &query : std::as_const( userProfileQueries ) )
960 QAction *action =
new QAction( query.name, mPresetQueryMenu );
961 mPresetQueryMenu->addAction( action );
962 connect( action, &QAction::triggered,
this, [
this, query] {
963 mQueryWidget->sqlEditor()->insertText( query.definition );
966 if ( userProfileQueries.empty() )
968 QAction *action =
new QAction( tr(
"No Stored Queries Available" ), mPresetQueryMenu );
969 action->setEnabled(
false );
970 mPresetQueryMenu->addAction( action );
974 for (
const QgsStoredQueryManager::QueryDetails &query : std::as_const( projectQueries ) )
976 QAction *action =
new QAction( query.name, mPresetQueryMenu );
977 mPresetQueryMenu->addAction( action );
978 connect( action, &QAction::triggered,
this, [
this, query] {
979 mQueryWidget->sqlEditor()->insertText( query.definition );
982 if ( projectQueries.empty() )
984 QAction *action =
new QAction( tr(
"No Stored Queries Available" ), mPresetQueryMenu );
985 action->setEnabled(
false );
986 mPresetQueryMenu->addAction( action );
989 mPresetQueryMenu->addSeparator();
991 QMenu *removeQueryMenu =
new QMenu( tr(
"Removed Stored Query" ), mPresetQueryMenu );
992 mPresetQueryMenu->addMenu( removeQueryMenu );
994 for (
const QgsStoredQueryManager::QueryDetails &query : storedQueries )
996 QAction *action =
new QAction( tr(
"%1…" ).arg( query.name ), mPresetQueryMenu );
997 removeQueryMenu->addAction( action );
998 connect( action, &QAction::triggered,
this, [
this, query] {
999 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 );
1000 if ( res == QMessageBox::Yes )
1016 QgsNewNameDialog dlg(
1022 dlg.setWindowTitle( tr(
"Store Query" ) );
1023 dlg.setHintString( tr(
"Name for the stored query" ) );
1024 dlg.setOverwriteEnabled(
true );
1025 dlg.setConflictingNameWarning( tr(
"A stored query with this name already exists, it will be overwritten." ) );
1026 dlg.setShowExistingNamesCompleter(
true );
1027 if ( dlg.exec() != QDialog::Accepted )
1030 const QString name = dlg.name();
1031 if ( name.isEmpty() )
1042void QgsQueryResultWidget::showHistoryPanel(
bool show )
1048 mHistoryWidget =
new QgsDatabaseQueryHistoryWidget();
1049 mHistoryWidget->setPanelTitle( tr(
"SQL History" ) );
1050 mPanelStack->showPanel( mHistoryWidget );
1053 Q_UNUSED( connectionUri );
1054 Q_UNUSED( provider );
1056 mQueryWidget->sqlEditor()->setText( sql );
1057 mActionUndo->setEnabled(
false );
1058 mActionRedo->setEnabled(
false );
1059 mHistoryWidget->acceptPanel();
1062 else if ( mHistoryWidget )
1064 mPanelStack->closePanel( mHistoryWidget );
1065 mHistoryWidget->deleteLater();
1073void QgsConnectionsApiFetcher::fetchTokens()
1075 if ( mStopFetching )
1077 emit fetchingFinished();
1085 emit fetchingFinished();
1089 if ( !mStopFetching && connection )
1091 mFeedback = std::make_unique<QgsFeedback>();
1092 QStringList schemas;
1097 schemas = connection->
schemas();
1098 emit tokensReady( schemas );
1107 schemas.push_back( QString() );
1110 for (
const auto &schema : std::as_const( schemas ) )
1112 if ( mStopFetching )
1115 emit fetchingFinished();
1119 QStringList tableNames;
1125 if ( mStopFetching )
1128 emit fetchingFinished();
1131 tableNames.push_back( table.tableName() );
1133 emit tokensReady( tableNames );
1141 for (
const auto &table : std::as_const( tableNames ) )
1143 if ( mStopFetching )
1146 emit fetchingFinished();
1150 QStringList fieldNames;
1153 const QgsFields fields( connection->
fields( schema, table, mFeedback.get() ) );
1154 if ( mStopFetching )
1157 emit fetchingFinished();
1161 for (
const auto &field : std::as_const( fields ) )
1163 fieldNames.push_back( field.name() );
1164 if ( mStopFetching )
1167 emit fetchingFinished();
1171 emit tokensReady( fieldNames );
1182 emit fetchingFinished();
1185void QgsConnectionsApiFetcher::stopFetching()
1189 mFeedback->cancel();
1192QgsQueryResultItemDelegate::QgsQueryResultItemDelegate( QObject *parent )
1193 : QStyledItemDelegate( parent )
1197QString QgsQueryResultItemDelegate::displayText(
const QVariant &value,
const QLocale &locale )
const
1200 QString result { QgsExpressionUtils::toLocalizedString( value ) };
1202 if ( result.length() > 255 )
1204 result.truncate( 255 );
1205 result.append( QStringLiteral(
"…" ) );
1219 setObjectName( QStringLiteral(
"QgsQueryResultDialog" ) );
1222 mWidget =
new QgsQueryResultWidget(
this, connection );
1223 QVBoxLayout *l =
new QVBoxLayout();
1224 l->setContentsMargins( 6, 6, 6, 6 );
1226 QDialogButtonBox *mButtonBox =
new QDialogButtonBox( QDialogButtonBox::StandardButton::Close | QDialogButtonBox::StandardButton::Help );
1227 connect( mButtonBox, &QDialogButtonBox::rejected,
this, &QDialog::close );
1228 connect( mButtonBox, &QDialogButtonBox::helpRequested,
this, [] {
1229 QgsHelp::openHelp( QStringLiteral(
"managing_data_source/create_layers.html#execute-sql" ) );
1231 l->addWidget( mWidget );
1232 l->addWidget( mButtonBox );
1237void QgsQueryResultDialog::closeEvent( QCloseEvent *event )
1239 if ( !mWidget->promptUnsavedChanges() )
1254 : mIdentifierName( identifierName )
1256 setObjectName( QStringLiteral(
"SQLCommandsDialog" ) );
1260 mWidget =
new QgsQueryResultWidget(
nullptr, connection );
1261 setCentralWidget( mWidget );
1262 mWidget->layout()->setContentsMargins( 6, 6, 6, 6 );
1264 connect( mWidget, &QgsQueryResultWidget::requestDialogTitleUpdate,
this, &QgsQueryResultMainWindow::updateWindowTitle );
1266 updateWindowTitle( QString() );
1269void QgsQueryResultMainWindow::closeEvent( QCloseEvent *event )
1271 if ( !mWidget->promptUnsavedChanges() )
1281void QgsQueryResultMainWindow::updateWindowTitle(
const QString &fileName )
1283 if ( fileName.isEmpty() )
1285 if ( !mIdentifierName.isEmpty() )
1286 setWindowTitle( tr(
"%1 — Execute SQL" ).arg( mIdentifierName ) );
1288 setWindowTitle( tr(
"Execute SQL" ) );
1292 if ( !mIdentifierName.isEmpty() )
1293 setWindowTitle( tr(
"%1 — %2 — Execute SQL" ).arg( fileName, mIdentifierName ) );
1295 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.