17#include "moc_qgsqueryresultwidget.cpp"
40#include <QInputDialog>
43const QgsSettingsEntryString *QgsQueryResultWidget::settingLastSourceFolder =
new QgsSettingsEntryString( QStringLiteral(
"last-source-folder" ), sTreeSqlQueries, QString(), QStringLiteral(
"Last used folder for SQL source files" ) );
56 mPresetQueryMenu =
new QMenu(
this );
57 connect( mPresetQueryMenu, &QMenu::aboutToShow,
this, &QgsQueryResultWidget::populatePresetQueryMenu );
59 QToolButton *presetQueryButton =
new QToolButton();
60 presetQueryButton->setMenu( mPresetQueryMenu );
62 presetQueryButton->setPopupMode( QToolButton::InstantPopup );
63 mToolBar->addWidget( presetQueryButton );
66 mainLayout->setSpacing( 6 );
67 progressLayout->setSpacing( 6 );
69 mQueryResultsTableView->hide();
70 mQueryResultsTableView->setItemDelegate(
new QgsQueryResultItemDelegate( mQueryResultsTableView ) );
71 mQueryResultsTableView->setContextMenuPolicy( Qt::CustomContextMenu );
72 connect( mQueryResultsTableView, &QTableView::customContextMenuRequested,
this, &QgsQueryResultWidget::showCellContextMenu );
78 QVBoxLayout *vl =
new QVBoxLayout();
79 vl->setContentsMargins( 0, 0, 0, 0 );
80 vl->addWidget( mCodeEditorWidget );
81 mSqlEditorContainer->setLayout( vl );
83 connect( mActionOpenQuery, &QAction::triggered,
this, &QgsQueryResultWidget::openQuery );
84 connect( mActionSaveQuery, &QAction::triggered,
this, [
this] { saveQuery(
false ); } );
85 connect( mActionSaveQueryAs, &QAction::triggered,
this, [
this] { saveQuery(
true ); } );
87 connect( mActionCut, &QAction::triggered, mSqlEditor, &QgsCodeEditor::cut );
88 connect( mActionCopy, &QAction::triggered, mSqlEditor, &QgsCodeEditor::copy );
89 connect( mActionPaste, &QAction::triggered, mSqlEditor, &QgsCodeEditor::paste );
90 connect( mActionUndo, &QAction::triggered, mSqlEditor, &QgsCodeEditor::undo );
91 connect( mActionRedo, &QAction::triggered, mSqlEditor, &QgsCodeEditor::redo );
92 mActionUndo->setEnabled(
false );
93 mActionRedo->setEnabled(
false );
97 connect( mSqlEditor, &QgsCodeEditor::modificationChanged,
this, &QgsQueryResultWidget::setHasChanged );
99 connect( mExecuteButton, &QPushButton::pressed,
this, &QgsQueryResultWidget::executeQuery );
101 connect( mActionClear, &QAction::triggered,
this, [=] {
102 mSqlEditor->setText( QString() );
103 mActionUndo->setEnabled(
false );
104 mActionRedo->setEnabled(
false );
106 connect( mLoadLayerPushButton, &QPushButton::pressed,
this, [=] {
114 const bool res = mConnection->validateSqlVectorLayer( options, message );
117 mMessageBar->pushCritical( QString(), message );
121 emit createSqlVectorLayer( mConnection->providerKey(), mConnection->uri(), options );
126 mMessageBar->pushCritical( tr(
"Error validating query" ), e.
what() );
130 connect( mSqlEditor, &QgsCodeEditorSQL::textChanged,
this, &QgsQueryResultWidget::updateButtons );
132 connect( mSqlEditor, &QgsCodeEditorSQL::copyAvailable, mActionCut, &QAction::setEnabled );
133 connect( mSqlEditor, &QgsCodeEditorSQL::copyAvailable, mActionCopy, &QAction::setEnabled );
135 connect( mSqlEditor, &QgsCodeEditorSQL::selectionChanged,
this, [=] {
136 mExecuteButton->setText( mSqlEditor->selectedText().isEmpty() ? tr(
"Execute" ) : tr(
"Execute Selection" ) );
138 connect( mFilterToolButton, &QToolButton::pressed,
this, [=] {
143 std::unique_ptr<QgsVectorLayer> vlayer { mConnection->createSqlVectorLayer( sqlVectorLayerOptions() ) };
145 if ( builder.exec() == QDialog::Accepted )
147 mFilterLineEdit->setText( builder.sql() );
152 mMessageBar->pushCritical( tr(
"Error opening filter dialog" ), tr(
"There was an error while preparing SQL filter dialog: %1." ).arg( ex.
what() ) );
158 mStatusLabel->hide();
159 mSqlErrorText->hide();
161 mLoadAsNewLayerGroupBox->setCollapsed(
true );
168 mPkColumnsCheckBox->setVisible( showPkConfig );
169 mPkColumnsComboBox->setVisible( showPkConfig );
172 mGeometryColumnCheckBox->setVisible( showGeometryColumnConfig );
173 mGeometryColumnComboBox->setVisible( showGeometryColumnConfig );
176 mFilterLabel->setVisible( showFilterConfig );
177 mFilterToolButton->setVisible( showFilterConfig );
178 mFilterLineEdit->setVisible( showFilterConfig );
181 mAvoidSelectingAsFeatureIdCheckBox->setVisible( showDisableSelectAtId );
185 QShortcut *copySelection =
new QShortcut( QKeySequence::Copy, mQueryResultsTableView );
186 connect( copySelection, &QShortcut::activated,
this, &QgsQueryResultWidget::copySelection );
188 setConnection( connection );
189 setHasChanged(
false );
192QgsQueryResultWidget::~QgsQueryResultWidget()
195 cancelRunningQuery();
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 QgsQueryResultWidget::setWidgetMode( QueryWidgetMode widgetMode )
224 mQueryWidgetMode = widgetMode;
225 switch ( widgetMode )
227 case QueryWidgetMode::SqlQueryMode:
228 mLoadAsNewLayerGroupBox->setTitle( tr(
"Load as New Layer" ) );
229 mLoadLayerPushButton->setText( tr(
"Load Layer" ) );
230 mLoadAsNewLayerGroupBox->setCollapsed(
true );
232 case QueryWidgetMode::QueryLayerUpdateMode:
233 mLoadAsNewLayerGroupBox->setTitle( tr(
"Update Query Layer" ) );
234 mLoadLayerPushButton->setText( tr(
"Update Layer" ) );
235 mLoadAsNewLayerGroupBox->setCollapsed(
false );
240void QgsQueryResultWidget::executeQuery()
242 mQueryResultsTableView->hide();
243 mSqlErrorText->hide();
244 mFirstRowFetched =
false;
246 cancelRunningQuery();
249 const QString sql { mSqlEditor->selectedText().isEmpty() ? mSqlEditor->text() : mSqlEditor->selectedText() };
253 { QStringLiteral(
"query" ), sql },
254 { QStringLiteral(
"provider" ), mConnection->providerKey() },
255 { QStringLiteral(
"connection" ), mConnection->uri() },
259 mWasCanceled =
false;
260 mFeedback = std::make_unique<QgsFeedback>();
261 mStopButton->setEnabled(
true );
262 mStatusLabel->show();
263 mStatusLabel->setText( tr(
"Executing query…" ) );
264 mProgressBar->show();
265 mProgressBar->setRange( 0, 0 );
266 mSqlErrorMessage.clear();
268 connect( mStopButton, &QPushButton::pressed, mFeedback.get(), [=] {
269 mStatusLabel->setText( tr(
"Stopped" ) );
271 mProgressBar->hide();
276 connect( &mQueryResultWatcher, &QFutureWatcher<QgsAbstractDatabaseProviderConnection::QueryResult>::finished,
this, &QgsQueryResultWidget::startFetching, Qt::ConnectionType::UniqueConnection );
281 return mConnection->execSql( sql, mFeedback.get() );
285 mSqlErrorMessage = ex.
what();
289 mQueryResultWatcher.setFuture( future );
293 showError( tr(
"Connection error" ), tr(
"Cannot execute query: connection to the database is not available." ) );
297void QgsQueryResultWidget::updateButtons()
299 mFilterLineEdit->setEnabled( mFirstRowFetched );
300 mFilterToolButton->setEnabled( mFirstRowFetched );
301 const bool isEmpty = mSqlEditor->text().isEmpty();
302 mExecuteButton->setEnabled( !isEmpty );
303 mActionClear->setEnabled( !isEmpty );
304 mActionUndo->setEnabled( mSqlEditor->isUndoAvailable() );
305 mActionRedo->setEnabled( mSqlEditor->isRedoAvailable() );
307 mLoadAsNewLayerGroupBox->setEnabled(
308 mSqlErrorMessage.isEmpty() && mFirstRowFetched
312void QgsQueryResultWidget::showCellContextMenu( QPoint point )
314 const QModelIndex modelIndex = mQueryResultsTableView->indexAt( point );
315 if ( modelIndex.isValid() )
317 QMenu *menu =
new QMenu();
318 menu->setAttribute( Qt::WA_DeleteOnClose );
320 menu->addAction(
QgsApplication::getThemeIcon(
"mActionEditCopy.svg" ), tr(
"Copy" ),
this, [=] { copySelection(); }, QKeySequence::Copy );
322 menu->exec( mQueryResultsTableView->viewport()->mapToGlobal( point ) );
326void QgsQueryResultWidget::copySelection()
328 const QModelIndexList selection = mQueryResultsTableView->selectionModel()->selectedIndexes();
329 if ( selection.empty() )
336 for (
const QModelIndex &index : selection )
338 if ( minRow == -1 || index.row() < minRow )
339 minRow = index.row();
340 if ( maxRow == -1 || index.row() > maxRow )
341 maxRow = index.row();
342 if ( minCol == -1 || index.column() < minCol )
343 minCol = index.column();
344 if ( maxCol == -1 || index.column() > maxCol )
345 maxCol = index.column();
348 if ( minRow == maxRow && minCol == maxCol )
351 const QString text = mModel->data( selection.at( 0 ), Qt::DisplayRole ).toString();
352 QApplication::clipboard()->setText( text );
356 copyResults( minRow, maxRow, minCol, maxCol );
360void QgsQueryResultWidget::updateSqlLayerColumns()
365 mFilterToolButton->setEnabled(
true );
366 mFilterLineEdit->setEnabled(
true );
367 mPkColumnsComboBox->clear();
368 mGeometryColumnComboBox->clear();
369 const bool hasPkInformation { !mSqlVectorLayerOptions.primaryKeyColumns.isEmpty() };
370 const bool hasGeomColInformation { !mSqlVectorLayerOptions.geometryColumn.isEmpty() };
371 static const QStringList geomColCandidates { QStringLiteral(
"geom" ), QStringLiteral(
"geometry" ), QStringLiteral(
"the_geom" ) };
372 const QStringList constCols { mModel->columns() };
373 for (
const QString &
c : constCols )
375 const bool pkCheckedState = hasPkInformation ? mSqlVectorLayerOptions.primaryKeyColumns.contains(
c ) :
c.contains( QStringLiteral(
"id" ), Qt::CaseSensitivity::CaseInsensitive );
377 mPkColumnsComboBox->addItemWithCheckState(
c, pkCheckedState && mPkColumnsComboBox->checkedItems().isEmpty() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked );
378 mGeometryColumnComboBox->addItem(
c );
379 if ( !hasGeomColInformation && geomColCandidates.contains(
c, Qt::CaseSensitivity::CaseInsensitive ) )
381 mGeometryColumnComboBox->setCurrentText(
c );
384 mPkColumnsCheckBox->setChecked( hasPkInformation );
385 mGeometryColumnCheckBox->setChecked( hasGeomColInformation );
386 if ( hasGeomColInformation )
388 mGeometryColumnComboBox->setCurrentText( mSqlVectorLayerOptions.geometryColumn );
392void QgsQueryResultWidget::cancelRunningQuery()
401 if ( mQueryResultWatcher.isRunning() )
403 mQueryResultWatcher.waitForFinished();
407void QgsQueryResultWidget::cancelApiFetcher()
411 mApiFetcher->stopFetching();
416void QgsQueryResultWidget::startFetching()
420 if ( !mSqlErrorMessage.isEmpty() )
422 showError( tr(
"SQL error" ), mSqlErrorMessage,
true );
428 mStatusLabel->setText( QStringLiteral(
"Query executed successfully (%1 rows, %2 ms)" )
429 .arg( QLocale().toString( mQueryResultWatcher.result().rowCount() ), QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() ) ) );
433 mStatusLabel->setText( QStringLiteral(
"Query executed successfully (%1 s)" ).arg( QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() ) ) );
435 mProgressBar->hide();
436 mModel = std::make_unique<QgsQueryResultModel>( mQueryResultWatcher.result() );
442 connect( mModel.get(), &QgsQueryResultModel::fetchMoreRows,
this, [=](
long long maxRows ) {
443 mFetchedRowsBatchCount = 0;
444 mProgressBar->setRange( 0, maxRows );
445 mProgressBar->show();
448 connect( mModel.get(), &QgsQueryResultModel::rowsInserted,
this, [=](
const QModelIndex &,
int first,
int last ) {
449 if ( !mFirstRowFetched )
451 emit firstResultBatchFetched();
452 mFirstRowFetched = true;
453 mQueryResultsTableView->show();
455 updateSqlLayerColumns();
456 mActualRowCount = mModel->queryResult().rowCount();
458 mStatusLabel->setText( tr(
"Fetched rows: %1/%2 %3 %4 ms" )
459 .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() ) ) );
460 mFetchedRowsBatchCount += last - first + 1;
461 mProgressBar->setValue( mFetchedRowsBatchCount );
464 mQueryResultsTableView->setModel( mModel.get() );
465 mQueryResultsTableView->show();
467 connect( mModel.get(), &QgsQueryResultModel::fetchingComplete, mStopButton, [=] {
469 const QgsHistoryEntry currentHistoryEntry = QgsGui::historyProviderRegistry()->entry( mCurrentHistoryEntryId, ok );
470 QVariantMap entryDetails = currentHistoryEntry.entry;
471 entryDetails.insert( QStringLiteral(
"rows" ), mActualRowCount );
472 entryDetails.insert( QStringLiteral(
"time" ), 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 QgsQueryResultWidget::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 );
500 mMessageBar->pushCritical( title, message );
504void QgsQueryResultWidget::tokensReady(
const QStringList &tokens )
506 mSqlEditor->setExtraKeywords( mSqlEditor->extraKeywords() + tokens );
507 mSqlErrorText->setExtraKeywords( mSqlErrorText->extraKeywords() + tokens );
510void QgsQueryResultWidget::copyResults()
512 const int rowCount = mModel->rowCount( QModelIndex() );
513 const int columnCount = mModel->columnCount( QModelIndex() );
514 copyResults( 0, rowCount - 1, 0, columnCount - 1 );
517void QgsQueryResultWidget::copyResults(
int fromRow,
int toRow,
int fromColumn,
int toColumn )
519 QStringList rowStrings;
520 QStringList columnStrings;
522 const int rowCount = mModel->rowCount( QModelIndex() );
523 const int columnCount = mModel->columnCount( QModelIndex() );
525 toRow = std::min( toRow, rowCount - 1 );
526 toColumn = std::min( toColumn, columnCount - 1 );
528 rowStrings.reserve( toRow - fromRow );
531 for (
int col = fromColumn; col <= toColumn; col++ )
533 columnStrings += mModel->headerData( col, Qt::Horizontal, Qt::DisplayRole ).toString();
535 rowStrings += columnStrings.join( QLatin1Char(
'\t' ) );
536 columnStrings.clear();
538 for (
int row = fromRow; row <= toRow; row++ )
540 for (
int col = fromColumn; col <= toColumn; col++ )
542 columnStrings += mModel->data( mModel->index( row, col ), Qt::DisplayRole ).toString();
544 rowStrings += columnStrings.join( QLatin1Char(
'\t' ) );
545 columnStrings.clear();
548 if ( !rowStrings.isEmpty() )
550 const QString text = rowStrings.join( QLatin1Char(
'\n' ) );
551 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 );
552 html.replace( QLatin1String(
"\t" ), QLatin1String(
"</td><td>" ) ).replace( QLatin1String(
"\n" ), QLatin1String(
"</td></tr><tr><td>" ) );
554 QMimeData *mdata =
new QMimeData();
555 mdata->setData( QStringLiteral(
"text/html" ), html.toUtf8() );
556 if ( !text.isEmpty() )
558 mdata->setText( text );
562 QApplication::clipboard()->setMimeData( mdata, QClipboard::Selection );
564 QApplication::clipboard()->setMimeData( mdata, QClipboard::Clipboard );
568void QgsQueryResultWidget::openQuery()
570 if ( !mCodeEditorWidget->filePath().isEmpty() && mHasChangedFileContents )
572 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 )
576 QString initialDir = settingLastSourceFolder->value();
577 if ( initialDir.isEmpty() )
578 initialDir = QDir::homePath();
580 const QString fileName = QFileDialog::getOpenFileName(
this, tr(
"Open Query" ), initialDir, tr(
"SQL queries (*.sql *.SQL)" ) + QStringLiteral(
";;" ) + QObject::tr(
"All files" ) + QStringLiteral(
" (*.*)" ) );
582 if ( fileName.isEmpty() )
585 QFileInfo fi( fileName );
586 settingLastSourceFolder->setValue( fi.path() );
590 mCodeEditorWidget->loadFile( fileName );
591 setHasChanged(
false );
594void QgsQueryResultWidget::saveQuery(
bool saveAs )
596 if ( mCodeEditorWidget->filePath().isEmpty() || saveAs )
598 QString selectedFilter;
600 QString initialDir = settingLastSourceFolder->value();
601 if ( initialDir.isEmpty() )
602 initialDir = QDir::homePath();
604 QString newPath = QFileDialog::getSaveFileName(
608 tr(
"SQL queries (*.sql *.SQL)" ) + QStringLiteral(
";;" ) + QObject::tr(
"All files" ) + QStringLiteral(
" (*.*)" ),
612 if ( !newPath.isEmpty() )
614 QFileInfo fi( newPath );
615 settingLastSourceFolder->setValue( fi.path() );
617 if ( !selectedFilter.contains( QStringLiteral(
"*.*)" ) ) )
619 mCodeEditorWidget->save( newPath );
620 setHasChanged(
false );
623 else if ( !mCodeEditorWidget->filePath().isEmpty() )
625 mCodeEditorWidget->save();
626 setHasChanged(
false );
632 const thread_local QRegularExpression rx( QStringLiteral(
";\\s*$" ) );
633 mSqlVectorLayerOptions.sql = mSqlEditor->text().replace( rx, QString() );
634 mSqlVectorLayerOptions.filter = mFilterLineEdit->text();
635 mSqlVectorLayerOptions.primaryKeyColumns = mPkColumnsComboBox->checkedItems();
636 mSqlVectorLayerOptions.geometryColumn = mGeometryColumnComboBox->currentText();
637 mSqlVectorLayerOptions.layerName = mLayerNameLineEdit->text();
638 mSqlVectorLayerOptions.disableSelectAtId = mAvoidSelectingAsFeatureIdCheckBox->isChecked();
641 if ( !mPkColumnsCheckBox->isChecked() )
645 if ( !mGeometryColumnCheckBox->isChecked() )
654 mConnection.reset( connection );
661 const QMultiMap<Qgis::SqlKeywordCategory, QStringList> keywordsDict { connection->
sqlDictionary() };
662 QStringList keywords;
663 for (
auto it = keywordsDict.constBegin(); it != keywordsDict.constEnd(); it++ )
665 keywords.append( it.value() );
669 mSqlEditor->setExtraKeywords( keywords );
670 mSqlErrorText->setExtraKeywords( keywords );
673 QThread *apiFetcherWorkerThread =
new QThread();
674 QgsConnectionsApiFetcher *apiFetcher =
new QgsConnectionsApiFetcher( mConnection->uri(), mConnection->providerKey() );
675 apiFetcher->moveToThread( apiFetcherWorkerThread );
676 connect( apiFetcherWorkerThread, &QThread::started, apiFetcher, &QgsConnectionsApiFetcher::fetchTokens );
677 connect( apiFetcher, &QgsConnectionsApiFetcher::tokensReady,
this, &QgsQueryResultWidget::tokensReady );
678 connect( apiFetcher, &QgsConnectionsApiFetcher::fetchingFinished, apiFetcherWorkerThread, [apiFetcher, apiFetcherWorkerThread] {
679 apiFetcherWorkerThread->quit();
680 apiFetcherWorkerThread->wait();
681 apiFetcherWorkerThread->deleteLater();
682 apiFetcher->deleteLater();
685 mApiFetcher = apiFetcher;
686 apiFetcherWorkerThread->start();
692void QgsQueryResultWidget::setQuery(
const QString &sql )
694 mSqlEditor->setText( sql );
696 mActionUndo->setEnabled(
false );
697 mActionRedo->setEnabled(
false );
701bool QgsQueryResultWidget::promptUnsavedChanges()
703 if ( !mCodeEditorWidget->filePath().isEmpty() && mHasChangedFileContents )
705 const QMessageBox::StandardButton ret = QMessageBox::question(
709 "There are unsaved changes in this query. Do you want to save those?"
711 QMessageBox::StandardButton::Save
712 | QMessageBox::StandardButton::Cancel
713 | QMessageBox::StandardButton::Discard,
714 QMessageBox::StandardButton::Cancel
717 if ( ret == QMessageBox::StandardButton::Save )
722 else if ( ret == QMessageBox::StandardButton::Discard )
738void QgsQueryResultWidget::notify(
const QString &title,
const QString &text,
Qgis::MessageLevel level )
740 mMessageBar->pushMessage( title, text, level );
744void QgsQueryResultWidget::setHasChanged(
bool hasChanged )
746 mHasChangedFileContents = hasChanged;
747 mActionSaveQuery->setEnabled( hasChanged );
751void QgsQueryResultWidget::updateDialogTitle()
754 if ( !mCodeEditorWidget->filePath().isEmpty() )
756 const QFileInfo fi( mCodeEditorWidget->filePath() );
757 fileName = fi.fileName();
758 if ( mHasChangedFileContents )
760 fileName.prepend(
'*' );
764 emit requestDialogTitleUpdate( fileName );
767void QgsQueryResultWidget::populatePresetQueryMenu()
769 mPresetQueryMenu->clear();
771 QMenu *storeQueryMenu =
new QMenu( tr(
"Store Current Query" ), mPresetQueryMenu );
772 mPresetQueryMenu->addMenu( storeQueryMenu );
773 QAction *storeInProfileAction =
new QAction( tr(
"In User Profile…" ), storeQueryMenu );
774 storeQueryMenu->addAction( storeInProfileAction );
775 storeInProfileAction->setEnabled( !mSqlEditor->text().isEmpty() );
776 connect( storeInProfileAction, &QAction::triggered,
this, [
this] {
779 QAction *storeInProjectAction =
new QAction( tr(
"In Current Project…" ), storeQueryMenu );
780 storeQueryMenu->addAction( storeInProjectAction );
781 storeInProjectAction->setEnabled( !mSqlEditor->text().isEmpty() );
782 connect( storeInProjectAction, &QAction::triggered,
this, [
this] {
788 if ( !storedQueries.isEmpty() )
790 QList< QgsStoredQueryManager::QueryDetails > userProfileQueries;
792 return details.backend == Qgis::QueryStorageBackend::LocalProfile;
795 QList< QgsStoredQueryManager::QueryDetails > projectQueries;
797 return details.backend == Qgis::QueryStorageBackend::CurrentProject;
803 QAction *action =
new QAction( query.name, mPresetQueryMenu );
804 mPresetQueryMenu->addAction( action );
805 connect( action, &QAction::triggered,
this, [
this, query] {
806 mSqlEditor->insertText( query.definition );
809 if ( userProfileQueries.empty() )
811 QAction *action =
new QAction( tr(
"No Stored Queries Available" ), mPresetQueryMenu );
812 action->setEnabled(
false );
813 mPresetQueryMenu->addAction( action );
819 QAction *action =
new QAction( query.name, mPresetQueryMenu );
820 mPresetQueryMenu->addAction( action );
821 connect( action, &QAction::triggered,
this, [
this, query] {
822 mSqlEditor->insertText( query.definition );
825 if ( projectQueries.empty() )
827 QAction *action =
new QAction( tr(
"No Stored Queries Available" ), mPresetQueryMenu );
828 action->setEnabled(
false );
829 mPresetQueryMenu->addAction( action );
832 mPresetQueryMenu->addSeparator();
834 QMenu *removeQueryMenu =
new QMenu( tr(
"Removed Stored Query" ), mPresetQueryMenu );
835 mPresetQueryMenu->addMenu( removeQueryMenu );
839 QAction *action =
new QAction( tr(
"%1…" ).arg( query.name ), mPresetQueryMenu );
840 removeQueryMenu->addAction( action );
841 connect( action, &QAction::triggered,
this, [
this, query] {
842 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 );
843 if ( res == QMessageBox::Yes )
865 dlg.setWindowTitle( tr(
"Store Query" ) );
866 dlg.setHintString( tr(
"Name for the stored query" ) );
867 dlg.setOverwriteEnabled(
true );
868 dlg.setConflictingNameWarning( tr(
"A stored query with this name already exists, it will be overwritten." ) );
869 dlg.setShowExistingNamesCompleter(
true );
870 if ( dlg.exec() != QDialog::Accepted )
873 const QString name = dlg.name();
874 if ( name.isEmpty() )
886void QgsConnectionsApiFetcher::fetchTokens()
890 emit fetchingFinished();
898 emit fetchingFinished();
902 if ( !mStopFetching && connection )
904 mFeedback = std::make_unique<QgsFeedback>();
910 schemas = connection->
schemas();
911 emit tokensReady( schemas );
920 schemas.push_back( QString() );
923 for (
const auto &schema : std::as_const( schemas ) )
928 emit fetchingFinished();
932 QStringList tableNames;
941 emit fetchingFinished();
944 tableNames.push_back( table.tableName() );
946 emit tokensReady( tableNames );
954 for (
const auto &table : std::as_const( tableNames ) )
959 emit fetchingFinished();
963 QStringList fieldNames;
966 const QgsFields fields( connection->
fields( schema, table, mFeedback.get() ) );
970 emit fetchingFinished();
974 for (
const auto &field : std::as_const( fields ) )
976 fieldNames.push_back( field.name() );
980 emit fetchingFinished();
984 emit tokensReady( fieldNames );
995 emit fetchingFinished();
998void QgsConnectionsApiFetcher::stopFetching()
1002 mFeedback->cancel();
1005QgsQueryResultItemDelegate::QgsQueryResultItemDelegate( QObject *parent )
1006 : QStyledItemDelegate( parent )
1010QString QgsQueryResultItemDelegate::displayText(
const QVariant &value,
const QLocale &locale )
const
1013 QString result { QgsExpressionUtils::toLocalizedString( value ) };
1015 if ( result.length() > 255 )
1017 result.truncate( 255 );
1018 result.append( QStringLiteral(
"…" ) );
1032 setObjectName( QStringLiteral(
"QgsQueryResultDialog" ) );
1035 mWidget =
new QgsQueryResultWidget(
this, connection );
1036 QVBoxLayout *l =
new QVBoxLayout();
1037 l->setContentsMargins( 0, 0, 0, 0 );
1038 l->addWidget( mWidget );
1042void QgsQueryResultDialog::closeEvent( QCloseEvent *event )
1044 if ( !mWidget->promptUnsavedChanges() )
1059 : mIdentifierName( identifierName )
1061 setObjectName( QStringLiteral(
"SQLCommandsDialog" ) );
1065 mWidget =
new QgsQueryResultWidget(
nullptr, connection );
1066 setCentralWidget( mWidget );
1068 connect( mWidget, &QgsQueryResultWidget::requestDialogTitleUpdate,
this, &QgsQueryResultMainWindow::updateWindowTitle );
1070 updateWindowTitle( QString() );
1073void QgsQueryResultMainWindow::closeEvent( QCloseEvent *event )
1075 if ( !mWidget->promptUnsavedChanges() )
1085void QgsQueryResultMainWindow::updateWindowTitle(
const QString &fileName )
1087 if ( fileName.isEmpty() )
1089 if ( !mIdentifierName.isEmpty() )
1090 setWindowTitle( tr(
"%1 — Execute SQL" ).arg( mIdentifierName ) );
1092 setWindowTitle( tr(
"Execute SQL" ) );
1096 if ( !mIdentifierName.isEmpty() )
1097 setWindowTitle( tr(
"%1 — %2 — Execute SQL" ).arg( fileName, mIdentifierName ) );
1099 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.
The QgsAbstractDatabaseProviderConnection class provides common functionality for DB based connection...
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 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.
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
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.