17#include "moc_qgsqueryresultwidget.cpp"
43 mQueryResultsTableView->hide();
44 mQueryResultsTableView->setItemDelegate(
new QgsQueryResultItemDelegate( mQueryResultsTableView ) );
45 mQueryResultsTableView->setContextMenuPolicy( Qt::CustomContextMenu );
46 connect( mQueryResultsTableView, &QTableView::customContextMenuRequested,
this, &QgsQueryResultWidget::showCellContextMenu );
52 QVBoxLayout *vl =
new QVBoxLayout();
53 vl->setContentsMargins( 0, 0, 0, 0 );
54 vl->addWidget( mCodeEditorWidget );
55 mSqlEditorContainer->setLayout( vl );
57 connect( mExecuteButton, &QPushButton::pressed,
this, &QgsQueryResultWidget::executeQuery );
58 connect( mClearButton, &QPushButton::pressed,
this, [=] {
59 mSqlEditor->setText( QString() );
61 connect( mLoadLayerPushButton, &QPushButton::pressed,
this, [=] {
64 emit createSqlVectorLayer( mConnection->providerKey(), mConnection->uri(), sqlVectorLayerOptions() );
67 connect( mSqlEditor, &QgsCodeEditorSQL::textChanged,
this, &QgsQueryResultWidget::updateButtons );
68 connect( mSqlEditor, &QgsCodeEditorSQL::selectionChanged,
this, [=] {
69 mExecuteButton->setText( mSqlEditor->selectedText().isEmpty() ? tr(
"Execute" ) : tr(
"Execute Selection" ) );
71 connect( mFilterToolButton, &QToolButton::pressed,
this, [=] {
76 std::unique_ptr<QgsVectorLayer> vlayer { mConnection->createSqlVectorLayer( sqlVectorLayerOptions() ) };
78 if ( builder.exec() == QDialog::Accepted )
80 mFilterLineEdit->setText( builder.sql() );
85 mMessageBar->pushCritical( tr(
"Error opening filter dialog" ), tr(
"There was an error while preparing SQL filter dialog: %1." ).arg( ex.
what() ) );
92 mSqlErrorText->hide();
94 mLoadAsNewLayerGroupBox->setCollapsed(
true );
101 mPkColumnsCheckBox->setVisible( showPkConfig );
102 mPkColumnsComboBox->setVisible( showPkConfig );
105 mGeometryColumnCheckBox->setVisible( showGeometryColumnConfig );
106 mGeometryColumnComboBox->setVisible( showGeometryColumnConfig );
109 mFilterLabel->setVisible( showFilterConfig );
110 mFilterToolButton->setVisible( showFilterConfig );
111 mFilterLineEdit->setVisible( showFilterConfig );
114 mAvoidSelectingAsFeatureIdCheckBox->setVisible( showDisableSelectAtId );
118 QShortcut *copySelection =
new QShortcut( QKeySequence::Copy, mQueryResultsTableView );
119 connect( copySelection, &QShortcut::activated,
this, &QgsQueryResultWidget::copySelection );
121 setConnection( connection );
124QgsQueryResultWidget::~QgsQueryResultWidget()
127 cancelRunningQuery();
132 mSqlVectorLayerOptions = options;
133 if ( !options.
sql.isEmpty() )
135 setQuery( options.
sql );
139 mPkColumnsComboBox->setCheckedItems( {} );
144 mGeometryColumnCheckBox->setChecked( !options.
geometryColumn.isEmpty() );
145 mGeometryColumnComboBox->clear();
148 mGeometryColumnComboBox->setCurrentText( options.
geometryColumn );
150 mFilterLineEdit->setText( options.
filter );
151 mLayerNameLineEdit->setText( options.
layerName );
154void QgsQueryResultWidget::setWidgetMode( QueryWidgetMode widgetMode )
156 mQueryWidgetMode = widgetMode;
157 switch ( widgetMode )
159 case QueryWidgetMode::SqlQueryMode:
160 mLoadAsNewLayerGroupBox->setTitle( tr(
"Load as New Layer" ) );
161 mLoadLayerPushButton->setText( tr(
"Load Layer" ) );
162 mLoadAsNewLayerGroupBox->setCollapsed(
true );
164 case QueryWidgetMode::QueryLayerUpdateMode:
165 mLoadAsNewLayerGroupBox->setTitle( tr(
"Update Query Layer" ) );
166 mLoadLayerPushButton->setText( tr(
"Update Layer" ) );
167 mLoadAsNewLayerGroupBox->setCollapsed(
false );
172void QgsQueryResultWidget::executeQuery()
174 mQueryResultsTableView->hide();
175 mSqlErrorText->hide();
176 mFirstRowFetched =
false;
178 cancelRunningQuery();
181 const QString sql { mSqlEditor->selectedText().isEmpty() ? mSqlEditor->text() : mSqlEditor->selectedText() };
185 { QStringLiteral(
"query" ), sql },
186 { QStringLiteral(
"provider" ), mConnection->providerKey() },
187 { QStringLiteral(
"connection" ), mConnection->uri() },
191 mWasCanceled =
false;
192 mFeedback = std::make_unique<QgsFeedback>();
193 mStopButton->setEnabled(
true );
194 mStatusLabel->show();
195 mStatusLabel->setText( tr(
"Executing query…" ) );
196 mProgressBar->show();
197 mProgressBar->setRange( 0, 0 );
198 mSqlErrorMessage.clear();
200 connect( mStopButton, &QPushButton::pressed, mFeedback.get(), [=] {
201 mStatusLabel->setText( tr(
"Stopped" ) );
203 mProgressBar->hide();
208 connect( &mQueryResultWatcher, &QFutureWatcher<QgsAbstractDatabaseProviderConnection::QueryResult>::finished,
this, &QgsQueryResultWidget::startFetching, Qt::ConnectionType::UniqueConnection );
213 return mConnection->execSql( sql, mFeedback.get() );
217 mSqlErrorMessage = ex.
what();
221 mQueryResultWatcher.setFuture( future );
225 showError( tr(
"Connection error" ), tr(
"Cannot execute query: connection to the database is not available." ) );
229void QgsQueryResultWidget::updateButtons()
231 mFilterLineEdit->setEnabled( mFirstRowFetched );
232 mFilterToolButton->setEnabled( mFirstRowFetched );
233 const bool isEmpty = mSqlEditor->text().isEmpty();
234 mExecuteButton->setEnabled( !isEmpty );
235 mClearButton->setEnabled( !isEmpty );
237 mLoadAsNewLayerGroupBox->setEnabled(
238 mSqlErrorMessage.isEmpty() && mFirstRowFetched
242void QgsQueryResultWidget::showCellContextMenu( QPoint point )
244 const QModelIndex modelIndex = mQueryResultsTableView->indexAt( point );
245 if ( modelIndex.isValid() )
247 QMenu *menu =
new QMenu();
248 menu->setAttribute( Qt::WA_DeleteOnClose );
250 menu->addAction(
QgsApplication::getThemeIcon(
"mActionEditCopy.svg" ), tr(
"Copy" ),
this, [=] { copySelection(); }, QKeySequence::Copy );
252 menu->exec( mQueryResultsTableView->viewport()->mapToGlobal( point ) );
256void QgsQueryResultWidget::copySelection()
258 const QModelIndexList selection = mQueryResultsTableView->selectionModel()->selectedIndexes();
259 if ( selection.empty() )
266 for (
const QModelIndex &index : selection )
268 if ( minRow == -1 || index.row() < minRow )
269 minRow = index.row();
270 if ( maxRow == -1 || index.row() > maxRow )
271 maxRow = index.row();
272 if ( minCol == -1 || index.column() < minCol )
273 minCol = index.column();
274 if ( maxCol == -1 || index.column() > maxCol )
275 maxCol = index.column();
278 if ( minRow == maxRow && minCol == maxCol )
281 const QString text = mModel->data( selection.at( 0 ), Qt::DisplayRole ).toString();
282 QApplication::clipboard()->setText( text );
286 copyResults( minRow, maxRow, minCol, maxCol );
290void QgsQueryResultWidget::updateSqlLayerColumns()
295 mFilterToolButton->setEnabled(
true );
296 mFilterLineEdit->setEnabled(
true );
297 mPkColumnsComboBox->clear();
298 mGeometryColumnComboBox->clear();
299 const bool hasPkInformation { !mSqlVectorLayerOptions.primaryKeyColumns.isEmpty() };
300 const bool hasGeomColInformation { !mSqlVectorLayerOptions.geometryColumn.isEmpty() };
301 static const QStringList geomColCandidates { QStringLiteral(
"geom" ), QStringLiteral(
"geometry" ), QStringLiteral(
"the_geom" ) };
302 const QStringList constCols { mModel->columns() };
303 for (
const QString &
c : constCols )
305 const bool pkCheckedState = hasPkInformation ? mSqlVectorLayerOptions.primaryKeyColumns.contains(
c ) :
c.contains( QStringLiteral(
"id" ), Qt::CaseSensitivity::CaseInsensitive );
307 mPkColumnsComboBox->addItemWithCheckState(
c, pkCheckedState && mPkColumnsComboBox->checkedItems().isEmpty() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked );
308 mGeometryColumnComboBox->addItem(
c );
309 if ( !hasGeomColInformation && geomColCandidates.contains(
c, Qt::CaseSensitivity::CaseInsensitive ) )
311 mGeometryColumnComboBox->setCurrentText(
c );
314 mPkColumnsCheckBox->setChecked( hasPkInformation );
315 mGeometryColumnCheckBox->setChecked( hasGeomColInformation );
316 if ( hasGeomColInformation )
318 mGeometryColumnComboBox->setCurrentText( mSqlVectorLayerOptions.geometryColumn );
322void QgsQueryResultWidget::cancelRunningQuery()
331 if ( mQueryResultWatcher.isRunning() )
333 mQueryResultWatcher.waitForFinished();
337void QgsQueryResultWidget::cancelApiFetcher()
341 mApiFetcher->stopFetching();
346void QgsQueryResultWidget::startFetching()
350 if ( !mSqlErrorMessage.isEmpty() )
352 showError( tr(
"SQL error" ), mSqlErrorMessage,
true );
358 mStatusLabel->setText( QStringLiteral(
"Query executed successfully (%1 rows, %2 ms)" )
359 .arg( QLocale().toString( mQueryResultWatcher.result().rowCount() ), QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() ) ) );
363 mStatusLabel->setText( QStringLiteral(
"Query executed successfully (%1 s)" ).arg( QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() ) ) );
365 mProgressBar->hide();
366 mModel = std::make_unique<QgsQueryResultModel>( mQueryResultWatcher.result() );
372 connect( mModel.get(), &QgsQueryResultModel::fetchMoreRows,
this, [=](
long long maxRows ) {
373 mFetchedRowsBatchCount = 0;
374 mProgressBar->setRange( 0, maxRows );
375 mProgressBar->show();
378 connect( mModel.get(), &QgsQueryResultModel::rowsInserted,
this, [=](
const QModelIndex &,
int first,
int last ) {
379 if ( !mFirstRowFetched )
381 emit firstResultBatchFetched();
382 mFirstRowFetched = true;
383 mQueryResultsTableView->show();
385 updateSqlLayerColumns();
386 mActualRowCount = mModel->queryResult().rowCount();
388 mStatusLabel->setText( tr(
"Fetched rows: %1/%2 %3 %4 ms" )
389 .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() ) ) );
390 mFetchedRowsBatchCount += last - first + 1;
391 mProgressBar->setValue( mFetchedRowsBatchCount );
394 mQueryResultsTableView->setModel( mModel.get() );
395 mQueryResultsTableView->show();
397 connect( mModel.get(), &QgsQueryResultModel::fetchingComplete, mStopButton, [=] {
399 const QgsHistoryEntry currentHistoryEntry = QgsGui::historyProviderRegistry()->entry( mCurrentHistoryEntryId, ok );
400 QVariantMap entryDetails = currentHistoryEntry.entry;
401 entryDetails.insert( QStringLiteral(
"rows" ), mActualRowCount );
402 entryDetails.insert( QStringLiteral(
"time" ), mQueryResultWatcher.result().queryExecutionTime() );
404 QgsGui::historyProviderRegistry()->updateEntry( mCurrentHistoryEntryId, entryDetails );
405 mProgressBar->hide();
406 mStopButton->setEnabled( false );
412 mStatusLabel->setText( tr(
"SQL command aborted" ) );
413 mProgressBar->hide();
417void QgsQueryResultWidget::showError(
const QString &title,
const QString &message,
bool isSqlError )
419 mStatusLabel->show();
420 mStatusLabel->setText( tr(
"An error occurred while executing the query" ) );
421 mProgressBar->hide();
422 mQueryResultsTableView->hide();
425 mSqlErrorText->show();
426 mSqlErrorText->setText( message );
430 mMessageBar->pushCritical( title, message );
434void QgsQueryResultWidget::tokensReady(
const QStringList &tokens )
436 mSqlEditor->setExtraKeywords( mSqlEditor->extraKeywords() + tokens );
437 mSqlErrorText->setExtraKeywords( mSqlErrorText->extraKeywords() + tokens );
440void QgsQueryResultWidget::copyResults()
442 const int rowCount = mModel->rowCount( QModelIndex() );
443 const int columnCount = mModel->columnCount( QModelIndex() );
444 copyResults( 0, rowCount - 1, 0, columnCount - 1 );
447void QgsQueryResultWidget::copyResults(
int fromRow,
int toRow,
int fromColumn,
int toColumn )
449 QStringList rowStrings;
450 QStringList columnStrings;
452 const int rowCount = mModel->rowCount( QModelIndex() );
453 const int columnCount = mModel->columnCount( QModelIndex() );
455 toRow = std::min( toRow, rowCount - 1 );
456 toColumn = std::min( toColumn, columnCount - 1 );
458 rowStrings.reserve( toRow - fromRow );
461 for (
int col = fromColumn; col <= toColumn; col++ )
463 columnStrings += mModel->headerData( col, Qt::Horizontal, Qt::DisplayRole ).toString();
465 rowStrings += columnStrings.join( QLatin1Char(
'\t' ) );
466 columnStrings.clear();
468 for (
int row = fromRow; row <= toRow; row++ )
470 for (
int col = fromColumn; col <= toColumn; col++ )
472 columnStrings += mModel->data( mModel->index( row, col ), Qt::DisplayRole ).toString();
474 rowStrings += columnStrings.join( QLatin1Char(
'\t' ) );
475 columnStrings.clear();
478 if ( !rowStrings.isEmpty() )
480 const QString text = rowStrings.join( QLatin1Char(
'\n' ) );
481 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 );
482 html.replace( QLatin1String(
"\t" ), QLatin1String(
"</td><td>" ) ).replace( QLatin1String(
"\n" ), QLatin1String(
"</td></tr><tr><td>" ) );
484 QMimeData *mdata =
new QMimeData();
485 mdata->setData( QStringLiteral(
"text/html" ), html.toUtf8() );
486 if ( !text.isEmpty() )
488 mdata->setText( text );
492 QApplication::clipboard()->setMimeData( mdata, QClipboard::Selection );
494 QApplication::clipboard()->setMimeData( mdata, QClipboard::Clipboard );
500 mSqlVectorLayerOptions.
sql = mSqlEditor->text().replace( QRegularExpression(
";\\s*$" ), QString() );
501 mSqlVectorLayerOptions.filter = mFilterLineEdit->text();
502 mSqlVectorLayerOptions.primaryKeyColumns = mPkColumnsComboBox->checkedItems();
503 mSqlVectorLayerOptions.geometryColumn = mGeometryColumnComboBox->currentText();
504 mSqlVectorLayerOptions.layerName = mLayerNameLineEdit->text();
505 mSqlVectorLayerOptions.disableSelectAtId = mAvoidSelectingAsFeatureIdCheckBox->isChecked();
508 if ( !mPkColumnsCheckBox->isChecked() )
512 if ( !mGeometryColumnCheckBox->isChecked() )
521 mConnection.reset( connection );
528 const QMultiMap<Qgis::SqlKeywordCategory, QStringList> keywordsDict { connection->
sqlDictionary() };
529 QStringList keywords;
530 for (
auto it = keywordsDict.constBegin(); it != keywordsDict.constEnd(); it++ )
532 keywords.append( it.value() );
536 mSqlEditor->setExtraKeywords( keywords );
537 mSqlErrorText->setExtraKeywords( keywords );
540 QThread *apiFetcherWorkerThread =
new QThread();
541 QgsConnectionsApiFetcher *apiFetcher =
new QgsConnectionsApiFetcher( mConnection->uri(), mConnection->providerKey() );
542 apiFetcher->moveToThread( apiFetcherWorkerThread );
543 connect( apiFetcherWorkerThread, &QThread::started, apiFetcher, &QgsConnectionsApiFetcher::fetchTokens );
544 connect( apiFetcher, &QgsConnectionsApiFetcher::tokensReady,
this, &QgsQueryResultWidget::tokensReady );
545 connect( apiFetcher, &QgsConnectionsApiFetcher::fetchingFinished, apiFetcherWorkerThread, [apiFetcher, apiFetcherWorkerThread] {
546 apiFetcherWorkerThread->quit();
547 apiFetcherWorkerThread->wait();
548 apiFetcherWorkerThread->deleteLater();
549 apiFetcher->deleteLater();
552 mApiFetcher = apiFetcher;
553 apiFetcherWorkerThread->start();
559void QgsQueryResultWidget::setQuery(
const QString &sql )
561 mSqlEditor->setText( sql );
564void QgsQueryResultWidget::notify(
const QString &title,
const QString &text,
Qgis::MessageLevel level )
566 mMessageBar->pushMessage( title, text, level );
572void QgsConnectionsApiFetcher::fetchTokens()
576 emit fetchingFinished();
584 emit fetchingFinished();
588 if ( !mStopFetching && connection )
590 mFeedback = std::make_unique<QgsFeedback>();
596 schemas = connection->
schemas();
597 emit tokensReady( schemas );
606 schemas.push_back( QString() );
609 for (
const auto &schema : std::as_const( schemas ) )
614 emit fetchingFinished();
618 QStringList tableNames;
627 emit fetchingFinished();
630 tableNames.push_back( table.tableName() );
632 emit tokensReady( tableNames );
640 for (
const auto &table : std::as_const( tableNames ) )
645 emit fetchingFinished();
649 QStringList fieldNames;
652 const QgsFields fields( connection->
fields( schema, table, mFeedback.get() ) );
656 emit fetchingFinished();
660 for (
const auto &field : std::as_const( fields ) )
662 fieldNames.push_back( field.name() );
666 emit fetchingFinished();
670 emit tokensReady( fieldNames );
681 emit fetchingFinished();
684void QgsConnectionsApiFetcher::stopFetching()
691QgsQueryResultItemDelegate::QgsQueryResultItemDelegate( QObject *parent )
692 : QStyledItemDelegate( parent )
696QString QgsQueryResultItemDelegate::displayText(
const QVariant &value,
const QLocale &locale )
const
699 QString result { QgsExpressionUtils::toLocalizedString( value ) };
701 if ( result.length() > 255 )
703 result.truncate( 255 );
704 result.append( QStringLiteral(
"…" ) );
MessageLevel
Level for messages This will be used both for message log and message bar in application.
@ Warning
Warning message.
@ 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 QgsHistoryProviderRegistry * historyProviderRegistry()
Returns the global history provider registry, used for tracking history providers.
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)
Adds a message to the log instance (and creates it if necessary).
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.
@ UnknownCount
Provider returned an unknown feature count.
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.