QGIS API Documentation 3.41.0-Master (3440c17df1d)
Loading...
Searching...
No Matches
qgsqueryresultwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsqueryresultwidget.cpp - QgsQueryResultWidget
3
4 ---------------------
5 begin : 14.1.2021
6 copyright : (C) 2021 by Alessandro Pasotti
7 email : elpaso at itopen dot it
8 ***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
17#include "moc_qgsqueryresultwidget.cpp"
19#include "qgsexpressionutils.h"
20#include "qgscodeeditorsql.h"
21#include "qgsmessagelog.h"
22#include "qgsquerybuilder.h"
23#include "qgsvectorlayer.h"
24#include "qgsapplication.h"
25#include "qgsgui.h"
27#include "qgshistoryentry.h"
28#include "qgsproviderregistry.h"
29#include "qgsprovidermetadata.h"
30#include "qgscodeeditorwidget.h"
31
32#include <QClipboard>
33#include <QShortcut>
34
35QgsQueryResultWidget::QgsQueryResultWidget( QWidget *parent, QgsAbstractDatabaseProviderConnection *connection )
36 : QWidget( parent )
37{
38 setupUi( this );
39
40 // Unsure :/
41 // mSqlEditor->setLineNumbersVisible( true );
42
43 mQueryResultsTableView->hide();
44 mQueryResultsTableView->setItemDelegate( new QgsQueryResultItemDelegate( mQueryResultsTableView ) );
45 mQueryResultsTableView->setContextMenuPolicy( Qt::CustomContextMenu );
46 connect( mQueryResultsTableView, &QTableView::customContextMenuRequested, this, &QgsQueryResultWidget::showCellContextMenu );
47
48 mProgressBar->hide();
49
50 mSqlEditor = new QgsCodeEditorSQL();
51 mCodeEditorWidget = new QgsCodeEditorWidget( mSqlEditor, mMessageBar );
52 QVBoxLayout *vl = new QVBoxLayout();
53 vl->setContentsMargins( 0, 0, 0, 0 );
54 vl->addWidget( mCodeEditorWidget );
55 mSqlEditorContainer->setLayout( vl );
56
57 connect( mExecuteButton, &QPushButton::pressed, this, &QgsQueryResultWidget::executeQuery );
58 connect( mClearButton, &QPushButton::pressed, this, [ = ]
59 {
60 mSqlEditor->setText( QString() );
61 } );
62 connect( mLoadLayerPushButton, &QPushButton::pressed, this, [ = ]
63 {
64 if ( mConnection )
65 {
66 emit createSqlVectorLayer( mConnection->providerKey(), mConnection->uri(), sqlVectorLayerOptions() );
67 }
68 }
69 );
70 connect( mSqlEditor, &QgsCodeEditorSQL::textChanged, this, &QgsQueryResultWidget::updateButtons );
71 connect( mSqlEditor, &QgsCodeEditorSQL::selectionChanged, this, [ = ]
72 {
73 mExecuteButton->setText( mSqlEditor->selectedText().isEmpty() ? tr( "Execute" ) : tr( "Execute Selection" ) );
74 } );
75 connect( mFilterToolButton, &QToolButton::pressed, this, [ = ]
76 {
77 if ( mConnection )
78 {
79 try
80 {
81 std::unique_ptr<QgsVectorLayer> vlayer { mConnection->createSqlVectorLayer( sqlVectorLayerOptions() ) };
82 QgsQueryBuilder builder{ vlayer.get() };
83 if ( builder.exec() == QDialog::Accepted )
84 {
85 mFilterLineEdit->setText( builder.sql() );
86 }
87 }
88 catch ( const QgsProviderConnectionException &ex )
89 {
90 mMessageBar->pushCritical( tr( "Error opening filter dialog" ), tr( "There was an error while preparing SQL filter dialog: %1." ).arg( ex.what() ) );
91 }
92 }
93 } );
94
95
96 mStatusLabel->hide();
97 mSqlErrorText->hide();
98
99 mLoadAsNewLayerGroupBox->setCollapsed( true );
100
101 connect( mLoadAsNewLayerGroupBox, &QgsCollapsibleGroupBox::collapsedStateChanged, this, [ = ]( bool collapsed )
102 {
103 if ( ! collapsed )
104 {
105 // Configure the load layer interface
106 const bool showPkConfig { connection &&connection->sqlLayerDefinitionCapabilities().testFlag( Qgis::SqlLayerDefinitionCapability::PrimaryKeys )};
107 mPkColumnsCheckBox->setVisible( showPkConfig );
108 mPkColumnsComboBox->setVisible( showPkConfig );
109
110 const bool showGeometryColumnConfig {connection &&connection->sqlLayerDefinitionCapabilities().testFlag( Qgis::SqlLayerDefinitionCapability::GeometryColumn )};
111 mGeometryColumnCheckBox->setVisible( showGeometryColumnConfig );
112 mGeometryColumnComboBox->setVisible( showGeometryColumnConfig );
113
114 const bool showFilterConfig { connection &&connection->sqlLayerDefinitionCapabilities().testFlag( Qgis::SqlLayerDefinitionCapability::SubsetStringFilter ) };
115 mFilterLabel->setVisible( showFilterConfig );
116 mFilterToolButton->setVisible( showFilterConfig );
117 mFilterLineEdit->setVisible( showFilterConfig );
118
119 const bool showDisableSelectAtId{ connection &&connection->sqlLayerDefinitionCapabilities().testFlag( Qgis::SqlLayerDefinitionCapability::UnstableFeatureIds ) };
120 mAvoidSelectingAsFeatureIdCheckBox->setVisible( showDisableSelectAtId );
121
122 }
123 } );
124
125 QShortcut *copySelection = new QShortcut( QKeySequence::Copy, mQueryResultsTableView );
126 connect( copySelection, &QShortcut::activated, this, &QgsQueryResultWidget::copySelection );
127
128 setConnection( connection );
129}
130
131QgsQueryResultWidget::~QgsQueryResultWidget()
132{
133 cancelApiFetcher();
134 cancelRunningQuery();
135}
136
137void QgsQueryResultWidget::setSqlVectorLayerOptions( const QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions &options )
138{
139 mSqlVectorLayerOptions = options;
140 if ( ! options.sql.isEmpty() )
141 {
142 setQuery( options.sql );
143 }
144 mAvoidSelectingAsFeatureIdCheckBox->setChecked( options.disableSelectAtId );
145 mPkColumnsCheckBox->setChecked( ! options.primaryKeyColumns.isEmpty() );
146 mPkColumnsComboBox->setCheckedItems( {} );
147 if ( ! options.primaryKeyColumns.isEmpty() )
148 {
149 mPkColumnsComboBox->setCheckedItems( options.primaryKeyColumns );
150 }
151 mGeometryColumnCheckBox->setChecked( ! options.geometryColumn.isEmpty() );
152 mGeometryColumnComboBox->clear();
153 if ( ! options.geometryColumn.isEmpty() )
154 {
155 mGeometryColumnComboBox->setCurrentText( options.geometryColumn );
156 }
157 mFilterLineEdit->setText( options.filter );
158 mLayerNameLineEdit->setText( options.layerName );
159}
160
161void QgsQueryResultWidget::setWidgetMode( QueryWidgetMode widgetMode )
162{
163 mQueryWidgetMode = widgetMode;
164 switch ( widgetMode )
165 {
166 case QueryWidgetMode::SqlQueryMode:
167 mLoadAsNewLayerGroupBox->setTitle( tr( "Load as New Layer" ) );
168 mLoadLayerPushButton->setText( tr( "Load Layer" ) );
169 mLoadAsNewLayerGroupBox->setCollapsed( true );
170 break;
171 case QueryWidgetMode::QueryLayerUpdateMode:
172 mLoadAsNewLayerGroupBox->setTitle( tr( "Update Query Layer" ) );
173 mLoadLayerPushButton->setText( tr( "Update Layer" ) );
174 mLoadAsNewLayerGroupBox->setCollapsed( false );
175 break;
176 }
177}
178
179void QgsQueryResultWidget::executeQuery()
180{
181 mQueryResultsTableView->hide();
182 mSqlErrorText->hide();
183 mFirstRowFetched = false;
184
185 cancelRunningQuery();
186 if ( mConnection )
187 {
188 const QString sql { mSqlEditor->selectedText().isEmpty() ? mSqlEditor->text() : mSqlEditor->selectedText() };
189
190 bool ok = false;
191 mCurrentHistoryEntryId = QgsGui::historyProviderRegistry()->addEntry( QStringLiteral( "dbquery" ),
192 QVariantMap
193 {
194 { QStringLiteral( "query" ), sql },
195 { QStringLiteral( "provider" ), mConnection->providerKey() },
196 { QStringLiteral( "connection" ), mConnection->uri() },
197 },
198 ok );
199
200 mWasCanceled = false;
201 mFeedback = std::make_unique<QgsFeedback>();
202 mStopButton->setEnabled( true );
203 mStatusLabel->show();
204 mStatusLabel->setText( tr( "Executing query…" ) );
205 mProgressBar->show();
206 mProgressBar->setRange( 0, 0 );
207 mSqlErrorMessage.clear();
208
209 connect( mStopButton, &QPushButton::pressed, mFeedback.get(), [ = ]
210 {
211 mStatusLabel->setText( tr( "Stopped" ) );
212 mFeedback->cancel();
213 mProgressBar->hide();
214 mWasCanceled = true;
215 } );
216
217 // Create model when result is ready
218 connect( &mQueryResultWatcher, &QFutureWatcher<QgsAbstractDatabaseProviderConnection::QueryResult>::finished, this, &QgsQueryResultWidget::startFetching, Qt::ConnectionType::UniqueConnection );
219
220 QFuture<QgsAbstractDatabaseProviderConnection::QueryResult> future = QtConcurrent::run( [ = ]() -> QgsAbstractDatabaseProviderConnection::QueryResult
221 {
222 try
223 {
224 return mConnection->execSql( sql, mFeedback.get() );
225 }
227 {
228 mSqlErrorMessage = ex.what();
230 }
231 } );
232 mQueryResultWatcher.setFuture( future );
233 }
234 else
235 {
236 showError( tr( "Connection error" ), tr( "Cannot execute query: connection to the database is not available." ) );
237 }
238}
239
240void QgsQueryResultWidget::updateButtons()
241{
242 mFilterLineEdit->setEnabled( mFirstRowFetched );
243 mFilterToolButton->setEnabled( mFirstRowFetched );
244 const bool isEmpty = mSqlEditor->text().isEmpty();
245 mExecuteButton->setEnabled( !isEmpty );
246 mClearButton->setEnabled( !isEmpty );
247 mLoadAsNewLayerGroupBox->setVisible( mConnection && mConnection->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Capability::SqlLayers ) );
248 mLoadAsNewLayerGroupBox->setEnabled(
249 mSqlErrorMessage.isEmpty() &&
250 mFirstRowFetched
251 );
252}
253
254void QgsQueryResultWidget::showCellContextMenu( QPoint point )
255{
256 const QModelIndex modelIndex = mQueryResultsTableView->indexAt( point );
257 if ( modelIndex.isValid() )
258 {
259 QMenu *menu = new QMenu();
260 menu->setAttribute( Qt::WA_DeleteOnClose );
261
262 menu->addAction( QgsApplication::getThemeIcon( "mActionEditCopy.svg" ), tr( "Copy" ), this, [ = ]
263 {
264 copySelection();
265 }, QKeySequence::Copy );
266
267 menu->exec( mQueryResultsTableView->viewport()->mapToGlobal( point ) );
268 }
269}
270
271void QgsQueryResultWidget::copySelection()
272{
273 const QModelIndexList selection = mQueryResultsTableView->selectionModel()->selectedIndexes();
274 if ( selection.empty() )
275 return;
276
277 int minRow = -1;
278 int maxRow = -1;
279 int minCol = -1;
280 int maxCol = -1;
281 for ( const QModelIndex &index : selection )
282 {
283 if ( minRow == -1 || index.row() < minRow )
284 minRow = index.row();
285 if ( maxRow == -1 || index.row() > maxRow )
286 maxRow = index.row();
287 if ( minCol == -1 || index.column() < minCol )
288 minCol = index.column();
289 if ( maxCol == -1 || index.column() > maxCol )
290 maxCol = index.column();
291 }
292
293 if ( minRow == maxRow && minCol == maxCol )
294 {
295 // copy only one cell
296 const QString text = mModel->data( selection.at( 0 ), Qt::DisplayRole ).toString();
297 QApplication::clipboard()->setText( text );
298 }
299 else
300 {
301 copyResults( minRow, maxRow, minCol, maxCol );
302 }
303}
304
305void QgsQueryResultWidget::updateSqlLayerColumns( )
306{
307 // Precondition
308 Q_ASSERT( mModel );
309
310 mFilterToolButton->setEnabled( true );
311 mFilterLineEdit->setEnabled( true );
312 mPkColumnsComboBox->clear();
313 mGeometryColumnComboBox->clear();
314 const bool hasPkInformation { ! mSqlVectorLayerOptions.primaryKeyColumns.isEmpty() };
315 const bool hasGeomColInformation { ! mSqlVectorLayerOptions.geometryColumn.isEmpty() };
316 static const QStringList geomColCandidates { QStringLiteral( "geom" ), QStringLiteral( "geometry" ), QStringLiteral( "the_geom" ) };
317 const QStringList constCols { mModel->columns() };
318 for ( const QString &c : constCols )
319 {
320 const bool pkCheckedState = hasPkInformation ? mSqlVectorLayerOptions.primaryKeyColumns.contains( c ) : c.contains( QStringLiteral( "id" ), Qt::CaseSensitivity::CaseInsensitive );
321 // Only check first match
322 mPkColumnsComboBox->addItemWithCheckState( c, pkCheckedState && mPkColumnsComboBox->checkedItems().isEmpty() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked );
323 mGeometryColumnComboBox->addItem( c );
324 if ( ! hasGeomColInformation && geomColCandidates.contains( c, Qt::CaseSensitivity::CaseInsensitive ) )
325 {
326 mGeometryColumnComboBox->setCurrentText( c );
327 }
328 }
329 mPkColumnsCheckBox->setChecked( hasPkInformation );
330 mGeometryColumnCheckBox->setChecked( hasGeomColInformation );
331 if ( hasGeomColInformation )
332 {
333 mGeometryColumnComboBox->setCurrentText( mSqlVectorLayerOptions.geometryColumn );
334 }
335}
336
337void QgsQueryResultWidget::cancelRunningQuery()
338{
339 // Cancel other threads
340 if ( mFeedback )
341 {
342 mFeedback->cancel();
343 }
344
345 // ... and wait
346 if ( mQueryResultWatcher.isRunning() )
347 {
348 mQueryResultWatcher.waitForFinished();
349 }
350}
351
352void QgsQueryResultWidget::cancelApiFetcher()
353{
354 if ( mApiFetcher )
355 {
356 mApiFetcher->stopFetching();
357 // apiFetcher and apiFetcherWorkerThread will be deleted when the thread fetchingFinished signal is emitted
358 }
359}
360
361void QgsQueryResultWidget::startFetching()
362{
363 if ( ! mWasCanceled )
364 {
365 if ( ! mSqlErrorMessage.isEmpty() )
366 {
367 showError( tr( "SQL error" ), mSqlErrorMessage, true );
368 }
369 else
370 {
371 if ( mQueryResultWatcher.result().rowCount() != static_cast<long long>( Qgis::FeatureCountState::UnknownCount ) )
372 {
373 mStatusLabel->setText( QStringLiteral( "Query executed successfully (%1 rows, %2 ms)" )
374 .arg( QLocale().toString( mQueryResultWatcher.result().rowCount() ),
375 QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() ) ) );
376 }
377 else
378 {
379 mStatusLabel->setText( QStringLiteral( "Query executed successfully (%1 s)" ).arg( QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() ) ) );
380 }
381 mProgressBar->hide();
382 mModel = std::make_unique<QgsQueryResultModel>( mQueryResultWatcher.result() );
383 connect( mFeedback.get(), &QgsFeedback::canceled, mModel.get(), [ = ]
384 {
385 mModel->cancel();
386 mWasCanceled = true;
387 } );
388
389 connect( mModel.get(), &QgsQueryResultModel::fetchMoreRows, this, [ = ]( long long maxRows )
390 {
391 mFetchedRowsBatchCount = 0;
392 mProgressBar->setRange( 0, maxRows );
393 mProgressBar->show();
394 } );
395
396 connect( mModel.get(), &QgsQueryResultModel::rowsInserted, this, [ = ]( const QModelIndex &, int first, int last )
397 {
398 if ( ! mFirstRowFetched )
399 {
400 emit firstResultBatchFetched();
401 mFirstRowFetched = true;
402 mQueryResultsTableView->show();
403 updateButtons();
404 updateSqlLayerColumns( );
405 mActualRowCount = mModel->queryResult().rowCount();
406 }
407 mStatusLabel->setText( tr( "Fetched rows: %1/%2 %3 %4 ms" )
408 .arg( QLocale().toString( mModel->rowCount( mModel->index( -1, -1 ) ) ),
409 mActualRowCount != -1 ? QLocale().toString( mActualRowCount ) : tr( "unknown" ),
410 mWasCanceled ? tr( "(stopped)" ) : QString(),
411 QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() ) ) );
412 mFetchedRowsBatchCount += last - first + 1;
413 mProgressBar->setValue( mFetchedRowsBatchCount );
414 } );
415
416 mQueryResultsTableView->setModel( mModel.get() );
417 mQueryResultsTableView->show();
418
419 connect( mModel.get(), &QgsQueryResultModel::fetchingComplete, mStopButton, [ = ]
420 {
421 bool ok = false;
422 const QgsHistoryEntry currentHistoryEntry = QgsGui::historyProviderRegistry()->entry( mCurrentHistoryEntryId, ok );
423 QVariantMap entryDetails = currentHistoryEntry.entry;
424 entryDetails.insert( QStringLiteral( "rows" ), mActualRowCount );
425 entryDetails.insert( QStringLiteral( "time" ), mQueryResultWatcher.result().queryExecutionTime() );
426
427 QgsGui::historyProviderRegistry()->updateEntry( mCurrentHistoryEntryId,
428 entryDetails );
429 mProgressBar->hide();
430 mStopButton->setEnabled( false );
431 } );
432 }
433 }
434 else
435 {
436 mStatusLabel->setText( tr( "SQL command aborted" ) );
437 mProgressBar->hide();
438 }
439}
440
441void QgsQueryResultWidget::showError( const QString &title, const QString &message, bool isSqlError )
442{
443 mStatusLabel->show();
444 mStatusLabel->setText( tr( "An error occurred while executing the query" ) );
445 mProgressBar->hide();
446 mQueryResultsTableView->hide();
447 if ( isSqlError )
448 {
449 mSqlErrorText->show();
450 mSqlErrorText->setText( message );
451 }
452 else
453 {
454 mMessageBar->pushCritical( title, message );
455 }
456}
457
458void QgsQueryResultWidget::tokensReady( const QStringList &tokens )
459{
460 mSqlEditor->setExtraKeywords( mSqlEditor->extraKeywords() + tokens );
461 mSqlErrorText->setExtraKeywords( mSqlErrorText->extraKeywords() + tokens );
462}
463
464void QgsQueryResultWidget::copyResults()
465{
466 const int rowCount = mModel->rowCount( QModelIndex() );
467 const int columnCount = mModel->columnCount( QModelIndex() );
468 copyResults( 0, rowCount - 1, 0, columnCount - 1 );
469}
470
471void QgsQueryResultWidget::copyResults( int fromRow, int toRow, int fromColumn, int toColumn )
472{
473 QStringList rowStrings;
474 QStringList columnStrings;
475
476 const int rowCount = mModel->rowCount( QModelIndex() );
477 const int columnCount = mModel->columnCount( QModelIndex() );
478
479 toRow = std::min( toRow, rowCount - 1 );
480 toColumn = std::min( toColumn, columnCount - 1 );
481
482 rowStrings.reserve( toRow - fromRow );
483
484 // add titles first
485 for ( int col = fromColumn; col <= toColumn; col++ )
486 {
487 columnStrings += mModel->headerData( col, Qt::Horizontal, Qt::DisplayRole ).toString();
488 }
489 rowStrings += columnStrings.join( QLatin1Char( '\t' ) );
490 columnStrings.clear();
491
492 for ( int row = fromRow; row <= toRow; row++ )
493 {
494 for ( int col = fromColumn; col <= toColumn; col++ )
495 {
496 columnStrings += mModel->data( mModel->index( row, col ), Qt::DisplayRole ).toString();
497 }
498 rowStrings += columnStrings.join( QLatin1Char( '\t' ) );
499 columnStrings.clear();
500 }
501
502 if ( !rowStrings.isEmpty() )
503 {
504 const QString text = rowStrings.join( QLatin1Char( '\n' ) );
505 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 );
506 html.replace( QLatin1String( "\t" ), QLatin1String( "</td><td>" ) ).replace( QLatin1String( "\n" ), QLatin1String( "</td></tr><tr><td>" ) );
507
508 QMimeData *mdata = new QMimeData();
509 mdata->setData( QStringLiteral( "text/html" ), html.toUtf8() );
510 if ( !text.isEmpty() )
511 {
512 mdata->setText( text );
513 }
514 // Transfers ownership to the clipboard object
515#ifdef Q_OS_LINUX
516 QApplication::clipboard()->setMimeData( mdata, QClipboard::Selection );
517#endif
518 QApplication::clipboard()->setMimeData( mdata, QClipboard::Clipboard );
519 }
520}
521
522QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions QgsQueryResultWidget::sqlVectorLayerOptions() const
523{
524 mSqlVectorLayerOptions.sql = mSqlEditor->text();
525 mSqlVectorLayerOptions.filter = mFilterLineEdit->text();
526 mSqlVectorLayerOptions.primaryKeyColumns = mPkColumnsComboBox->checkedItems();
527 mSqlVectorLayerOptions.geometryColumn = mGeometryColumnComboBox->currentText();
528 mSqlVectorLayerOptions.layerName = mLayerNameLineEdit->text();
529 mSqlVectorLayerOptions.disableSelectAtId = mAvoidSelectingAsFeatureIdCheckBox->isChecked();
530 QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions options { mSqlVectorLayerOptions };
531 // Override if not used
532 if ( ! mPkColumnsCheckBox->isChecked() )
533 {
534 options.primaryKeyColumns.clear();
535 }
536 if ( ! mGeometryColumnCheckBox->isChecked() )
537 {
538 options.geometryColumn.clear();
539 }
540 return options;
541}
542
543void QgsQueryResultWidget::setConnection( QgsAbstractDatabaseProviderConnection *connection )
544{
545 mConnection.reset( connection );
546
547 cancelApiFetcher();
548
549 if ( connection )
550 {
551
552 // Add provider specific APIs
553 const QMultiMap<Qgis::SqlKeywordCategory, QStringList> keywordsDict { connection->sqlDictionary() };
554 QStringList keywords;
555 for ( auto it = keywordsDict.constBegin(); it != keywordsDict.constEnd(); it++ )
556 {
557 keywords.append( it.value() );
558 }
559
560 // Add static keywords from provider
561 mSqlEditor->setExtraKeywords( keywords );
562 mSqlErrorText->setExtraKeywords( keywords );
563
564 // Add dynamic keywords in a separate thread
565 QThread *apiFetcherWorkerThread = new QThread();
566 QgsConnectionsApiFetcher *apiFetcher = new QgsConnectionsApiFetcher( mConnection->uri(), mConnection->providerKey() );
567 apiFetcher->moveToThread( apiFetcherWorkerThread );
568 connect( apiFetcherWorkerThread, &QThread::started, apiFetcher, &QgsConnectionsApiFetcher::fetchTokens );
569 connect( apiFetcher, &QgsConnectionsApiFetcher::tokensReady, this, &QgsQueryResultWidget::tokensReady );
570 connect( apiFetcher, &QgsConnectionsApiFetcher::fetchingFinished, apiFetcherWorkerThread, [apiFetcher, apiFetcherWorkerThread]
571 {
572 apiFetcherWorkerThread->quit();
573 apiFetcherWorkerThread->wait();
574 apiFetcherWorkerThread->deleteLater();
575 apiFetcher->deleteLater();
576 } );
577
578 mApiFetcher = apiFetcher;
579 apiFetcherWorkerThread->start();
580 }
581
582 updateButtons();
583
584}
585
586void QgsQueryResultWidget::setQuery( const QString &sql )
587{
588 mSqlEditor->setText( sql );
589}
590
591void QgsQueryResultWidget::notify( const QString &title, const QString &text, Qgis::MessageLevel level )
592{
593 mMessageBar->pushMessage( title, text, level );
594}
595
596
598
599void QgsConnectionsApiFetcher::fetchTokens()
600{
601 if ( mStopFetching )
602 {
603 emit fetchingFinished();
604 return;
605 }
606
607
609 if ( !md )
610 {
611 emit fetchingFinished();
612 return;
613 }
614 std::unique_ptr< QgsAbstractDatabaseProviderConnection > connection( static_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( mUri, {} ) ) );
615 if ( ! mStopFetching && connection )
616 {
617 mFeedback = std::make_unique< QgsFeedback >();
618 QStringList schemas;
620 {
621 try
622 {
623 schemas = connection->schemas();
624 emit tokensReady( schemas );
625 }
627 {
628 QgsMessageLog::logMessage( tr( "Error retrieving schemas: %1" ).arg( ex.what() ), QStringLiteral( "QGIS" ), Qgis::MessageLevel::Warning );
629 }
630 }
631 else
632 {
633 schemas.push_back( QString() ); // Fake empty schema for DBs not supporting it
634 }
635
636 for ( const auto &schema : std::as_const( schemas ) )
637 {
638
639 if ( mStopFetching )
640 {
641 connection.reset();
642 emit fetchingFinished();
643 return;
644 }
645
646 QStringList tableNames;
647 try
648 {
649 const QList<QgsAbstractDatabaseProviderConnection::TableProperty> tables = connection->tables( schema, QgsAbstractDatabaseProviderConnection::TableFlags(), mFeedback.get() );
650 for ( const QgsAbstractDatabaseProviderConnection::TableProperty &table : std::as_const( tables ) )
651 {
652 if ( mStopFetching )
653 {
654 connection.reset();
655 emit fetchingFinished();
656 return;
657 }
658 tableNames.push_back( table.tableName() );
659 }
660 emit tokensReady( tableNames );
661 }
663 {
664 QgsMessageLog::logMessage( tr( "Error retrieving tables: %1" ).arg( ex.what() ), QStringLiteral( "QGIS" ), Qgis::MessageLevel::Warning );
665 }
666
667 // Get fields
668 for ( const auto &table : std::as_const( tableNames ) )
669 {
670
671 if ( mStopFetching )
672 {
673 connection.reset();
674 emit fetchingFinished();
675 return;
676 }
677
678 QStringList fieldNames;
679 try
680 {
681 const QgsFields fields( connection->fields( schema, table, mFeedback.get() ) );
682 if ( mStopFetching )
683 {
684 connection.reset();
685 emit fetchingFinished();
686 return;
687 }
688
689 for ( const auto &field : std::as_const( fields ) )
690 {
691 fieldNames.push_back( field.name() );
692 if ( mStopFetching )
693 {
694 connection.reset();
695 emit fetchingFinished();
696 return;
697 }
698 }
699 emit tokensReady( fieldNames );
700 }
702 {
703 QgsMessageLog::logMessage( tr( "Error retrieving fields for table %1: %2" ).arg( table, ex.what() ), QStringLiteral( "QGIS" ), Qgis::MessageLevel::Warning );
704 }
705 }
706 }
707 }
708
709 connection.reset();
710 emit fetchingFinished();
711}
712
713void QgsConnectionsApiFetcher::stopFetching()
714{
715 mStopFetching = 1;
716 if ( mFeedback )
717 mFeedback->cancel();
718}
719
720QgsQueryResultItemDelegate::QgsQueryResultItemDelegate( QObject *parent )
721 : QStyledItemDelegate( parent )
722{
723}
724
725QString QgsQueryResultItemDelegate::displayText( const QVariant &value, const QLocale &locale ) const
726{
727 Q_UNUSED( locale )
728 QString result { QgsExpressionUtils::toLocalizedString( value ) };
729 // Show no more than 255 characters
730 if ( result.length() > 255 )
731 {
732 result.truncate( 255 );
733 result.append( QStringLiteral( "…" ) );
734 }
735 return result;
736}
737
MessageLevel
Level for messages This will be used both for message log and message bar in application.
Definition qgis.h:154
@ Warning
Warning message.
Definition qgis.h:156
@ 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.
@ 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.
A widget which wraps a QgsCodeEditor in additional functionality.
void collapsedStateChanged(bool collapsed)
Signal emitted when groupbox collapsed/expanded state is changed, and when first shown.
QString what() const
void canceled()
Internal routines can connect to this signal if they use event loop.
Container of fields for a vector layer.
Definition qgsfields.h:46
static QgsHistoryProviderRegistry * historyProviderRegistry()
Returns the global history provider registry, used for tracking history providers.
Definition qgsgui.cpp:199
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.
Holds data provider key, description, and associated shared library file or function pointer informat...
virtual QgsAbstractProviderConnection * createConnection(const QString &uri, const QVariantMap &configuration)
Creates a new connection from uri and configuration, the newly created connection is not automaticall...
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.
QString filter
Additional subset string (provider-side filter), not all data providers support this feature: check s...
bool disableSelectAtId
If SelectAtId is disabled (default is false), not all data providers support this feature: check supp...
The TableProperty class represents a database table or view.