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