QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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
30#include <QClipboard>
31#include <QShortcut>
32
34 : QWidget( parent )
35{
36 setupUi( this );
37
38 // Unsure :/
39 // mSqlEditor->setLineNumbersVisible( true );
40
41 mQueryResultsTableView->hide();
42 mQueryResultsTableView->setItemDelegate( new QgsQueryResultItemDelegate( mQueryResultsTableView ) );
43 mQueryResultsTableView->setContextMenuPolicy( Qt::CustomContextMenu );
44 connect( mQueryResultsTableView, &QTableView::customContextMenuRequested, this, &QgsQueryResultWidget::showCellContextMenu );
45
46 mProgressBar->hide();
47
48 connect( mExecuteButton, &QPushButton::pressed, this, &QgsQueryResultWidget::executeQuery );
49 connect( mClearButton, &QPushButton::pressed, this, [ = ]
50 {
51 mSqlEditor->setText( QString() );
52 } );
53 connect( mLoadLayerPushButton, &QPushButton::pressed, this, [ = ]
54 {
55 if ( mConnection )
56 {
57 emit createSqlVectorLayer( mConnection->providerKey(), mConnection->uri(), sqlVectorLayerOptions() );
58 }
59 }
60 );
61 connect( mSqlEditor, &QgsCodeEditorSQL::textChanged, this, &QgsQueryResultWidget::updateButtons );
62 connect( mFilterToolButton, &QToolButton::pressed, this, [ = ]
63 {
64 if ( mConnection )
65 {
66 try
67 {
68 std::unique_ptr<QgsVectorLayer> vlayer { mConnection->createSqlVectorLayer( sqlVectorLayerOptions() ) };
69 QgsQueryBuilder builder{ vlayer.get() };
70 if ( builder.exec() == QDialog::Accepted )
71 {
72 mFilterLineEdit->setText( builder.sql() );
73 }
74 }
75 catch ( const QgsProviderConnectionException &ex )
76 {
77 mMessageBar->pushCritical( tr( "Error opening filter dialog" ), tr( "There was an error while preparing SQL filter dialog: %1." ).arg( ex.what() ) );
78 }
79 }
80 } );
81
82
83 mStatusLabel->hide();
84 mSqlErrorText->hide();
85
86 mLoadAsNewLayerGroupBox->setCollapsed( true );
87
88 connect( mLoadAsNewLayerGroupBox, &QgsCollapsibleGroupBox::collapsedStateChanged, this, [ = ]( bool collapsed )
89 {
90 if ( ! collapsed )
91 {
92 // Configure the load layer interface
93 const bool showPkConfig { connection &&connection->sqlLayerDefinitionCapabilities().testFlag( Qgis::SqlLayerDefinitionCapability::PrimaryKeys )};
94 mPkColumnsCheckBox->setVisible( showPkConfig );
95 mPkColumnsComboBox->setVisible( showPkConfig );
96
97 const bool showGeometryColumnConfig {connection &&connection->sqlLayerDefinitionCapabilities().testFlag( Qgis::SqlLayerDefinitionCapability::GeometryColumn )};
98 mGeometryColumnCheckBox->setVisible( showGeometryColumnConfig );
99 mGeometryColumnComboBox->setVisible( showGeometryColumnConfig );
100
101 const bool showFilterConfig { connection &&connection->sqlLayerDefinitionCapabilities().testFlag( Qgis::SqlLayerDefinitionCapability::SubsetStringFilter ) };
102 mFilterLabel->setVisible( showFilterConfig );
103 mFilterToolButton->setVisible( showFilterConfig );
104 mFilterLineEdit->setVisible( showFilterConfig );
105
106 const bool showDisableSelectAtId{ connection &&connection->sqlLayerDefinitionCapabilities().testFlag( Qgis::SqlLayerDefinitionCapability::UnstableFeatureIds ) };
107 mAvoidSelectingAsFeatureIdCheckBox->setVisible( showDisableSelectAtId );
108
109 }
110 } );
111
112 QShortcut *copySelection = new QShortcut( QKeySequence::Copy, mQueryResultsTableView );
113 connect( copySelection, &QShortcut::activated, this, &QgsQueryResultWidget::copySelection );
114
115 setConnection( connection );
116}
117
119{
120 cancelApiFetcher();
121 cancelRunningQuery();
122}
123
125{
126 mSqlVectorLayerOptions = options;
127 if ( ! options.sql.isEmpty() )
128 {
129 setQuery( options.sql );
130 }
131 mAvoidSelectingAsFeatureIdCheckBox->setChecked( options.disableSelectAtId );
132 mPkColumnsCheckBox->setChecked( ! options.primaryKeyColumns.isEmpty() );
133 mPkColumnsComboBox->setCheckedItems( {} );
134 if ( ! options.primaryKeyColumns.isEmpty() )
135 {
136 mPkColumnsComboBox->setCheckedItems( options.primaryKeyColumns );
137 }
138 mGeometryColumnCheckBox->setChecked( ! options.geometryColumn.isEmpty() );
139 mGeometryColumnComboBox->clear();
140 if ( ! options.geometryColumn.isEmpty() )
141 {
142 mGeometryColumnComboBox->setCurrentText( options.geometryColumn );
143 }
144 mFilterLineEdit->setText( options.filter );
145 mLayerNameLineEdit->setText( options.layerName );
146}
147
149{
150 mQueryWidgetMode = widgetMode;
151 switch ( widgetMode )
152 {
154 mLoadAsNewLayerGroupBox->setTitle( tr( "Load as New Layer" ) );
155 mLoadLayerPushButton->setText( tr( "Load Layer" ) );
156 mLoadAsNewLayerGroupBox->setCollapsed( true );
157 break;
159 mLoadAsNewLayerGroupBox->setTitle( tr( "Update Query Layer" ) );
160 mLoadLayerPushButton->setText( tr( "Update Layer" ) );
161 mLoadAsNewLayerGroupBox->setCollapsed( false );
162 break;
163 }
164}
165
167{
168 mQueryResultsTableView->hide();
169 mSqlErrorText->hide();
170 mFirstRowFetched = false;
171
172 cancelRunningQuery();
173 if ( mConnection )
174 {
175 const QString sql { mSqlEditor->text( ) };
176
177 bool ok = false;
178 mCurrentHistoryEntryId = QgsGui::historyProviderRegistry()->addEntry( QStringLiteral( "dbquery" ),
179 QVariantMap
180 {
181 { QStringLiteral( "query" ), sql },
182 { QStringLiteral( "provider" ), mConnection->providerKey() },
183 { QStringLiteral( "connection" ), mConnection->uri() },
184 },
185 ok );
186
187 mWasCanceled = false;
188 mFeedback = std::make_unique<QgsFeedback>();
189 mStopButton->setEnabled( true );
190 mStatusLabel->show();
191 mStatusLabel->setText( tr( "Executing query…" ) );
192 mProgressBar->show();
193 mProgressBar->setRange( 0, 0 );
194 mSqlErrorMessage.clear();
195
196 connect( mStopButton, &QPushButton::pressed, mFeedback.get(), [ = ]
197 {
198 mStatusLabel->setText( tr( "Stopped" ) );
199 mFeedback->cancel();
200 mProgressBar->hide();
201 mWasCanceled = true;
202 } );
203
204 // Create model when result is ready
205 connect( &mQueryResultWatcher, &QFutureWatcher<QgsAbstractDatabaseProviderConnection::QueryResult>::finished, this, &QgsQueryResultWidget::startFetching, Qt::ConnectionType::UniqueConnection );
206
207 QFuture<QgsAbstractDatabaseProviderConnection::QueryResult> future = QtConcurrent::run( [ = ]() -> QgsAbstractDatabaseProviderConnection::QueryResult
208 {
209 try
210 {
211 return mConnection->execSql( sql, mFeedback.get() );
212 }
214 {
215 mSqlErrorMessage = ex.what();
217 }
218 } );
219 mQueryResultWatcher.setFuture( future );
220 }
221 else
222 {
223 showError( tr( "Connection error" ), tr( "Cannot execute query: connection to the database is not available." ) );
224 }
225}
226
227void QgsQueryResultWidget::updateButtons()
228{
229 mFilterLineEdit->setEnabled( mFirstRowFetched );
230 mFilterToolButton->setEnabled( mFirstRowFetched );
231 mExecuteButton->setEnabled( ! mSqlEditor->text().isEmpty() );
232 mLoadAsNewLayerGroupBox->setVisible( mConnection && mConnection->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Capability::SqlLayers ) );
233 mLoadAsNewLayerGroupBox->setEnabled(
234 mSqlErrorMessage.isEmpty() &&
235 mFirstRowFetched
236 );
237}
238
239void QgsQueryResultWidget::showCellContextMenu( QPoint point )
240{
241 const QModelIndex modelIndex = mQueryResultsTableView->indexAt( point );
242 if ( modelIndex.isValid() )
243 {
244 QMenu *menu = new QMenu();
245 menu->setAttribute( Qt::WA_DeleteOnClose );
246
247 menu->addAction( QgsApplication::getThemeIcon( "mActionEditCopy.svg" ), tr( "Copy" ), this, [ = ]
248 {
249 copySelection();
250 }, 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() ),
360 QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() ) ) );
361 }
362 else
363 {
364 mStatusLabel->setText( QStringLiteral( "Query executed successfully (%1 s)" ).arg( QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() ) ) );
365 }
366 mProgressBar->hide();
367 mModel = std::make_unique<QgsQueryResultModel>( mQueryResultWatcher.result() );
368 connect( mFeedback.get(), &QgsFeedback::canceled, mModel.get(), [ = ]
369 {
370 mModel->cancel();
371 mWasCanceled = true;
372 } );
373
374 connect( mModel.get(), &QgsQueryResultModel::fetchMoreRows, this, [ = ]( long long maxRows )
375 {
376 mFetchedRowsBatchCount = 0;
377 mProgressBar->setRange( 0, maxRows );
378 mProgressBar->show();
379 } );
380
381 connect( mModel.get(), &QgsQueryResultModel::rowsInserted, this, [ = ]( const QModelIndex &, int first, int last )
382 {
383 if ( ! mFirstRowFetched )
384 {
385 emit firstResultBatchFetched();
386 mFirstRowFetched = true;
387 mQueryResultsTableView->show();
388 updateButtons();
389 updateSqlLayerColumns( );
390 mActualRowCount = mModel->queryResult().rowCount();
391 }
392 mStatusLabel->setText( tr( "Fetched rows: %1/%2 %3 %4 ms" )
393 .arg( QLocale().toString( mModel->rowCount( mModel->index( -1, -1 ) ) ),
394 mActualRowCount != -1 ? QLocale().toString( mActualRowCount ) : tr( "unknown" ),
395 mWasCanceled ? tr( "(stopped)" ) : QString(),
396 QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() ) ) );
397 mFetchedRowsBatchCount += last - first + 1;
398 mProgressBar->setValue( mFetchedRowsBatchCount );
399 } );
400
401 mQueryResultsTableView->setModel( mModel.get() );
402 mQueryResultsTableView->show();
403
404 connect( mModel.get(), &QgsQueryResultModel::fetchingComplete, mStopButton, [ = ]
405 {
406 bool ok = false;
407 const QgsHistoryEntry currentHistoryEntry = QgsGui::historyProviderRegistry()->entry( mCurrentHistoryEntryId, ok );
408 QVariantMap entryDetails = currentHistoryEntry.entry;
409 entryDetails.insert( QStringLiteral( "rows" ), mActualRowCount );
410 entryDetails.insert( QStringLiteral( "time" ), mQueryResultWatcher.result().queryExecutionTime() );
411
412 QgsGui::historyProviderRegistry()->updateEntry( mCurrentHistoryEntryId,
413 entryDetails );
414 mProgressBar->hide();
415 mStopButton->setEnabled( false );
416 } );
417 }
418 }
419 else
420 {
421 mStatusLabel->setText( tr( "SQL command aborted" ) );
422 mProgressBar->hide();
423 }
424}
425
426void QgsQueryResultWidget::showError( const QString &title, const QString &message, bool isSqlError )
427{
428 mStatusLabel->show();
429 mStatusLabel->setText( tr( "An error occurred while executing the query" ) );
430 mProgressBar->hide();
431 mQueryResultsTableView->hide();
432 if ( isSqlError )
433 {
434 mSqlErrorText->show();
435 mSqlErrorText->setText( message );
436 }
437 else
438 {
439 mMessageBar->pushCritical( title, message );
440 }
441}
442
443void QgsQueryResultWidget::tokensReady( const QStringList &tokens )
444{
445 mSqlEditor->setExtraKeywords( mSqlEditor->extraKeywords() + tokens );
446 mSqlErrorText->setExtraKeywords( mSqlErrorText->extraKeywords() + tokens );
447}
448
450{
451 const int rowCount = mModel->rowCount( QModelIndex() );
452 const int columnCount = mModel->columnCount( QModelIndex() );
453 copyResults( 0, rowCount - 1, 0, columnCount - 1 );
454}
455
456void QgsQueryResultWidget::copyResults( int fromRow, int toRow, int fromColumn, int toColumn )
457{
458 QStringList rowStrings;
459 QStringList columnStrings;
460
461 const int rowCount = mModel->rowCount( QModelIndex() );
462 const int columnCount = mModel->columnCount( QModelIndex() );
463
464 toRow = std::min( toRow, rowCount - 1 );
465 toColumn = std::min( toColumn, columnCount - 1 );
466
467 rowStrings.reserve( toRow - fromRow );
468
469 // add titles first
470 for ( int col = fromColumn; col <= toColumn; col++ )
471 {
472 columnStrings += mModel->headerData( col, Qt::Horizontal, Qt::DisplayRole ).toString();
473 }
474 rowStrings += columnStrings.join( QLatin1Char( '\t' ) );
475 columnStrings.clear();
476
477 for ( int row = fromRow; row <= toRow; row++ )
478 {
479 for ( int col = fromColumn; col <= toColumn; col++ )
480 {
481 columnStrings += mModel->data( mModel->index( row, col ), Qt::DisplayRole ).toString();
482 }
483 rowStrings += columnStrings.join( QLatin1Char( '\t' ) );
484 columnStrings.clear();
485 }
486
487 if ( !rowStrings.isEmpty() )
488 {
489 const QString text = rowStrings.join( QLatin1Char( '\n' ) );
490 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 );
491 html.replace( QLatin1String( "\t" ), QLatin1String( "</td><td>" ) ).replace( QLatin1String( "\n" ), QLatin1String( "</td></tr><tr><td>" ) );
492
493 QMimeData *mdata = new QMimeData();
494 mdata->setData( QStringLiteral( "text/html" ), html.toUtf8() );
495 if ( !text.isEmpty() )
496 {
497 mdata->setText( text );
498 }
499 // Transfers ownership to the clipboard object
500#ifdef Q_OS_LINUX
501 QApplication::clipboard()->setMimeData( mdata, QClipboard::Selection );
502#endif
503 QApplication::clipboard()->setMimeData( mdata, QClipboard::Clipboard );
504 }
505}
506
507QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions QgsQueryResultWidget::sqlVectorLayerOptions() const
508{
509 mSqlVectorLayerOptions.sql = mSqlEditor->text();
510 mSqlVectorLayerOptions.filter = mFilterLineEdit->text();
511 mSqlVectorLayerOptions.primaryKeyColumns = mPkColumnsComboBox->checkedItems();
512 mSqlVectorLayerOptions.geometryColumn = mGeometryColumnComboBox->currentText();
513 mSqlVectorLayerOptions.layerName = mLayerNameLineEdit->text();
514 mSqlVectorLayerOptions.disableSelectAtId = mAvoidSelectingAsFeatureIdCheckBox->isChecked();
515 QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions options { mSqlVectorLayerOptions };
516 // Override if not used
517 if ( ! mPkColumnsCheckBox->isChecked() )
518 {
519 options.primaryKeyColumns.clear();
520 }
521 if ( ! mGeometryColumnCheckBox->isChecked() )
522 {
523 options.geometryColumn.clear();
524 }
525 return options;
526}
527
529{
530 mConnection.reset( connection );
531
532 cancelApiFetcher();
533
534 if ( connection )
535 {
536
537 // Add provider specific APIs
538 const QMultiMap<Qgis::SqlKeywordCategory, QStringList> keywordsDict { connection->sqlDictionary() };
539 QStringList keywords;
540 for ( auto it = keywordsDict.constBegin(); it != keywordsDict.constEnd(); it++ )
541 {
542 keywords.append( it.value() );
543 }
544
545 // Add static keywords from provider
546 mSqlEditor->setExtraKeywords( keywords );
547 mSqlErrorText->setExtraKeywords( keywords );
548
549 // Add dynamic keywords in a separate thread
550 QThread *apiFetcherWorkerThread = new QThread();
551 QgsConnectionsApiFetcher *apiFetcher = new QgsConnectionsApiFetcher( mConnection->uri(), mConnection->providerKey() );
552 apiFetcher->moveToThread( apiFetcherWorkerThread );
553 connect( apiFetcherWorkerThread, &QThread::started, apiFetcher, &QgsConnectionsApiFetcher::fetchTokens );
554 connect( apiFetcher, &QgsConnectionsApiFetcher::tokensReady, this, &QgsQueryResultWidget::tokensReady );
555 connect( apiFetcher, &QgsConnectionsApiFetcher::fetchingFinished, apiFetcherWorkerThread, [apiFetcher, apiFetcherWorkerThread]
556 {
557 apiFetcherWorkerThread->quit();
558 apiFetcherWorkerThread->wait();
559 apiFetcherWorkerThread->deleteLater();
560 apiFetcher->deleteLater();
561 } );
562
563 mApiFetcher = apiFetcher;
564 apiFetcherWorkerThread->start();
565 }
566
567 updateButtons();
568
569}
570
571void QgsQueryResultWidget::setQuery( const QString &sql )
572{
573 mSqlEditor->setText( sql );
574}
575
576void QgsQueryResultWidget::notify( const QString &title, const QString &text, Qgis::MessageLevel level )
577{
578 mMessageBar->pushMessage( title, text, level );
579}
580
581
583
584void QgsConnectionsApiFetcher::fetchTokens()
585{
586 if ( mStopFetching )
587 {
588 emit fetchingFinished();
589 return;
590 }
591
592
594 if ( !md )
595 {
596 emit fetchingFinished();
597 return;
598 }
599 std::unique_ptr< QgsAbstractDatabaseProviderConnection > connection( static_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( mUri, {} ) ) );
600 if ( ! mStopFetching && connection )
601 {
602 mFeedback = std::make_unique< QgsFeedback >();
603 QStringList schemas;
604 if ( connection->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Capability::Schemas ) )
605 {
606 try
607 {
608 schemas = connection->schemas();
609 emit tokensReady( schemas );
610 }
612 {
613 QgsMessageLog::logMessage( tr( "Error retrieving schemas: %1" ).arg( ex.what() ), QStringLiteral( "QGIS" ), Qgis::MessageLevel::Warning );
614 }
615 }
616 else
617 {
618 schemas.push_back( QString() ); // Fake empty schema for DBs not supporting it
619 }
620
621 for ( const auto &schema : std::as_const( schemas ) )
622 {
623
624 if ( mStopFetching )
625 {
626 connection.reset();
627 emit fetchingFinished();
628 return;
629 }
630
631 QStringList tableNames;
632 try
633 {
634 const QList<QgsAbstractDatabaseProviderConnection::TableProperty> tables = connection->tables( schema, QgsAbstractDatabaseProviderConnection::TableFlags(), mFeedback.get() );
635 for ( const QgsAbstractDatabaseProviderConnection::TableProperty &table : std::as_const( tables ) )
636 {
637 if ( mStopFetching )
638 {
639 connection.reset();
640 emit fetchingFinished();
641 return;
642 }
643 tableNames.push_back( table.tableName() );
644 }
645 emit tokensReady( tableNames );
646 }
648 {
649 QgsMessageLog::logMessage( tr( "Error retrieving tables: %1" ).arg( ex.what() ), QStringLiteral( "QGIS" ), Qgis::MessageLevel::Warning );
650 }
651
652 // Get fields
653 for ( const auto &table : std::as_const( tableNames ) )
654 {
655
656 if ( mStopFetching )
657 {
658 connection.reset();
659 emit fetchingFinished();
660 return;
661 }
662
663 QStringList fieldNames;
664 try
665 {
666 const QgsFields fields( connection->fields( schema, table, mFeedback.get() ) );
667 if ( mStopFetching )
668 {
669 connection.reset();
670 emit fetchingFinished();
671 return;
672 }
673
674 for ( const auto &field : std::as_const( fields ) )
675 {
676 fieldNames.push_back( field.name() );
677 if ( mStopFetching )
678 {
679 connection.reset();
680 emit fetchingFinished();
681 return;
682 }
683 }
684 emit tokensReady( fieldNames );
685 }
687 {
688 QgsMessageLog::logMessage( tr( "Error retrieving fields for table %1: %2" ).arg( table, ex.what() ), QStringLiteral( "QGIS" ), Qgis::MessageLevel::Warning );
689 }
690 }
691 }
692 }
693
694 connection.reset();
695 emit fetchingFinished();
696}
697
698void QgsConnectionsApiFetcher::stopFetching()
699{
700 mStopFetching = 1;
701 if ( mFeedback )
702 mFeedback->cancel();
703}
704
705QgsQueryResultItemDelegate::QgsQueryResultItemDelegate( QObject *parent )
706 : QStyledItemDelegate( parent )
707{
708}
709
710QString QgsQueryResultItemDelegate::displayText( const QVariant &value, const QLocale &locale ) const
711{
712 Q_UNUSED( locale )
713 QString result { QgsExpressionUtils::toLocalizedString( value ) };
714 // Show no more than 255 characters
715 if ( result.length() > 255 )
716 {
717 result.truncate( 255 );
718 result.append( QStringLiteral( "…" ) );
719 }
720 return result;
721}
722
MessageLevel
Level for messages This will be used both for message log and message bar in application.
Definition: qgis.h:99
@ 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 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.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
void collapsedStateChanged(bool collapsed)
Signal emitted when groupbox collapsed/expanded state is changed, and when first shown.
QString what() const
Definition: qgsexception.h:49
void canceled()
Internal routines can connect to this signal if they use event loop.
Container of fields for a vector layer.
Definition: qgsfields.h:45
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.
Definition: qgsexception.h:101
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.
void fetchMoreRows(qlonglong maxRows)
Emitted when more rows are requested.
void fetchingComplete()
Emitted when rows have been fetched (all of them or a batch if maxRows was passed to fetchMoreRows() ...
void copyResults()
Copies the query results to the clipboard, as a formatted table.
void tokensReady(const QStringList &tokens)
Triggered when the threaded API fetcher has new tokens to add.
void notify(const QString &title, const QString &text, Qgis::MessageLevel level=Qgis::MessageLevel::Info)
Displays a message with text title and level in the widget's message bar.
void setQuery(const QString &sql)
Convenience method to set the SQL editor text to sql.
void setSqlVectorLayerOptions(const QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions &options)
Initializes the widget from options.
QgsQueryResultWidget(QWidget *parent=nullptr, QgsAbstractDatabaseProviderConnection *connection=nullptr)
Creates a QgsQueryResultWidget with the given connection, ownership is transferred to the widget.
void setConnection(QgsAbstractDatabaseProviderConnection *connection)
Sets the connection to connection, ownership is transferred to the widget.
void showError(const QString &title, const QString &message, bool isSqlError=false)
Hides the result table and shows the error title and message in the message bar or in the SQL error p...
void executeQuery()
Starts executing the query.
QueryWidgetMode
The QueryWidgetMode enum represents various modes for the widget appearance.
@ QueryLayerUpdateMode
SQL query layer update mode: the create SQL layer button is renamed to 'Update' and the SQL layer cre...
@ SqlQueryMode
Defaults widget mode for SQL execution and SQL query layer creation.
void createSqlVectorLayer(const QString &providerKey, const QString &connectionUri, const QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions &options)
Emitted when a new vector SQL (query) layer must be created.
void setWidgetMode(QueryWidgetMode widgetMode)
Sets the widget mode to widgetMode, default is SqlQueryMode.
@ 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.