40#include <QDialogButtonBox>
42#include <QInputDialog>
47#include "moc_qgsqueryresultwidget.cpp"
49using namespace Qt::StringLiterals;
69 splitter->setCollapsible( 0,
false );
70 splitter->setCollapsible( 1,
false );
72 splitter->restoreState( settings.
value( u
"Windows/QueryResult/SplitState"_s ).toByteArray() );
74 connect( splitter, &QSplitter::splitterMoved,
this, [
this] {
76 settings.
setValue( u
"Windows/QueryResult/SplitState"_s, splitter->saveState() );
80 mainLayout->setSpacing( 6 );
81 progressLayout->setSpacing( 6 );
83 mResultsContainer->hide();
84 mQueryResultsTableView->hide();
85 mQueryResultsTableView->setItemDelegate(
new QgsQueryResultItemDelegate( mQueryResultsTableView ) );
86 mQueryResultsTableView->setContextMenuPolicy( Qt::CustomContextMenu );
87 connect( mQueryResultsTableView, &QTableView::customContextMenuRequested,
this, &QgsQueryResultPanelWidget::showCellContextMenu );
93 QVBoxLayout *vl =
new QVBoxLayout();
94 vl->setContentsMargins( 0, 0, 0, 0 );
95 vl->addWidget( mCodeEditorWidget );
96 mSqlEditorContainer->setLayout( vl );
98 connect( mExecuteButton, &QPushButton::pressed,
this, &QgsQueryResultPanelWidget::executeQuery );
100 connect( mLoadLayerPushButton, &QPushButton::pressed,
this, [
this] {
108 const bool res = mConnection->validateSqlVectorLayer( options, message );
111 mMessageBar->pushCritical( QString(), message );
115 emit createSqlVectorLayer( mConnection->providerKey(), mConnection->uri(), options );
120 mMessageBar->pushCritical( tr(
"Error validating query" ), e.
what() );
124 connect( mSqlEditor, &QgsCodeEditorSQL::textChanged,
this, &QgsQueryResultPanelWidget::updateButtons );
126 connect( mSqlEditor, &QgsCodeEditorSQL::selectionChanged,
this, [
this] {
127 mExecuteButton->setText( mSqlEditor->selectedText().isEmpty() ? tr(
"Execute" ) : tr(
"Execute Selection" ) );
129 connect( mFilterToolButton, &QToolButton::pressed,
this, [
this] {
134 std::unique_ptr<QgsVectorLayer> vlayer { mConnection->createSqlVectorLayer( sqlVectorLayerOptions() ) };
136 if ( builder.exec() == QDialog::Accepted )
138 mFilterLineEdit->setText( builder.
sql() );
143 mMessageBar->pushCritical( tr(
"Error opening filter dialog" ), tr(
"There was an error while preparing SQL filter dialog: %1." ).arg( ex.
what() ) );
149 mStatusLabel->hide();
150 mSqlErrorText->hide();
152 mLoadAsNewLayerGroupBox->setCollapsed(
true );
159 mPkColumnsCheckBox->setVisible( showPkConfig );
160 mPkColumnsComboBox->setVisible( showPkConfig );
163 mGeometryColumnCheckBox->setVisible( showGeometryColumnConfig );
164 mGeometryColumnComboBox->setVisible( showGeometryColumnConfig );
167 mFilterLabel->setVisible( showFilterConfig );
168 mFilterToolButton->setVisible( showFilterConfig );
169 mFilterLineEdit->setVisible( showFilterConfig );
172 mAvoidSelectingAsFeatureIdCheckBox->setVisible( showDisableSelectAtId );
176 QShortcut *copySelection =
new QShortcut( QKeySequence::Copy, mQueryResultsTableView );
177 connect( copySelection, &QShortcut::activated,
this, &QgsQueryResultPanelWidget::copySelection );
179 setConnection( connection );
182QgsQueryResultPanelWidget::~QgsQueryResultPanelWidget()
185 cancelRunningQuery();
195 return mCodeEditorWidget;
200 mSqlVectorLayerOptions = options;
201 if ( !options.
sql.isEmpty() )
203 setQuery( options.
sql );
207 mPkColumnsComboBox->setCheckedItems( {} );
212 mGeometryColumnCheckBox->setChecked( !options.
geometryColumn.isEmpty() );
213 mGeometryColumnComboBox->clear();
216 mGeometryColumnComboBox->setCurrentText( options.
geometryColumn );
218 mFilterLineEdit->setText( options.
filter );
219 mLayerNameLineEdit->setText( options.
layerName );
222void QgsQueryResultPanelWidget::setWidgetMode( QgsQueryResultWidget::QueryWidgetMode widgetMode )
224 mQueryWidgetMode = widgetMode;
225 switch ( widgetMode )
227 case QgsQueryResultWidget::QueryWidgetMode::SqlQueryMode:
228 mLoadAsNewLayerGroupBox->setTitle( tr(
"Load as New Layer" ) );
229 mLoadLayerPushButton->setText( tr(
"Load Layer" ) );
230 mLoadAsNewLayerGroupBox->setCollapsed(
true );
232 case QgsQueryResultWidget::QueryWidgetMode::QueryLayerUpdateMode:
233 mLoadAsNewLayerGroupBox->setTitle( tr(
"Update Query Layer" ) );
234 mLoadLayerPushButton->setText( tr(
"Update Layer" ) );
235 mLoadAsNewLayerGroupBox->setCollapsed(
false );
240void QgsQueryResultPanelWidget::executeQuery()
242 mQueryResultsTableView->hide();
243 mSqlErrorText->hide();
244 mResultsContainer->hide();
245 mFirstRowFetched =
false;
247 cancelRunningQuery();
250 const QString sql { mSqlEditor->selectedText().isEmpty() ? mSqlEditor->text() : mSqlEditor->selectedText() };
255 { u
"provider"_s, mConnection->providerKey() },
256 { u
"connection"_s, mConnection->uri() },
260 mWasCanceled =
false;
261 mFeedback = std::make_unique<QgsFeedback>();
262 mStopButton->setEnabled(
true );
263 mStatusLabel->show();
264 mStatusLabel->setText( tr(
"Executing query…" ) );
265 mProgressBar->show();
266 mProgressBar->setRange( 0, 0 );
267 mSqlErrorMessage.clear();
269 connect( mStopButton, &QPushButton::pressed, mFeedback.get(), [
this] {
270 mStatusLabel->setText( tr(
"Stopped" ) );
272 mProgressBar->hide();
277 connect( &mQueryResultWatcher, &QFutureWatcher<QgsAbstractDatabaseProviderConnection::QueryResult>::finished,
this, &QgsQueryResultPanelWidget::startFetching, Qt::ConnectionType::UniqueConnection );
279 QFuture<QgsAbstractDatabaseProviderConnection::QueryResult> future = QtConcurrent::run( [
this, sql]() -> QgsAbstractDatabaseProviderConnection::QueryResult {
282 return mConnection->execSql( sql, mFeedback.get() );
284 catch ( QgsProviderConnectionException &ex )
286 mSqlErrorMessage = ex.
what();
287 return QgsAbstractDatabaseProviderConnection::QueryResult();
290 mQueryResultWatcher.setFuture( future );
294 showError( tr(
"Connection error" ), tr(
"Cannot execute query: connection to the database is not available." ) );
298void QgsQueryResultPanelWidget::updateButtons()
300 mFilterLineEdit->setEnabled( mFirstRowFetched );
301 mFilterToolButton->setEnabled( mFirstRowFetched );
302 const bool isEmpty = mSqlEditor->text().isEmpty();
303 mExecuteButton->setEnabled( !isEmpty );
305 mLoadAsNewLayerGroupBox->setEnabled(
306 mSqlErrorMessage.isEmpty() && mFirstRowFetched
310void QgsQueryResultPanelWidget::showCellContextMenu( QPoint point )
312 const QModelIndex modelIndex = mQueryResultsTableView->indexAt( point );
313 if ( modelIndex.isValid() )
315 QMenu *menu =
new QMenu();
316 menu->setAttribute( Qt::WA_DeleteOnClose );
318 menu->addAction(
QgsApplication::getThemeIcon(
"mActionEditCopy.svg" ), tr(
"Copy" ),
this, [
this] { copySelection(); }, QKeySequence::Copy );
320 menu->exec( mQueryResultsTableView->viewport()->mapToGlobal( point ) );
324void QgsQueryResultPanelWidget::copySelection()
326 const QModelIndexList selection = mQueryResultsTableView->selectionModel()->selectedIndexes();
327 if ( selection.empty() )
334 for (
const QModelIndex &index : selection )
336 if ( minRow == -1 || index.row() < minRow )
337 minRow = index.row();
338 if ( maxRow == -1 || index.row() > maxRow )
339 maxRow = index.row();
340 if ( minCol == -1 || index.column() < minCol )
341 minCol = index.column();
342 if ( maxCol == -1 || index.column() > maxCol )
343 maxCol = index.column();
346 if ( minRow == maxRow && minCol == maxCol )
349 const QString text = mModel->data( selection.at( 0 ), Qt::DisplayRole ).toString();
350 QApplication::clipboard()->setText( text );
354 copyResults( minRow, maxRow, minCol, maxCol );
358void QgsQueryResultPanelWidget::updateSqlLayerColumns()
363 mFilterToolButton->setEnabled(
true );
364 mFilterLineEdit->setEnabled(
true );
365 mPkColumnsComboBox->clear();
366 mGeometryColumnComboBox->clear();
367 const bool hasPkInformation { !mSqlVectorLayerOptions.primaryKeyColumns.isEmpty() };
368 const bool hasGeomColInformation { !mSqlVectorLayerOptions.geometryColumn.isEmpty() };
369 static const QStringList geomColCandidates { u
"geom"_s, u
"geometry"_s, u
"the_geom"_s };
370 const QStringList constCols { mModel->columns() };
371 for (
const QString &
c : constCols )
373 const bool pkCheckedState = hasPkInformation ? mSqlVectorLayerOptions.primaryKeyColumns.contains(
c ) :
c.contains( u
"id"_s, Qt::CaseSensitivity::CaseInsensitive );
375 mPkColumnsComboBox->addItemWithCheckState(
c, pkCheckedState && mPkColumnsComboBox->checkedItems().isEmpty() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked );
376 mGeometryColumnComboBox->addItem(
c );
377 if ( !hasGeomColInformation && geomColCandidates.contains(
c, Qt::CaseSensitivity::CaseInsensitive ) )
379 mGeometryColumnComboBox->setCurrentText(
c );
382 mPkColumnsCheckBox->setChecked( hasPkInformation );
383 mGeometryColumnCheckBox->setChecked( hasGeomColInformation );
384 if ( hasGeomColInformation )
386 mGeometryColumnComboBox->setCurrentText( mSqlVectorLayerOptions.geometryColumn );
390void QgsQueryResultPanelWidget::cancelRunningQuery()
399 if ( mQueryResultWatcher.isRunning() )
401 mQueryResultWatcher.waitForFinished();
405void QgsQueryResultPanelWidget::cancelApiFetcher()
409 mApiFetcher->stopFetching();
414void QgsQueryResultPanelWidget::startFetching()
418 if ( !mSqlErrorMessage.isEmpty() )
420 showError( tr(
"SQL error" ), mSqlErrorMessage,
true );
426 mStatusLabel->setText( u
"Query executed successfully (%1 rows, %2 ms)"_s
427 .arg( QLocale().toString( mQueryResultWatcher.result().rowCount() ), QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() ) ) );
431 mStatusLabel->setText( u
"Query executed successfully (%1 s)"_s.arg( QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() ) ) );
433 mProgressBar->hide();
434 mModel = std::make_unique<QgsQueryResultModel>( mQueryResultWatcher.result() );
440 connect( mModel.get(), &QgsQueryResultModel::fetchMoreRows,
this, [
this](
long long maxRows ) {
441 mFetchedRowsBatchCount = 0;
442 mProgressBar->setRange( 0, maxRows );
443 mProgressBar->show();
446 connect( mModel.get(), &QgsQueryResultModel::rowsInserted,
this, [
this](
const QModelIndex &,
int first,
int last ) {
447 if ( !mFirstRowFetched )
449 emit firstResultBatchFetched();
450 mFirstRowFetched = true;
451 mQueryResultsTableView->show();
452 mResultsContainer->show();
454 updateSqlLayerColumns();
455 mActualRowCount = mModel->queryResult().rowCount();
457 mStatusLabel->setText( tr(
"Fetched rows: %1/%2 %3 %4 ms" )
458 .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() ) ) );
459 mFetchedRowsBatchCount += last - first + 1;
460 mProgressBar->setValue( mFetchedRowsBatchCount );
463 mQueryResultsTableView->setModel( mModel.get() );
464 mQueryResultsTableView->show();
465 mResultsContainer->show();
467 connect( mModel.get(), &QgsQueryResultModel::fetchingComplete, mStopButton, [
this] {
469 const QgsHistoryEntry currentHistoryEntry = QgsGui::historyProviderRegistry()->entry( mCurrentHistoryEntryId, ok );
470 QVariantMap entryDetails = currentHistoryEntry.entry;
471 entryDetails.insert( u
"rows"_s, mActualRowCount );
472 entryDetails.insert( u
"time"_s, mQueryResultWatcher.result().queryExecutionTime() );
474 QgsGui::historyProviderRegistry()->updateEntry( mCurrentHistoryEntryId, entryDetails );
475 mProgressBar->hide();
476 mStopButton->setEnabled( false );
482 mStatusLabel->setText( tr(
"SQL command aborted" ) );
483 mProgressBar->hide();
487void QgsQueryResultPanelWidget::showError(
const QString &title,
const QString &message,
bool isSqlError )
489 mStatusLabel->show();
490 mStatusLabel->setText( tr(
"An error occurred while executing the query" ) );
491 mProgressBar->hide();
492 mQueryResultsTableView->hide();
495 mSqlErrorText->show();
496 mSqlErrorText->setText( message );
497 mResultsContainer->show();
501 mMessageBar->pushCritical( title, message );
502 mResultsContainer->hide();
506void QgsQueryResultPanelWidget::tokensReady(
const QStringList &tokens )
508 mSqlEditor->setExtraKeywords( mSqlEditor->extraKeywords() + tokens );
509 mSqlErrorText->setExtraKeywords( mSqlErrorText->extraKeywords() + tokens );
512void QgsQueryResultPanelWidget::copyResults()
514 const int rowCount = mModel->rowCount( QModelIndex() );
515 const int columnCount = mModel->columnCount( QModelIndex() );
516 copyResults( 0, rowCount - 1, 0, columnCount - 1 );
519void QgsQueryResultPanelWidget::copyResults(
int fromRow,
int toRow,
int fromColumn,
int toColumn )
521 QStringList rowStrings;
522 QStringList columnStrings;
524 const int rowCount = mModel->rowCount( QModelIndex() );
525 const int columnCount = mModel->columnCount( QModelIndex() );
527 toRow = std::min( toRow, rowCount - 1 );
528 toColumn = std::min( toColumn, columnCount - 1 );
530 rowStrings.reserve( toRow - fromRow );
533 for (
int col = fromColumn; col <= toColumn; col++ )
535 columnStrings += mModel->headerData( col, Qt::Horizontal, Qt::DisplayRole ).toString();
537 rowStrings += columnStrings.join( QLatin1Char(
'\t' ) );
538 columnStrings.clear();
540 for (
int row = fromRow; row <= toRow; row++ )
542 for (
int col = fromColumn; col <= toColumn; col++ )
544 columnStrings += mModel->data( mModel->index( row, col ), Qt::DisplayRole ).toString();
546 rowStrings += columnStrings.join( QLatin1Char(
'\t' ) );
547 columnStrings.clear();
550 if ( !rowStrings.isEmpty() )
552 const QString text = rowStrings.join( QLatin1Char(
'\n' ) );
553 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 );
554 html.replace(
"\t"_L1,
"</td><td>"_L1 ).replace(
"\n"_L1,
"</td></tr><tr><td>"_L1 );
556 QMimeData *mdata =
new QMimeData();
557 mdata->setData( u
"text/html"_s, html.toUtf8() );
558 if ( !text.isEmpty() )
560 mdata->setText( text );
564 QApplication::clipboard()->setMimeData( mdata, QClipboard::Selection );
566 QApplication::clipboard()->setMimeData( mdata, QClipboard::Clipboard );
572 const thread_local QRegularExpression rx( u
";\\s*$"_s );
573 mSqlVectorLayerOptions.sql = mSqlEditor->text().replace( rx, QString() );
574 mSqlVectorLayerOptions.filter = mFilterLineEdit->text();
575 mSqlVectorLayerOptions.primaryKeyColumns = mPkColumnsComboBox->checkedItems();
576 mSqlVectorLayerOptions.geometryColumn = mGeometryColumnComboBox->currentText();
577 mSqlVectorLayerOptions.layerName = mLayerNameLineEdit->text();
578 mSqlVectorLayerOptions.disableSelectAtId = mAvoidSelectingAsFeatureIdCheckBox->isChecked();
579 QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions options { mSqlVectorLayerOptions };
581 if ( !mPkColumnsCheckBox->isChecked() )
585 if ( !mGeometryColumnCheckBox->isChecked() )
594 mConnection.reset( connection );
601 const QMultiMap<Qgis::SqlKeywordCategory, QStringList> keywordsDict { connection->
sqlDictionary() };
602 QStringList keywords;
603 for (
auto it = keywordsDict.constBegin(); it != keywordsDict.constEnd(); it++ )
605 keywords.append( it.value() );
609 mSqlEditor->setExtraKeywords( keywords );
610 mSqlErrorText->setExtraKeywords( keywords );
613 QThread *apiFetcherWorkerThread =
new QThread();
614 QgsConnectionsApiFetcher *apiFetcher =
new QgsConnectionsApiFetcher( mConnection->uri(), mConnection->providerKey() );
615 apiFetcher->moveToThread( apiFetcherWorkerThread );
616 connect( apiFetcherWorkerThread, &QThread::started, apiFetcher, &QgsConnectionsApiFetcher::fetchTokens );
617 connect( apiFetcher, &QgsConnectionsApiFetcher::tokensReady,
this, &QgsQueryResultPanelWidget::tokensReady );
618 connect( apiFetcher, &QgsConnectionsApiFetcher::fetchingFinished, apiFetcherWorkerThread, [apiFetcher, apiFetcherWorkerThread] {
619 apiFetcherWorkerThread->quit();
620 apiFetcherWorkerThread->wait();
621 apiFetcherWorkerThread->deleteLater();
622 apiFetcher->deleteLater();
625 mApiFetcher = apiFetcher;
626 apiFetcherWorkerThread->start();
632void QgsQueryResultPanelWidget::setQuery(
const QString &sql )
634 mSqlEditor->setText( sql );
637void QgsQueryResultPanelWidget::notify(
const QString &title,
const QString &text,
Qgis::MessageLevel level )
639 mMessageBar->pushMessage( title, text, level );
645 QgsNewNameDialog dlg(
651 dlg.setWindowTitle( tr(
"Store Query" ) );
652 dlg.setHintString( tr(
"Name for the stored query" ) );
653 dlg.setOverwriteEnabled(
true );
654 dlg.setConflictingNameWarning( tr(
"A stored query with this name already exists, it will be overwritten." ) );
655 dlg.setShowExistingNamesCompleter(
true );
656 if ( dlg.exec() != QDialog::Accepted )
659 const QString name = dlg.name();
660 if ( name.isEmpty() )
681 mQueryWidget =
new QgsQueryResultPanelWidget(
nullptr, connection );
682 mPanelStack->setMainPanel( mQueryWidget );
684 mPresetQueryMenu =
new QMenu(
this );
685 connect( mPresetQueryMenu, &QMenu::aboutToShow,
this, &QgsQueryResultWidget::populatePresetQueryMenu );
687 QToolButton *presetQueryButton =
new QToolButton();
688 presetQueryButton->setMenu( mPresetQueryMenu );
690 presetQueryButton->setPopupMode( QToolButton::InstantPopup );
691 mToolBar->addWidget( presetQueryButton );
693 connect( mActionOpenQuery, &QAction::triggered,
this, &QgsQueryResultWidget::openQuery );
694 connect( mActionSaveQuery, &QAction::triggered,
this, [
this] { saveQuery(
false ); } );
695 connect( mActionSaveQueryAs, &QAction::triggered,
this, [
this] { saveQuery(
true ); } );
697 connect( mActionCut, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::cut );
698 connect( mActionCopy, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::copy );
699 connect( mActionPaste, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::paste );
700 connect( mActionUndo, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::undo );
701 connect( mActionRedo, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::redo );
702 mActionUndo->setEnabled(
false );
703 mActionRedo->setEnabled(
false );
707 connect( mQueryWidget->sqlEditor(), &QgsCodeEditor::modificationChanged,
this, &QgsQueryResultWidget::setHasChanged );
709 connect( mActionShowHistory, &QAction::toggled,
this, &QgsQueryResultWidget::showHistoryPanel );
711 connect( mActionClear, &QAction::triggered,
this, [
this] {
713 mQueryWidget->sqlEditor()->SendScintilla( QsciScintilla::SCI_SETTEXT,
"" );
716 connect( mQueryWidget->sqlEditor(), &QgsCodeEditorSQL::textChanged,
this, &QgsQueryResultWidget::updateButtons );
718 connect( mQueryWidget->sqlEditor(), &QgsCodeEditorSQL::copyAvailable, mActionCut, &QAction::setEnabled );
719 connect( mQueryWidget->sqlEditor(), &QgsCodeEditorSQL::copyAvailable, mActionCopy, &QAction::setEnabled );
721 connect( mQueryWidget, &QgsQueryResultPanelWidget::createSqlVectorLayer,
this, &QgsQueryResultWidget::createSqlVectorLayer );
722 connect( mQueryWidget, &QgsQueryResultPanelWidget::firstResultBatchFetched,
this, &QgsQueryResultWidget::firstResultBatchFetched );
725 setHasChanged(
false );
728QgsQueryResultWidget::~QgsQueryResultWidget()
730 if ( mHistoryWidget )
732 mPanelStack->closePanel( mHistoryWidget );
733 mHistoryWidget->deleteLater();
739 if ( !options.
sql.isEmpty() )
741 setQuery( options.
sql );
743 mQueryWidget->setSqlVectorLayerOptions( options );
746void QgsQueryResultWidget::setWidgetMode( QueryWidgetMode widgetMode )
748 mQueryWidget->setWidgetMode( widgetMode );
751void QgsQueryResultWidget::executeQuery()
753 mQueryWidget->executeQuery();
756void QgsQueryResultWidget::updateButtons()
758 mQueryWidget->updateButtons();
760 const bool isEmpty = mQueryWidget->sqlEditor()->text().isEmpty();
761 mActionClear->setEnabled( !isEmpty );
762 mActionUndo->setEnabled( mQueryWidget->sqlEditor()->isUndoAvailable() );
763 mActionRedo->setEnabled( mQueryWidget->sqlEditor()->isRedoAvailable() );
766void QgsQueryResultWidget::showError(
const QString &title,
const QString &message,
bool isSqlError )
768 mQueryWidget->showError( title, message, isSqlError );
771void QgsQueryResultWidget::tokensReady(
const QStringList & )
775void QgsQueryResultWidget::copyResults()
777 mQueryWidget->copyResults();
780void QgsQueryResultWidget::copyResults(
int fromRow,
int toRow,
int fromColumn,
int toColumn )
782 mQueryWidget->copyResults( fromRow, toRow, fromColumn, toColumn );
785void QgsQueryResultWidget::openQuery()
787 if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() && mHasChangedFileContents )
789 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 )
793 QString initialDir = settingLastSourceFolder->value();
794 if ( initialDir.isEmpty() )
795 initialDir = QDir::homePath();
797 const QString fileName = QFileDialog::getOpenFileName(
this, tr(
"Open Query" ), initialDir, tr(
"SQL queries (*.sql *.SQL)" ) + u
";;"_s + QObject::tr(
"All files" ) + u
" (*.*)"_s );
799 if ( fileName.isEmpty() )
802 QFileInfo fi( fileName );
803 settingLastSourceFolder->setValue( fi.path() );
805 QgsTemporaryCursorOverride cursor( Qt::CursorShape::WaitCursor );
807 mQueryWidget->codeEditorWidget()->loadFile( fileName );
808 setHasChanged(
false );
811void QgsQueryResultWidget::saveQuery(
bool saveAs )
813 if ( mQueryWidget->codeEditorWidget()->filePath().isEmpty() || saveAs )
815 QString selectedFilter;
817 QString initialDir = settingLastSourceFolder->value();
818 if ( initialDir.isEmpty() )
819 initialDir = QDir::homePath();
821 QString newPath = QFileDialog::getSaveFileName(
825 tr(
"SQL queries (*.sql *.SQL)" ) + u
";;"_s + QObject::tr(
"All files" ) + u
" (*.*)"_s,
829 if ( !newPath.isEmpty() )
831 QFileInfo fi( newPath );
832 settingLastSourceFolder->setValue( fi.path() );
834 if ( !selectedFilter.contains( u
"*.*)"_s ) )
836 mQueryWidget->codeEditorWidget()->save( newPath );
837 setHasChanged(
false );
840 else if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() )
842 mQueryWidget->codeEditorWidget()->save();
843 setHasChanged(
false );
849 mQueryWidget->setConnection( connection );
853void QgsQueryResultWidget::setQuery(
const QString &sql )
855 mQueryWidget->sqlEditor()->setText( sql );
857 mActionUndo->setEnabled(
false );
858 mActionRedo->setEnabled(
false );
861bool QgsQueryResultWidget::promptUnsavedChanges()
863 if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() && mHasChangedFileContents )
865 const QMessageBox::StandardButton ret = QMessageBox::question(
869 "There are unsaved changes in this query. Do you want to save those?"
871 QMessageBox::StandardButton::Save
872 | QMessageBox::StandardButton::Cancel
873 | QMessageBox::StandardButton::Discard,
874 QMessageBox::StandardButton::Cancel
877 if ( ret == QMessageBox::StandardButton::Save )
882 else if ( ret == QMessageBox::StandardButton::Discard )
898void QgsQueryResultWidget::notify(
const QString &title,
const QString &text,
Qgis::MessageLevel level )
900 mQueryWidget->notify( title, text, level );
904void QgsQueryResultWidget::setHasChanged(
bool hasChanged )
906 mActionSaveQuery->setEnabled( hasChanged );
907 mHasChangedFileContents = hasChanged;
911void QgsQueryResultWidget::updateDialogTitle()
914 if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() )
916 const QFileInfo fi( mQueryWidget->codeEditorWidget()->filePath() );
917 fileName = fi.fileName();
918 if ( mHasChangedFileContents )
920 fileName.prepend(
'*' );
924 emit requestDialogTitleUpdate( fileName );
927void QgsQueryResultWidget::populatePresetQueryMenu()
929 mPresetQueryMenu->clear();
931 QMenu *storeQueryMenu =
new QMenu( tr(
"Store Current Query" ), mPresetQueryMenu );
932 mPresetQueryMenu->addMenu( storeQueryMenu );
933 QAction *storeInProfileAction =
new QAction( tr(
"In User Profile…" ), storeQueryMenu );
934 storeQueryMenu->addAction( storeInProfileAction );
935 storeInProfileAction->setEnabled( !mQueryWidget->sqlEditor()->text().isEmpty() );
936 connect( storeInProfileAction, &QAction::triggered,
this, [
this] {
939 QAction *storeInProjectAction =
new QAction( tr(
"In Current Project…" ), storeQueryMenu );
940 storeQueryMenu->addAction( storeInProjectAction );
941 storeInProjectAction->setEnabled( !mQueryWidget->sqlEditor()->text().isEmpty() );
942 connect( storeInProjectAction, &QAction::triggered,
this, [
this] {
948 if ( !storedQueries.isEmpty() )
950 QList< QgsStoredQueryManager::QueryDetails > userProfileQueries;
951 std::copy_if( storedQueries.begin(), storedQueries.end(), std::back_inserter( userProfileQueries ), [](
const QgsStoredQueryManager::QueryDetails &details ) {
952 return details.backend == Qgis::QueryStorageBackend::LocalProfile;
955 QList< QgsStoredQueryManager::QueryDetails > projectQueries;
956 std::copy_if( storedQueries.begin(), storedQueries.end(), std::back_inserter( projectQueries ), [](
const QgsStoredQueryManager::QueryDetails &details ) {
957 return details.backend == Qgis::QueryStorageBackend::CurrentProject;
961 for (
const QgsStoredQueryManager::QueryDetails &query : std::as_const( userProfileQueries ) )
963 QAction *action =
new QAction( query.name, mPresetQueryMenu );
964 mPresetQueryMenu->addAction( action );
965 connect( action, &QAction::triggered,
this, [
this, query] {
966 mQueryWidget->sqlEditor()->insertText( query.definition );
969 if ( userProfileQueries.empty() )
971 QAction *action =
new QAction( tr(
"No Stored Queries Available" ), mPresetQueryMenu );
972 action->setEnabled(
false );
973 mPresetQueryMenu->addAction( action );
977 for (
const QgsStoredQueryManager::QueryDetails &query : std::as_const( projectQueries ) )
979 QAction *action =
new QAction( query.name, mPresetQueryMenu );
980 mPresetQueryMenu->addAction( action );
981 connect( action, &QAction::triggered,
this, [
this, query] {
982 mQueryWidget->sqlEditor()->insertText( query.definition );
985 if ( projectQueries.empty() )
987 QAction *action =
new QAction( tr(
"No Stored Queries Available" ), mPresetQueryMenu );
988 action->setEnabled(
false );
989 mPresetQueryMenu->addAction( action );
992 mPresetQueryMenu->addSeparator();
994 QMenu *removeQueryMenu =
new QMenu( tr(
"Removed Stored Query" ), mPresetQueryMenu );
995 mPresetQueryMenu->addMenu( removeQueryMenu );
997 for (
const QgsStoredQueryManager::QueryDetails &query : storedQueries )
999 QAction *action =
new QAction( tr(
"%1…" ).arg( query.name ), mPresetQueryMenu );
1000 removeQueryMenu->addAction( action );
1001 connect( action, &QAction::triggered,
this, [
this, query] {
1002 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 );
1003 if ( res == QMessageBox::Yes )
1019 QgsNewNameDialog dlg(
1025 dlg.setWindowTitle( tr(
"Store Query" ) );
1026 dlg.setHintString( tr(
"Name for the stored query" ) );
1027 dlg.setOverwriteEnabled(
true );
1028 dlg.setConflictingNameWarning( tr(
"A stored query with this name already exists, it will be overwritten." ) );
1029 dlg.setShowExistingNamesCompleter(
true );
1030 if ( dlg.exec() != QDialog::Accepted )
1033 const QString name = dlg.name();
1034 if ( name.isEmpty() )
1045void 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();
1076void QgsConnectionsApiFetcher::fetchTokens()
1078 if ( mStopFetching )
1080 emit fetchingFinished();
1088 emit fetchingFinished();
1092 if ( !mStopFetching && connection )
1094 mFeedback = std::make_unique<QgsFeedback>();
1095 QStringList schemas;
1100 schemas = connection->
schemas();
1101 emit tokensReady( schemas );
1110 schemas.push_back( QString() );
1113 for (
const auto &schema : std::as_const( schemas ) )
1115 if ( mStopFetching )
1118 emit fetchingFinished();
1122 QStringList tableNames;
1128 if ( mStopFetching )
1131 emit fetchingFinished();
1134 tableNames.push_back( table.tableName() );
1136 emit tokensReady( tableNames );
1144 for (
const auto &table : std::as_const( tableNames ) )
1146 if ( mStopFetching )
1149 emit fetchingFinished();
1153 QStringList fieldNames;
1156 const QgsFields fields( connection->
fields( schema, table, mFeedback.get() ) );
1157 if ( mStopFetching )
1160 emit fetchingFinished();
1164 for (
const auto &field : std::as_const( fields ) )
1166 fieldNames.push_back( field.name() );
1167 if ( mStopFetching )
1170 emit fetchingFinished();
1174 emit tokensReady( fieldNames );
1185 emit fetchingFinished();
1188void QgsConnectionsApiFetcher::stopFetching()
1192 mFeedback->cancel();
1195QgsQueryResultItemDelegate::QgsQueryResultItemDelegate( QObject *parent )
1196 : QStyledItemDelegate( parent )
1200QString QgsQueryResultItemDelegate::displayText(
const QVariant &value,
const QLocale &locale )
const
1203 QString result { QgsExpressionUtils::toLocalizedString( value ) };
1205 if ( result.length() > 255 )
1207 result.truncate( 255 );
1208 result.append( u
"…"_s );
1222 setObjectName( u
"QgsQueryResultDialog"_s );
1225 mWidget =
new QgsQueryResultWidget(
this, connection );
1226 QVBoxLayout *l =
new QVBoxLayout();
1227 l->setContentsMargins( 6, 6, 6, 6 );
1229 QDialogButtonBox *mButtonBox =
new QDialogButtonBox( QDialogButtonBox::StandardButton::Close | QDialogButtonBox::StandardButton::Help );
1230 connect( mButtonBox, &QDialogButtonBox::rejected,
this, &QDialog::close );
1231 connect( mButtonBox, &QDialogButtonBox::helpRequested,
this, [] {
1234 l->addWidget( mWidget );
1235 l->addWidget( mButtonBox );
1240void QgsQueryResultDialog::closeEvent( QCloseEvent *event )
1242 if ( !mWidget->promptUnsavedChanges() )
1257 : mIdentifierName( identifierName )
1259 setObjectName( u
"SQLCommandsDialog"_s );
1263 mWidget =
new QgsQueryResultWidget(
nullptr, connection );
1264 setCentralWidget( mWidget );
1265 mWidget->layout()->setContentsMargins( 6, 6, 6, 6 );
1267 connect( mWidget, &QgsQueryResultWidget::requestDialogTitleUpdate,
this, &QgsQueryResultMainWindow::updateWindowTitle );
1269 updateWindowTitle( QString() );
1272void QgsQueryResultMainWindow::closeEvent( QCloseEvent *event )
1274 if ( !mWidget->promptUnsavedChanges() )
1284void QgsQueryResultMainWindow::updateWindowTitle(
const QString &fileName )
1286 if ( fileName.isEmpty() )
1288 if ( !mIdentifierName.isEmpty() )
1289 setWindowTitle( tr(
"%1 — Execute SQL" ).arg( mIdentifierName ) );
1291 setWindowTitle( tr(
"Execute SQL" ) );
1295 if ( !mIdentifierName.isEmpty() )
1296 setWindowTitle( tr(
"%1 — %2 — Execute SQL" ).arg( fileName, mIdentifierName ) );
1298 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.