QGIS API Documentation 3.43.0-Master (c4a2e9c6d2f)
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#include "qgsfileutils.h"
33#include "qgsproject.h"
34#include "qgsnewnamedialog.h"
35#include "qgshistorywidget.h"
37
38#include <QClipboard>
39#include <QShortcut>
40#include <QFileDialog>
41#include <QMessageBox>
42#include <QInputDialog>
43
45const QgsSettingsEntryString *QgsQueryResultWidget::settingLastSourceFolder = new QgsSettingsEntryString( QStringLiteral( "last-source-folder" ), sTreeSqlQueries, QString(), QStringLiteral( "Last used folder for SQL source files" ) );
48
49
50//
51// QgsQueryResultPanelWidget
52//
53
54QgsQueryResultPanelWidget::QgsQueryResultPanelWidget( QWidget *parent, QgsAbstractDatabaseProviderConnection *connection )
55 : QgsPanelWidget( parent )
56{
57 setupUi( this );
58
59 // Unsure :/
60 // mSqlEditor->setLineNumbersVisible( true );
61
62 splitter->setCollapsible( 0, false );
63 splitter->setCollapsible( 1, false );
64 QgsSettings settings;
65 splitter->restoreState( settings.value( QStringLiteral( "Windows/QueryResult/SplitState" ) ).toByteArray() );
66
67 connect( splitter, &QSplitter::splitterMoved, this, [this] {
68 QgsSettings settings;
69 settings.setValue( QStringLiteral( "Windows/QueryResult/SplitState" ), splitter->saveState() );
70 } );
71
72 // explicitly needed for some reason (Qt 5.15)
73 mainLayout->setSpacing( 6 );
74 progressLayout->setSpacing( 6 );
75
76 mQueryResultsTableView->hide();
77 mQueryResultsTableView->setItemDelegate( new QgsQueryResultItemDelegate( mQueryResultsTableView ) );
78 mQueryResultsTableView->setContextMenuPolicy( Qt::CustomContextMenu );
79 connect( mQueryResultsTableView, &QTableView::customContextMenuRequested, this, &QgsQueryResultPanelWidget::showCellContextMenu );
80
81 mProgressBar->hide();
82
83 mSqlEditor = new QgsCodeEditorSQL();
84 mCodeEditorWidget = new QgsCodeEditorWidget( mSqlEditor, mMessageBar );
85 QVBoxLayout *vl = new QVBoxLayout();
86 vl->setContentsMargins( 0, 0, 0, 0 );
87 vl->addWidget( mCodeEditorWidget );
88 mSqlEditorContainer->setLayout( vl );
89
90 connect( mExecuteButton, &QPushButton::pressed, this, &QgsQueryResultPanelWidget::executeQuery );
91
92 connect( mLoadLayerPushButton, &QPushButton::pressed, this, [=] {
93 if ( mConnection )
94 {
95 const QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions options = sqlVectorLayerOptions();
96
97 try
98 {
99 QString message;
100 const bool res = mConnection->validateSqlVectorLayer( options, message );
101 if ( !res )
102 {
103 mMessageBar->pushCritical( QString(), message );
104 }
105 else
106 {
107 emit createSqlVectorLayer( mConnection->providerKey(), mConnection->uri(), options );
108 }
109 }
111 {
112 mMessageBar->pushCritical( tr( "Error validating query" ), e.what() );
113 }
114 }
115 } );
116 connect( mSqlEditor, &QgsCodeEditorSQL::textChanged, this, &QgsQueryResultPanelWidget::updateButtons );
117
118 connect( mSqlEditor, &QgsCodeEditorSQL::selectionChanged, this, [=] {
119 mExecuteButton->setText( mSqlEditor->selectedText().isEmpty() ? tr( "Execute" ) : tr( "Execute Selection" ) );
120 } );
121 connect( mFilterToolButton, &QToolButton::pressed, this, [=] {
122 if ( mConnection )
123 {
124 try
125 {
126 std::unique_ptr<QgsVectorLayer> vlayer { mConnection->createSqlVectorLayer( sqlVectorLayerOptions() ) };
127 QgsQueryBuilder builder { vlayer.get() };
128 if ( builder.exec() == QDialog::Accepted )
129 {
130 mFilterLineEdit->setText( builder.sql() );
131 }
132 }
133 catch ( const QgsProviderConnectionException &ex )
134 {
135 mMessageBar->pushCritical( tr( "Error opening filter dialog" ), tr( "There was an error while preparing SQL filter dialog: %1." ).arg( ex.what() ) );
136 }
137 }
138 } );
139
140
141 mStatusLabel->hide();
142 mSqlErrorText->hide();
143
144 mLoadAsNewLayerGroupBox->setCollapsed( true );
145
146 connect( mLoadAsNewLayerGroupBox, &QgsCollapsibleGroupBox::collapsedStateChanged, this, [=]( bool collapsed ) {
147 if ( !collapsed )
148 {
149 // Configure the load layer interface
150 const bool showPkConfig { connection && connection->sqlLayerDefinitionCapabilities().testFlag( Qgis::SqlLayerDefinitionCapability::PrimaryKeys ) };
151 mPkColumnsCheckBox->setVisible( showPkConfig );
152 mPkColumnsComboBox->setVisible( showPkConfig );
153
154 const bool showGeometryColumnConfig { connection && connection->sqlLayerDefinitionCapabilities().testFlag( Qgis::SqlLayerDefinitionCapability::GeometryColumn ) };
155 mGeometryColumnCheckBox->setVisible( showGeometryColumnConfig );
156 mGeometryColumnComboBox->setVisible( showGeometryColumnConfig );
157
158 const bool showFilterConfig { connection && connection->sqlLayerDefinitionCapabilities().testFlag( Qgis::SqlLayerDefinitionCapability::SubsetStringFilter ) };
159 mFilterLabel->setVisible( showFilterConfig );
160 mFilterToolButton->setVisible( showFilterConfig );
161 mFilterLineEdit->setVisible( showFilterConfig );
162
163 const bool showDisableSelectAtId { connection && connection->sqlLayerDefinitionCapabilities().testFlag( Qgis::SqlLayerDefinitionCapability::UnstableFeatureIds ) };
164 mAvoidSelectingAsFeatureIdCheckBox->setVisible( showDisableSelectAtId );
165 }
166 } );
167
168 QShortcut *copySelection = new QShortcut( QKeySequence::Copy, mQueryResultsTableView );
169 connect( copySelection, &QShortcut::activated, this, &QgsQueryResultPanelWidget::copySelection );
170
171 setConnection( connection );
172}
173
174QgsQueryResultPanelWidget::~QgsQueryResultPanelWidget()
175{
176 cancelApiFetcher();
177 cancelRunningQuery();
178}
179
180QgsCodeEditorSQL *QgsQueryResultPanelWidget::sqlEditor()
181{
182 return mSqlEditor;
183}
184
185QgsCodeEditorWidget *QgsQueryResultPanelWidget::codeEditorWidget()
186{
187 return mCodeEditorWidget;
188}
189
190void QgsQueryResultPanelWidget::setSqlVectorLayerOptions( const QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions &options )
191{
192 mSqlVectorLayerOptions = options;
193 if ( !options.sql.isEmpty() )
194 {
195 setQuery( options.sql );
196 }
197 mAvoidSelectingAsFeatureIdCheckBox->setChecked( options.disableSelectAtId );
198 mPkColumnsCheckBox->setChecked( !options.primaryKeyColumns.isEmpty() );
199 mPkColumnsComboBox->setCheckedItems( {} );
200 if ( !options.primaryKeyColumns.isEmpty() )
201 {
202 mPkColumnsComboBox->setCheckedItems( options.primaryKeyColumns );
203 }
204 mGeometryColumnCheckBox->setChecked( !options.geometryColumn.isEmpty() );
205 mGeometryColumnComboBox->clear();
206 if ( !options.geometryColumn.isEmpty() )
207 {
208 mGeometryColumnComboBox->setCurrentText( options.geometryColumn );
209 }
210 mFilterLineEdit->setText( options.filter );
211 mLayerNameLineEdit->setText( options.layerName );
212}
213
214void QgsQueryResultPanelWidget::setWidgetMode( QgsQueryResultWidget::QueryWidgetMode widgetMode )
215{
216 mQueryWidgetMode = widgetMode;
217 switch ( widgetMode )
218 {
219 case QgsQueryResultWidget::QueryWidgetMode::SqlQueryMode:
220 mLoadAsNewLayerGroupBox->setTitle( tr( "Load as New Layer" ) );
221 mLoadLayerPushButton->setText( tr( "Load Layer" ) );
222 mLoadAsNewLayerGroupBox->setCollapsed( true );
223 break;
224 case QgsQueryResultWidget::QueryWidgetMode::QueryLayerUpdateMode:
225 mLoadAsNewLayerGroupBox->setTitle( tr( "Update Query Layer" ) );
226 mLoadLayerPushButton->setText( tr( "Update Layer" ) );
227 mLoadAsNewLayerGroupBox->setCollapsed( false );
228 break;
229 }
230}
231
232void QgsQueryResultPanelWidget::executeQuery()
233{
234 mQueryResultsTableView->hide();
235 mSqlErrorText->hide();
236 mFirstRowFetched = false;
237
238 cancelRunningQuery();
239 if ( mConnection )
240 {
241 const QString sql { mSqlEditor->selectedText().isEmpty() ? mSqlEditor->text() : mSqlEditor->selectedText() };
242
243 bool ok = false;
244 mCurrentHistoryEntryId = QgsGui::historyProviderRegistry()->addEntry( QStringLiteral( "dbquery" ), QVariantMap {
245 { QStringLiteral( "query" ), sql },
246 { QStringLiteral( "provider" ), mConnection->providerKey() },
247 { QStringLiteral( "connection" ), mConnection->uri() },
248 },
249 ok );
250
251 mWasCanceled = false;
252 mFeedback = std::make_unique<QgsFeedback>();
253 mStopButton->setEnabled( true );
254 mStatusLabel->show();
255 mStatusLabel->setText( tr( "Executing query…" ) );
256 mProgressBar->show();
257 mProgressBar->setRange( 0, 0 );
258 mSqlErrorMessage.clear();
259
260 connect( mStopButton, &QPushButton::pressed, mFeedback.get(), [=] {
261 mStatusLabel->setText( tr( "Stopped" ) );
262 mFeedback->cancel();
263 mProgressBar->hide();
264 mWasCanceled = true;
265 } );
266
267 // Create model when result is ready
268 connect( &mQueryResultWatcher, &QFutureWatcher<QgsAbstractDatabaseProviderConnection::QueryResult>::finished, this, &QgsQueryResultPanelWidget::startFetching, Qt::ConnectionType::UniqueConnection );
269
270 QFuture<QgsAbstractDatabaseProviderConnection::QueryResult> future = QtConcurrent::run( [=]() -> QgsAbstractDatabaseProviderConnection::QueryResult {
271 try
272 {
273 return mConnection->execSql( sql, mFeedback.get() );
274 }
276 {
277 mSqlErrorMessage = ex.what();
279 }
280 } );
281 mQueryResultWatcher.setFuture( future );
282 }
283 else
284 {
285 showError( tr( "Connection error" ), tr( "Cannot execute query: connection to the database is not available." ) );
286 }
287}
288
289void QgsQueryResultPanelWidget::updateButtons()
290{
291 mFilterLineEdit->setEnabled( mFirstRowFetched );
292 mFilterToolButton->setEnabled( mFirstRowFetched );
293 const bool isEmpty = mSqlEditor->text().isEmpty();
294 mExecuteButton->setEnabled( !isEmpty );
295 mLoadAsNewLayerGroupBox->setVisible( mConnection && mConnection->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Capability::SqlLayers ) );
296 mLoadAsNewLayerGroupBox->setEnabled(
297 mSqlErrorMessage.isEmpty() && mFirstRowFetched
298 );
299}
300
301void QgsQueryResultPanelWidget::showCellContextMenu( QPoint point )
302{
303 const QModelIndex modelIndex = mQueryResultsTableView->indexAt( point );
304 if ( modelIndex.isValid() )
305 {
306 QMenu *menu = new QMenu();
307 menu->setAttribute( Qt::WA_DeleteOnClose );
308
309 menu->addAction( QgsApplication::getThemeIcon( "mActionEditCopy.svg" ), tr( "Copy" ), this, [=] { copySelection(); }, QKeySequence::Copy );
310
311 menu->exec( mQueryResultsTableView->viewport()->mapToGlobal( point ) );
312 }
313}
314
315void QgsQueryResultPanelWidget::copySelection()
316{
317 const QModelIndexList selection = mQueryResultsTableView->selectionModel()->selectedIndexes();
318 if ( selection.empty() )
319 return;
320
321 int minRow = -1;
322 int maxRow = -1;
323 int minCol = -1;
324 int maxCol = -1;
325 for ( const QModelIndex &index : selection )
326 {
327 if ( minRow == -1 || index.row() < minRow )
328 minRow = index.row();
329 if ( maxRow == -1 || index.row() > maxRow )
330 maxRow = index.row();
331 if ( minCol == -1 || index.column() < minCol )
332 minCol = index.column();
333 if ( maxCol == -1 || index.column() > maxCol )
334 maxCol = index.column();
335 }
336
337 if ( minRow == maxRow && minCol == maxCol )
338 {
339 // copy only one cell
340 const QString text = mModel->data( selection.at( 0 ), Qt::DisplayRole ).toString();
341 QApplication::clipboard()->setText( text );
342 }
343 else
344 {
345 copyResults( minRow, maxRow, minCol, maxCol );
346 }
347}
348
349void QgsQueryResultPanelWidget::updateSqlLayerColumns()
350{
351 // Precondition
352 Q_ASSERT( mModel );
353
354 mFilterToolButton->setEnabled( true );
355 mFilterLineEdit->setEnabled( true );
356 mPkColumnsComboBox->clear();
357 mGeometryColumnComboBox->clear();
358 const bool hasPkInformation { !mSqlVectorLayerOptions.primaryKeyColumns.isEmpty() };
359 const bool hasGeomColInformation { !mSqlVectorLayerOptions.geometryColumn.isEmpty() };
360 static const QStringList geomColCandidates { QStringLiteral( "geom" ), QStringLiteral( "geometry" ), QStringLiteral( "the_geom" ) };
361 const QStringList constCols { mModel->columns() };
362 for ( const QString &c : constCols )
363 {
364 const bool pkCheckedState = hasPkInformation ? mSqlVectorLayerOptions.primaryKeyColumns.contains( c ) : c.contains( QStringLiteral( "id" ), Qt::CaseSensitivity::CaseInsensitive );
365 // Only check first match
366 mPkColumnsComboBox->addItemWithCheckState( c, pkCheckedState && mPkColumnsComboBox->checkedItems().isEmpty() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked );
367 mGeometryColumnComboBox->addItem( c );
368 if ( !hasGeomColInformation && geomColCandidates.contains( c, Qt::CaseSensitivity::CaseInsensitive ) )
369 {
370 mGeometryColumnComboBox->setCurrentText( c );
371 }
372 }
373 mPkColumnsCheckBox->setChecked( hasPkInformation );
374 mGeometryColumnCheckBox->setChecked( hasGeomColInformation );
375 if ( hasGeomColInformation )
376 {
377 mGeometryColumnComboBox->setCurrentText( mSqlVectorLayerOptions.geometryColumn );
378 }
379}
380
381void QgsQueryResultPanelWidget::cancelRunningQuery()
382{
383 // Cancel other threads
384 if ( mFeedback )
385 {
386 mFeedback->cancel();
387 }
388
389 // ... and wait
390 if ( mQueryResultWatcher.isRunning() )
391 {
392 mQueryResultWatcher.waitForFinished();
393 }
394}
395
396void QgsQueryResultPanelWidget::cancelApiFetcher()
397{
398 if ( mApiFetcher )
399 {
400 mApiFetcher->stopFetching();
401 // apiFetcher and apiFetcherWorkerThread will be deleted when the thread fetchingFinished signal is emitted
402 }
403}
404
405void QgsQueryResultPanelWidget::startFetching()
406{
407 if ( !mWasCanceled )
408 {
409 if ( !mSqlErrorMessage.isEmpty() )
410 {
411 showError( tr( "SQL error" ), mSqlErrorMessage, true );
412 }
413 else
414 {
415 if ( mQueryResultWatcher.result().rowCount() != static_cast<long long>( Qgis::FeatureCountState::UnknownCount ) )
416 {
417 mStatusLabel->setText( QStringLiteral( "Query executed successfully (%1 rows, %2 ms)" )
418 .arg( QLocale().toString( mQueryResultWatcher.result().rowCount() ), QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() ) ) );
419 }
420 else
421 {
422 mStatusLabel->setText( QStringLiteral( "Query executed successfully (%1 s)" ).arg( QLocale().toString( mQueryResultWatcher.result().queryExecutionTime() ) ) );
423 }
424 mProgressBar->hide();
425 mModel = std::make_unique<QgsQueryResultModel>( mQueryResultWatcher.result() );
426 connect( mFeedback.get(), &QgsFeedback::canceled, mModel.get(), [=] {
427 mModel->cancel();
428 mWasCanceled = true;
429 } );
430
431 connect( mModel.get(), &QgsQueryResultModel::fetchMoreRows, this, [=]( long long maxRows ) {
432 mFetchedRowsBatchCount = 0;
433 mProgressBar->setRange( 0, maxRows );
434 mProgressBar->show();
435 } );
436
437 connect( mModel.get(), &QgsQueryResultModel::rowsInserted, this, [=]( const QModelIndex &, int first, int last ) {
438 if ( !mFirstRowFetched )
439 {
440 emit firstResultBatchFetched();
441 mFirstRowFetched = true;
442 mQueryResultsTableView->show();
443 updateButtons();
444 updateSqlLayerColumns();
445 mActualRowCount = mModel->queryResult().rowCount();
446 }
447 mStatusLabel->setText( tr( "Fetched rows: %1/%2 %3 %4 ms" )
448 .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() ) ) );
449 mFetchedRowsBatchCount += last - first + 1;
450 mProgressBar->setValue( mFetchedRowsBatchCount );
451 } );
452
453 mQueryResultsTableView->setModel( mModel.get() );
454 mQueryResultsTableView->show();
455
456 connect( mModel.get(), &QgsQueryResultModel::fetchingComplete, mStopButton, [=] {
457 bool ok = false;
458 const QgsHistoryEntry currentHistoryEntry = QgsGui::historyProviderRegistry()->entry( mCurrentHistoryEntryId, ok );
459 QVariantMap entryDetails = currentHistoryEntry.entry;
460 entryDetails.insert( QStringLiteral( "rows" ), mActualRowCount );
461 entryDetails.insert( QStringLiteral( "time" ), mQueryResultWatcher.result().queryExecutionTime() );
462
463 QgsGui::historyProviderRegistry()->updateEntry( mCurrentHistoryEntryId, entryDetails );
464 mProgressBar->hide();
465 mStopButton->setEnabled( false );
466 } );
467 }
468 }
469 else
470 {
471 mStatusLabel->setText( tr( "SQL command aborted" ) );
472 mProgressBar->hide();
473 }
474}
475
476void QgsQueryResultPanelWidget::showError( const QString &title, const QString &message, bool isSqlError )
477{
478 mStatusLabel->show();
479 mStatusLabel->setText( tr( "An error occurred while executing the query" ) );
480 mProgressBar->hide();
481 mQueryResultsTableView->hide();
482 if ( isSqlError )
483 {
484 mSqlErrorText->show();
485 mSqlErrorText->setText( message );
486 }
487 else
488 {
489 mMessageBar->pushCritical( title, message );
490 }
491}
492
493void QgsQueryResultPanelWidget::tokensReady( const QStringList &tokens )
494{
495 mSqlEditor->setExtraKeywords( mSqlEditor->extraKeywords() + tokens );
496 mSqlErrorText->setExtraKeywords( mSqlErrorText->extraKeywords() + tokens );
497}
498
499void QgsQueryResultPanelWidget::copyResults()
500{
501 const int rowCount = mModel->rowCount( QModelIndex() );
502 const int columnCount = mModel->columnCount( QModelIndex() );
503 copyResults( 0, rowCount - 1, 0, columnCount - 1 );
504}
505
506void QgsQueryResultPanelWidget::copyResults( int fromRow, int toRow, int fromColumn, int toColumn )
507{
508 QStringList rowStrings;
509 QStringList columnStrings;
510
511 const int rowCount = mModel->rowCount( QModelIndex() );
512 const int columnCount = mModel->columnCount( QModelIndex() );
513
514 toRow = std::min( toRow, rowCount - 1 );
515 toColumn = std::min( toColumn, columnCount - 1 );
516
517 rowStrings.reserve( toRow - fromRow );
518
519 // add titles first
520 for ( int col = fromColumn; col <= toColumn; col++ )
521 {
522 columnStrings += mModel->headerData( col, Qt::Horizontal, Qt::DisplayRole ).toString();
523 }
524 rowStrings += columnStrings.join( QLatin1Char( '\t' ) );
525 columnStrings.clear();
526
527 for ( int row = fromRow; row <= toRow; row++ )
528 {
529 for ( int col = fromColumn; col <= toColumn; col++ )
530 {
531 columnStrings += mModel->data( mModel->index( row, col ), Qt::DisplayRole ).toString();
532 }
533 rowStrings += columnStrings.join( QLatin1Char( '\t' ) );
534 columnStrings.clear();
535 }
536
537 if ( !rowStrings.isEmpty() )
538 {
539 const QString text = rowStrings.join( QLatin1Char( '\n' ) );
540 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 );
541 html.replace( QLatin1String( "\t" ), QLatin1String( "</td><td>" ) ).replace( QLatin1String( "\n" ), QLatin1String( "</td></tr><tr><td>" ) );
542
543 QMimeData *mdata = new QMimeData();
544 mdata->setData( QStringLiteral( "text/html" ), html.toUtf8() );
545 if ( !text.isEmpty() )
546 {
547 mdata->setText( text );
548 }
549 // Transfers ownership to the clipboard object
550#ifdef Q_OS_LINUX
551 QApplication::clipboard()->setMimeData( mdata, QClipboard::Selection );
552#endif
553 QApplication::clipboard()->setMimeData( mdata, QClipboard::Clipboard );
554 }
555}
556
557QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions QgsQueryResultPanelWidget::sqlVectorLayerOptions() const
558{
559 const thread_local QRegularExpression rx( QStringLiteral( ";\\s*$" ) );
560 mSqlVectorLayerOptions.sql = mSqlEditor->text().replace( rx, QString() );
561 mSqlVectorLayerOptions.filter = mFilterLineEdit->text();
562 mSqlVectorLayerOptions.primaryKeyColumns = mPkColumnsComboBox->checkedItems();
563 mSqlVectorLayerOptions.geometryColumn = mGeometryColumnComboBox->currentText();
564 mSqlVectorLayerOptions.layerName = mLayerNameLineEdit->text();
565 mSqlVectorLayerOptions.disableSelectAtId = mAvoidSelectingAsFeatureIdCheckBox->isChecked();
566 QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions options { mSqlVectorLayerOptions };
567 // Override if not used
568 if ( !mPkColumnsCheckBox->isChecked() )
569 {
570 options.primaryKeyColumns.clear();
571 }
572 if ( !mGeometryColumnCheckBox->isChecked() )
573 {
574 options.geometryColumn.clear();
575 }
576 return options;
577}
578
579void QgsQueryResultPanelWidget::setConnection( QgsAbstractDatabaseProviderConnection *connection )
580{
581 mConnection.reset( connection );
582
583 cancelApiFetcher();
584
585 if ( connection )
586 {
587 // Add provider specific APIs
588 const QMultiMap<Qgis::SqlKeywordCategory, QStringList> keywordsDict { connection->sqlDictionary() };
589 QStringList keywords;
590 for ( auto it = keywordsDict.constBegin(); it != keywordsDict.constEnd(); it++ )
591 {
592 keywords.append( it.value() );
593 }
594
595 // Add static keywords from provider
596 mSqlEditor->setExtraKeywords( keywords );
597 mSqlErrorText->setExtraKeywords( keywords );
598
599 // Add dynamic keywords in a separate thread
600 QThread *apiFetcherWorkerThread = new QThread();
601 QgsConnectionsApiFetcher *apiFetcher = new QgsConnectionsApiFetcher( mConnection->uri(), mConnection->providerKey() );
602 apiFetcher->moveToThread( apiFetcherWorkerThread );
603 connect( apiFetcherWorkerThread, &QThread::started, apiFetcher, &QgsConnectionsApiFetcher::fetchTokens );
604 connect( apiFetcher, &QgsConnectionsApiFetcher::tokensReady, this, &QgsQueryResultPanelWidget::tokensReady );
605 connect( apiFetcher, &QgsConnectionsApiFetcher::fetchingFinished, apiFetcherWorkerThread, [apiFetcher, apiFetcherWorkerThread] {
606 apiFetcherWorkerThread->quit();
607 apiFetcherWorkerThread->wait();
608 apiFetcherWorkerThread->deleteLater();
609 apiFetcher->deleteLater();
610 } );
611
612 mApiFetcher = apiFetcher;
613 apiFetcherWorkerThread->start();
614 }
615
616 updateButtons();
617}
618
619void QgsQueryResultPanelWidget::setQuery( const QString &sql )
620{
621 mSqlEditor->setText( sql );
622}
623
624void QgsQueryResultPanelWidget::notify( const QString &title, const QString &text, Qgis::MessageLevel level )
625{
626 mMessageBar->pushMessage( title, text, level );
627}
628
629void QgsQueryResultPanelWidget::storeCurrentQuery( Qgis::QueryStorageBackend backend )
630{
631 const QStringList existingQueryNames = QgsGui::storedQueryManager()->allQueryNames( backend );
633 QString(),
634 QString(),
635 QStringList(),
636 existingQueryNames
637 );
638 dlg.setWindowTitle( tr( "Store Query" ) );
639 dlg.setHintString( tr( "Name for the stored query" ) );
640 dlg.setOverwriteEnabled( true );
641 dlg.setConflictingNameWarning( tr( "A stored query with this name already exists, it will be overwritten." ) );
642 dlg.setShowExistingNamesCompleter( true );
643 if ( dlg.exec() != QDialog::Accepted )
644 return;
645
646 const QString name = dlg.name();
647 if ( name.isEmpty() )
648 return;
649
650 QgsGui::storedQueryManager()->storeQuery( name, mSqlEditor->text(), backend );
652 {
654 }
655}
656
657//
658// QgsQueryResultWidget
659//
660
661QgsQueryResultWidget::QgsQueryResultWidget( QWidget *parent, QgsAbstractDatabaseProviderConnection *connection )
662 : QWidget( parent )
663{
664 setupUi( this );
665
666 mToolBar->setIconSize( QgsGuiUtils::iconSize( false ) );
667
668 mQueryWidget = new QgsQueryResultPanelWidget( nullptr, connection );
669 mPanelStack->setMainPanel( mQueryWidget );
670
671 mPresetQueryMenu = new QMenu( this );
672 connect( mPresetQueryMenu, &QMenu::aboutToShow, this, &QgsQueryResultWidget::populatePresetQueryMenu );
673
674 QToolButton *presetQueryButton = new QToolButton();
675 presetQueryButton->setMenu( mPresetQueryMenu );
676 presetQueryButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconStoredQueries.svg" ) ) );
677 presetQueryButton->setPopupMode( QToolButton::InstantPopup );
678 mToolBar->addWidget( presetQueryButton );
679
680 connect( mActionOpenQuery, &QAction::triggered, this, &QgsQueryResultWidget::openQuery );
681 connect( mActionSaveQuery, &QAction::triggered, this, [this] { saveQuery( false ); } );
682 connect( mActionSaveQueryAs, &QAction::triggered, this, [this] { saveQuery( true ); } );
683
684 connect( mActionCut, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::cut );
685 connect( mActionCopy, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::copy );
686 connect( mActionPaste, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::paste );
687 connect( mActionUndo, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::undo );
688 connect( mActionRedo, &QAction::triggered, mQueryWidget->sqlEditor(), &QgsCodeEditor::redo );
689 mActionUndo->setEnabled( false );
690 mActionRedo->setEnabled( false );
691
692 connect( mActionFindReplace, &QAction::toggled, mQueryWidget->codeEditorWidget(), &QgsCodeEditorWidget::setSearchBarVisible );
693 connect( mQueryWidget->codeEditorWidget(), &QgsCodeEditorWidget::searchBarToggled, mActionFindReplace, &QAction::setChecked );
694 connect( mQueryWidget->sqlEditor(), &QgsCodeEditor::modificationChanged, this, &QgsQueryResultWidget::setHasChanged );
695
696 connect( mActionShowHistory, &QAction::toggled, this, &QgsQueryResultWidget::showHistoryPanel );
697
698 connect( mActionClear, &QAction::triggered, this, [=] {
699 mQueryWidget->sqlEditor()->setText( QString() );
700 mActionUndo->setEnabled( false );
701 mActionRedo->setEnabled( false );
702 } );
703
704 connect( mQueryWidget->sqlEditor(), &QgsCodeEditorSQL::textChanged, this, &QgsQueryResultWidget::updateButtons );
705
706 connect( mQueryWidget->sqlEditor(), &QgsCodeEditorSQL::copyAvailable, mActionCut, &QAction::setEnabled );
707 connect( mQueryWidget->sqlEditor(), &QgsCodeEditorSQL::copyAvailable, mActionCopy, &QAction::setEnabled );
708
709 connect( mQueryWidget, &QgsQueryResultPanelWidget::createSqlVectorLayer, this, &QgsQueryResultWidget::createSqlVectorLayer );
710 connect( mQueryWidget, &QgsQueryResultPanelWidget::firstResultBatchFetched, this, &QgsQueryResultWidget::firstResultBatchFetched );
711
712 updateButtons();
713 setHasChanged( false );
714}
715
716QgsQueryResultWidget::~QgsQueryResultWidget()
717{
718 if ( mHistoryWidget )
719 {
720 mPanelStack->closePanel( mHistoryWidget );
721 mHistoryWidget->deleteLater();
722 }
723}
724
725void QgsQueryResultWidget::setSqlVectorLayerOptions( const QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions &options )
726{
727 if ( !options.sql.isEmpty() )
728 {
729 setQuery( options.sql );
730 }
731 mQueryWidget->setSqlVectorLayerOptions( options );
732}
733
734void QgsQueryResultWidget::setWidgetMode( QueryWidgetMode widgetMode )
735{
736 mQueryWidget->setWidgetMode( widgetMode );
737}
738
739void QgsQueryResultWidget::executeQuery()
740{
741 mQueryWidget->executeQuery();
742}
743
744void QgsQueryResultWidget::updateButtons()
745{
746 mQueryWidget->updateButtons();
747
748 const bool isEmpty = mQueryWidget->sqlEditor()->text().isEmpty();
749 mActionClear->setEnabled( !isEmpty );
750 mActionUndo->setEnabled( mQueryWidget->sqlEditor()->isUndoAvailable() );
751 mActionRedo->setEnabled( mQueryWidget->sqlEditor()->isRedoAvailable() );
752}
753
754void QgsQueryResultWidget::showError( const QString &title, const QString &message, bool isSqlError )
755{
756 mQueryWidget->showError( title, message, isSqlError );
757}
758
759void QgsQueryResultWidget::tokensReady( const QStringList & )
760{
761}
762
763void QgsQueryResultWidget::copyResults()
764{
765 mQueryWidget->copyResults();
766}
767
768void QgsQueryResultWidget::copyResults( int fromRow, int toRow, int fromColumn, int toColumn )
769{
770 mQueryWidget->copyResults( fromRow, toRow, fromColumn, toColumn );
771}
772
773void QgsQueryResultWidget::openQuery()
774{
775 if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() && mHasChangedFileContents )
776 {
777 if ( QMessageBox::warning( this, tr( "Unsaved Changes" ), tr( "There are unsaved changes in the query. Continue?" ), QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, QMessageBox::StandardButton::No ) == QMessageBox::StandardButton::No )
778 return;
779 }
780
781 QString initialDir = settingLastSourceFolder->value();
782 if ( initialDir.isEmpty() )
783 initialDir = QDir::homePath();
784
785 const QString fileName = QFileDialog::getOpenFileName( this, tr( "Open Query" ), initialDir, tr( "SQL queries (*.sql *.SQL)" ) + QStringLiteral( ";;" ) + QObject::tr( "All files" ) + QStringLiteral( " (*.*)" ) );
786
787 if ( fileName.isEmpty() )
788 return;
789
790 QFileInfo fi( fileName );
791 settingLastSourceFolder->setValue( fi.path() );
792
793 QgsTemporaryCursorOverride cursor( Qt::CursorShape::WaitCursor );
794
795 mQueryWidget->codeEditorWidget()->loadFile( fileName );
796 setHasChanged( false );
797}
798
799void QgsQueryResultWidget::saveQuery( bool saveAs )
800{
801 if ( mQueryWidget->codeEditorWidget()->filePath().isEmpty() || saveAs )
802 {
803 QString selectedFilter;
804
805 QString initialDir = settingLastSourceFolder->value();
806 if ( initialDir.isEmpty() )
807 initialDir = QDir::homePath();
808
809 QString newPath = QFileDialog::getSaveFileName(
810 this,
811 tr( "Save Query" ),
812 initialDir,
813 tr( "SQL queries (*.sql *.SQL)" ) + QStringLiteral( ";;" ) + QObject::tr( "All files" ) + QStringLiteral( " (*.*)" ),
814 &selectedFilter
815 );
816
817 if ( !newPath.isEmpty() )
818 {
819 QFileInfo fi( newPath );
820 settingLastSourceFolder->setValue( fi.path() );
821
822 if ( !selectedFilter.contains( QStringLiteral( "*.*)" ) ) )
823 newPath = QgsFileUtils::ensureFileNameHasExtension( newPath, { QStringLiteral( "sql" ) } );
824 mQueryWidget->codeEditorWidget()->save( newPath );
825 setHasChanged( false );
826 }
827 }
828 else if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() )
829 {
830 mQueryWidget->codeEditorWidget()->save();
831 setHasChanged( false );
832 }
833}
834
835void QgsQueryResultWidget::setConnection( QgsAbstractDatabaseProviderConnection *connection )
836{
837 mQueryWidget->setConnection( connection );
838 updateButtons();
839}
840
841void QgsQueryResultWidget::setQuery( const QString &sql )
842{
843 mQueryWidget->sqlEditor()->setText( sql );
844 // from the QScintilla docs, calling setText clears undo history!
845 mActionUndo->setEnabled( false );
846 mActionRedo->setEnabled( false );
847}
848
849bool QgsQueryResultWidget::promptUnsavedChanges()
850{
851 if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() && mHasChangedFileContents )
852 {
853 const QMessageBox::StandardButton ret = QMessageBox::question(
854 this,
855 tr( "Save Query?" ),
856 tr(
857 "There are unsaved changes in this query. Do you want to save those?"
858 ),
859 QMessageBox::StandardButton::Save
860 | QMessageBox::StandardButton::Cancel
861 | QMessageBox::StandardButton::Discard,
862 QMessageBox::StandardButton::Cancel
863 );
864
865 if ( ret == QMessageBox::StandardButton::Save )
866 {
867 saveQuery( false );
868 return true;
869 }
870 else if ( ret == QMessageBox::StandardButton::Discard )
871 {
872 return true;
873 }
874 else
875 {
876 return false;
877 }
878 }
879 else
880 {
881 return true;
882 }
883}
884
885
886void QgsQueryResultWidget::notify( const QString &title, const QString &text, Qgis::MessageLevel level )
887{
888 mQueryWidget->notify( title, text, level );
889}
890
891
892void QgsQueryResultWidget::setHasChanged( bool hasChanged )
893{
894 mActionSaveQuery->setEnabled( hasChanged );
895 mHasChangedFileContents = hasChanged;
896 updateDialogTitle();
897}
898
899void QgsQueryResultWidget::updateDialogTitle()
900{
901 QString fileName;
902 if ( !mQueryWidget->codeEditorWidget()->filePath().isEmpty() )
903 {
904 const QFileInfo fi( mQueryWidget->codeEditorWidget()->filePath() );
905 fileName = fi.fileName();
906 if ( mHasChangedFileContents )
907 {
908 fileName.prepend( '*' );
909 }
910 }
911
912 emit requestDialogTitleUpdate( fileName );
913}
914
915void QgsQueryResultWidget::populatePresetQueryMenu()
916{
917 mPresetQueryMenu->clear();
918
919 QMenu *storeQueryMenu = new QMenu( tr( "Store Current Query" ), mPresetQueryMenu );
920 mPresetQueryMenu->addMenu( storeQueryMenu );
921 QAction *storeInProfileAction = new QAction( tr( "In User Profile…" ), storeQueryMenu );
922 storeQueryMenu->addAction( storeInProfileAction );
923 storeInProfileAction->setEnabled( !mQueryWidget->sqlEditor()->text().isEmpty() );
924 connect( storeInProfileAction, &QAction::triggered, this, [this] {
925 storeCurrentQuery( Qgis::QueryStorageBackend::LocalProfile );
926 } );
927 QAction *storeInProjectAction = new QAction( tr( "In Current Project…" ), storeQueryMenu );
928 storeQueryMenu->addAction( storeInProjectAction );
929 storeInProjectAction->setEnabled( !mQueryWidget->sqlEditor()->text().isEmpty() );
930 connect( storeInProjectAction, &QAction::triggered, this, [this] {
932 } );
933
934
935 const QList< QgsStoredQueryManager::QueryDetails > storedQueries = QgsGui::storedQueryManager()->allQueries();
936 if ( !storedQueries.isEmpty() )
937 {
938 QList< QgsStoredQueryManager::QueryDetails > userProfileQueries;
939 std::copy_if( storedQueries.begin(), storedQueries.end(), std::back_inserter( userProfileQueries ), []( const QgsStoredQueryManager::QueryDetails &details ) {
940 return details.backend == Qgis::QueryStorageBackend::LocalProfile;
941 } );
942
943 QList< QgsStoredQueryManager::QueryDetails > projectQueries;
944 std::copy_if( storedQueries.begin(), storedQueries.end(), std::back_inserter( projectQueries ), []( const QgsStoredQueryManager::QueryDetails &details ) {
945 return details.backend == Qgis::QueryStorageBackend::CurrentProject;
946 } );
947
948 mPresetQueryMenu->addSection( QgsApplication::getThemeIcon( QStringLiteral( "mIconStoredQueries.svg" ) ), tr( "User Profile" ) );
949 for ( const QgsStoredQueryManager::QueryDetails &query : std::as_const( userProfileQueries ) )
950 {
951 QAction *action = new QAction( query.name, mPresetQueryMenu );
952 mPresetQueryMenu->addAction( action );
953 connect( action, &QAction::triggered, this, [this, query] {
954 mQueryWidget->sqlEditor()->insertText( query.definition );
955 } );
956 }
957 if ( userProfileQueries.empty() )
958 {
959 QAction *action = new QAction( tr( "No Stored Queries Available" ), mPresetQueryMenu );
960 action->setEnabled( false );
961 mPresetQueryMenu->addAction( action );
962 }
963
964 mPresetQueryMenu->addSection( QgsApplication::getThemeIcon( QStringLiteral( "mIconStoredQueries.svg" ) ), tr( "Current Project" ) );
965 for ( const QgsStoredQueryManager::QueryDetails &query : std::as_const( projectQueries ) )
966 {
967 QAction *action = new QAction( query.name, mPresetQueryMenu );
968 mPresetQueryMenu->addAction( action );
969 connect( action, &QAction::triggered, this, [this, query] {
970 mQueryWidget->sqlEditor()->insertText( query.definition );
971 } );
972 }
973 if ( projectQueries.empty() )
974 {
975 QAction *action = new QAction( tr( "No Stored Queries Available" ), mPresetQueryMenu );
976 action->setEnabled( false );
977 mPresetQueryMenu->addAction( action );
978 }
979
980 mPresetQueryMenu->addSeparator();
981
982 QMenu *removeQueryMenu = new QMenu( tr( "Removed Stored Query" ), mPresetQueryMenu );
983 mPresetQueryMenu->addMenu( removeQueryMenu );
984
985 for ( const QgsStoredQueryManager::QueryDetails &query : storedQueries )
986 {
987 QAction *action = new QAction( tr( "%1…" ).arg( query.name ), mPresetQueryMenu );
988 removeQueryMenu->addAction( action );
989 connect( action, &QAction::triggered, this, [this, query] {
990 const QMessageBox::StandardButton res = QMessageBox::question( this, tr( "Remove Stored Query" ), tr( "Are you sure you want to remove the stored query “%1”?" ).arg( query.name ), QMessageBox::Yes | QMessageBox::No, QMessageBox::No );
991 if ( res == QMessageBox::Yes )
992 {
993 QgsGui::storedQueryManager()->removeQuery( query.name, query.backend );
994 if ( query.backend == Qgis::QueryStorageBackend::CurrentProject )
995 {
997 }
998 }
999 } );
1000 }
1001 }
1002}
1003
1004void QgsQueryResultWidget::storeCurrentQuery( Qgis::QueryStorageBackend backend )
1005{
1006 const QStringList existingQueryNames = QgsGui::storedQueryManager()->allQueryNames( backend );
1007 QgsNewNameDialog dlg(
1008 QString(),
1009 QString(),
1010 QStringList(),
1011 existingQueryNames
1012 );
1013 dlg.setWindowTitle( tr( "Store Query" ) );
1014 dlg.setHintString( tr( "Name for the stored query" ) );
1015 dlg.setOverwriteEnabled( true );
1016 dlg.setConflictingNameWarning( tr( "A stored query with this name already exists, it will be overwritten." ) );
1017 dlg.setShowExistingNamesCompleter( true );
1018 if ( dlg.exec() != QDialog::Accepted )
1019 return;
1020
1021 const QString name = dlg.name();
1022 if ( name.isEmpty() )
1023 return;
1024
1025 QgsGui::storedQueryManager()->storeQuery( name, mQueryWidget->sqlEditor()->text(), backend );
1027 {
1029 }
1030}
1031
1032
1033void QgsQueryResultWidget::showHistoryPanel( bool show )
1034{
1035 // the below code block trips up the clang analyser!
1036 // NOLINTBEGIN(bugprone-branch-clone)
1037 if ( show )
1038 {
1039 mHistoryWidget = new QgsDatabaseQueryHistoryWidget();
1040 mHistoryWidget->setPanelTitle( tr( "SQL History" ) );
1041 mPanelStack->showPanel( mHistoryWidget );
1042 connect( mHistoryWidget, &QgsPanelWidget::panelAccepted, this, [this] { whileBlocking( mActionShowHistory )->setChecked( false ); } );
1043 connect( mHistoryWidget, &QgsDatabaseQueryHistoryWidget::sqlTriggered, this, [this]( const QString &connectionUri, const QString &provider, const QString &sql ) {
1044 Q_UNUSED( connectionUri );
1045 Q_UNUSED( provider );
1046
1047 mQueryWidget->sqlEditor()->setText( sql );
1048 mActionUndo->setEnabled( false );
1049 mActionRedo->setEnabled( false );
1050 mHistoryWidget->acceptPanel();
1051 } );
1052 }
1053 else if ( mHistoryWidget )
1054 {
1055 mPanelStack->closePanel( mHistoryWidget );
1056 mHistoryWidget->deleteLater();
1057 }
1058 // NOLINTEND(bugprone-branch-clone)
1059}
1060
1061
1063
1064void QgsConnectionsApiFetcher::fetchTokens()
1065{
1066 if ( mStopFetching )
1067 {
1068 emit fetchingFinished();
1069 return;
1070 }
1071
1072
1074 if ( !md )
1075 {
1076 emit fetchingFinished();
1077 return;
1078 }
1079 std::unique_ptr<QgsAbstractDatabaseProviderConnection> connection( static_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( mUri, {} ) ) );
1080 if ( !mStopFetching && connection )
1081 {
1082 mFeedback = std::make_unique<QgsFeedback>();
1083 QStringList schemas;
1085 {
1086 try
1087 {
1088 schemas = connection->schemas();
1089 emit tokensReady( schemas );
1090 }
1091 catch ( QgsProviderConnectionException &ex )
1092 {
1093 QgsMessageLog::logMessage( tr( "Error retrieving schemas: %1" ).arg( ex.what() ), QStringLiteral( "QGIS" ), Qgis::MessageLevel::Warning );
1094 }
1095 }
1096 else
1097 {
1098 schemas.push_back( QString() ); // Fake empty schema for DBs not supporting it
1099 }
1100
1101 for ( const auto &schema : std::as_const( schemas ) )
1102 {
1103 if ( mStopFetching )
1104 {
1105 connection.reset();
1106 emit fetchingFinished();
1107 return;
1108 }
1109
1110 QStringList tableNames;
1111 try
1112 {
1113 const QList<QgsAbstractDatabaseProviderConnection::TableProperty> tables = connection->tables( schema, QgsAbstractDatabaseProviderConnection::TableFlags(), mFeedback.get() );
1114 for ( const QgsAbstractDatabaseProviderConnection::TableProperty &table : std::as_const( tables ) )
1115 {
1116 if ( mStopFetching )
1117 {
1118 connection.reset();
1119 emit fetchingFinished();
1120 return;
1121 }
1122 tableNames.push_back( table.tableName() );
1123 }
1124 emit tokensReady( tableNames );
1125 }
1126 catch ( QgsProviderConnectionException &ex )
1127 {
1128 QgsMessageLog::logMessage( tr( "Error retrieving tables: %1" ).arg( ex.what() ), QStringLiteral( "QGIS" ), Qgis::MessageLevel::Warning );
1129 }
1130
1131 // Get fields
1132 for ( const auto &table : std::as_const( tableNames ) )
1133 {
1134 if ( mStopFetching )
1135 {
1136 connection.reset();
1137 emit fetchingFinished();
1138 return;
1139 }
1140
1141 QStringList fieldNames;
1142 try
1143 {
1144 const QgsFields fields( connection->fields( schema, table, mFeedback.get() ) );
1145 if ( mStopFetching )
1146 {
1147 connection.reset();
1148 emit fetchingFinished();
1149 return;
1150 }
1151
1152 for ( const auto &field : std::as_const( fields ) )
1153 {
1154 fieldNames.push_back( field.name() );
1155 if ( mStopFetching )
1156 {
1157 connection.reset();
1158 emit fetchingFinished();
1159 return;
1160 }
1161 }
1162 emit tokensReady( fieldNames );
1163 }
1164 catch ( QgsProviderConnectionException &ex )
1165 {
1166 QgsMessageLog::logMessage( tr( "Error retrieving fields for table %1: %2" ).arg( table, ex.what() ), QStringLiteral( "QGIS" ), Qgis::MessageLevel::Warning );
1167 }
1168 }
1169 }
1170 }
1171
1172 connection.reset();
1173 emit fetchingFinished();
1174}
1175
1176void QgsConnectionsApiFetcher::stopFetching()
1177{
1178 mStopFetching = 1;
1179 if ( mFeedback )
1180 mFeedback->cancel();
1181}
1182
1183QgsQueryResultItemDelegate::QgsQueryResultItemDelegate( QObject *parent )
1184 : QStyledItemDelegate( parent )
1185{
1186}
1187
1188QString QgsQueryResultItemDelegate::displayText( const QVariant &value, const QLocale &locale ) const
1189{
1190 Q_UNUSED( locale )
1191 QString result { QgsExpressionUtils::toLocalizedString( value ) };
1192 // Show no more than 255 characters
1193 if ( result.length() > 255 )
1194 {
1195 result.truncate( 255 );
1196 result.append( QStringLiteral( "…" ) );
1197 }
1198 return result;
1199}
1200
1202
1203//
1204// QgsQueryResultDialog
1205//
1206
1207QgsQueryResultDialog::QgsQueryResultDialog( QgsAbstractDatabaseProviderConnection *connection, QWidget *parent )
1208 : QDialog( parent )
1209{
1210 setObjectName( QStringLiteral( "QgsQueryResultDialog" ) );
1212
1213 mWidget = new QgsQueryResultWidget( this, connection );
1214 QVBoxLayout *l = new QVBoxLayout();
1215 l->setContentsMargins( 0, 0, 0, 0 );
1216 l->addWidget( mWidget );
1217 setLayout( l );
1218}
1219
1220void QgsQueryResultDialog::closeEvent( QCloseEvent *event )
1221{
1222 if ( !mWidget->promptUnsavedChanges() )
1223 {
1224 event->ignore();
1225 }
1226 else
1227 {
1228 event->accept();
1229 }
1230}
1231
1232//
1233// QgsQueryResultMainWindow
1234//
1235
1236QgsQueryResultMainWindow::QgsQueryResultMainWindow( QgsAbstractDatabaseProviderConnection *connection, const QString &identifierName )
1237 : mIdentifierName( identifierName )
1238{
1239 setObjectName( QStringLiteral( "SQLCommandsDialog" ) );
1240
1242
1243 mWidget = new QgsQueryResultWidget( nullptr, connection );
1244 setCentralWidget( mWidget );
1245
1246 connect( mWidget, &QgsQueryResultWidget::requestDialogTitleUpdate, this, &QgsQueryResultMainWindow::updateWindowTitle );
1247
1248 updateWindowTitle( QString() );
1249}
1250
1251void QgsQueryResultMainWindow::closeEvent( QCloseEvent *event )
1252{
1253 if ( !mWidget->promptUnsavedChanges() )
1254 {
1255 event->ignore();
1256 }
1257 else
1258 {
1259 event->accept();
1260 }
1261}
1262
1263void QgsQueryResultMainWindow::updateWindowTitle( const QString &fileName )
1264{
1265 if ( fileName.isEmpty() )
1266 {
1267 if ( !mIdentifierName.isEmpty() )
1268 setWindowTitle( tr( "%1 — Execute SQL" ).arg( mIdentifierName ) );
1269 else
1270 setWindowTitle( tr( "Execute SQL" ) );
1271 }
1272 else
1273 {
1274 if ( !mIdentifierName.isEmpty() )
1275 setWindowTitle( tr( "%1 — %2 — Execute SQL" ).arg( fileName, mIdentifierName ) );
1276 else
1277 setWindowTitle( tr( "%1 — Execute SQL" ).arg( fileName ) );
1278 }
1279}
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
QueryStorageBackend
Stored query storage backends.
Definition qgis.h:3385
@ CurrentProject
Current QGIS project.
@ LocalProfile
Local user profile.
@ 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.
Provides common functionality for database based connections.
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 searchBarToggled(bool visible)
Emitted when the visibility of the search bar is changed.
void setSearchBarVisible(bool visible)
Sets whether the search bar is visible.
void collapsedStateChanged(bool collapsed)
Signal emitted when groupbox collapsed/expanded state is changed, and when first shown.
Custom QgsHistoryWidget for use with the database query provider.
void sqlTriggered(const QString &connectionUri, const QString &provider, const QString &sql)
Emitted when the user has triggered a previously executed SQL statement in the widget.
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 QString ensureFileNameHasExtension(const QString &fileName, const QStringList &extensions)
Ensures that a fileName ends with an extension from the provided list of extensions.
static void enableAutoGeometryRestore(QWidget *widget, const QString &key=QString())
Register the widget to allow its position to be automatically saved and restored when open and closed...
Definition qgsgui.cpp:210
static QgsHistoryProviderRegistry * historyProviderRegistry()
Returns the global history provider registry, used for tracking history providers.
Definition qgsgui.cpp:200
static QgsStoredQueryManager * storedQueryManager()
Returns the global stored SQL query manager.
Definition qgsgui.cpp:229
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, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
A dialog for prompting users for a new name, for example new layer name dialog.
Base class for any widget that can be shown as an inline panel.
void panelAccepted(QgsPanelWidget *panel)
Emitted when the panel is accepted by the user.
static QgsProject * instance()
Returns the QgsProject singleton instance.
void setDirty(bool b=true)
Flag the project as dirty (modified).
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.
A string settings entry.
Stores settings for use within QGIS.
Definition qgssettings.h:65
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
Contains details about a stored query.
QList< QgsStoredQueryManager::QueryDetails > allQueries() const
Returns details of all queries stored in the manager.
QStringList allQueryNames(Qgis::QueryStorageBackend backend=Qgis::QueryStorageBackend::LocalProfile) const
Returns a list of the names of all stored queries for the specified backend.
void removeQuery(const QString &name, Qgis::QueryStorageBackend backend=Qgis::QueryStorageBackend::LocalProfile)
Removes the stored query with matching name.
void storeQuery(const QString &name, const QString &query, Qgis::QueryStorageBackend backend=Qgis::QueryStorageBackend::LocalProfile)
Saves a query to the manager.
Temporarily sets a cursor override for the QApplication for the lifetime of the object.
@ UnknownCount
Provider returned an unknown feature count.
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
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
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:6191
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.