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