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