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