QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
Loading...
Searching...
No Matches
qgsquerybuilder.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsquerybuilder.cpp - Query Builder
3 --------------------------------------
4 Date : 2004-11-19
5 Copyright : (C) 2004 by Gary E.Sherman
6 Email : sherman at mrcc.com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15#include "qgsquerybuilder.h"
16
17#include "qgsapplication.h"
18#include "qgsfieldmodel.h"
19#include "qgsfieldproxymodel.h"
20#include "qgsgui.h"
21#include "qgshelp.h"
22#include "qgslogger.h"
23#include "qgssettings.h"
25#include "qgsvectorlayer.h"
26
27#include <QDomDocument>
28#include <QDomElement>
29#include <QFileDialog>
30#include <QInputDialog>
31#include <QListView>
32#include <QMessageBox>
33#include <QPushButton>
34#include <QString>
35#include <QTextStream>
36
37#include "moc_qgsquerybuilder.cpp"
38
39using namespace Qt::StringLiterals;
40
41// constructor used when the query builder must make its own
42// connection to the database
43QgsQueryBuilder::QgsQueryBuilder( QgsVectorLayer *layer, QWidget *parent, Qt::WindowFlags fl )
44 : QgsSubsetStringEditorInterface( parent, fl )
45 , mLayer( layer )
46{
47 setupUi( this );
49 connect( btnEqual, &QPushButton::clicked, this, &QgsQueryBuilder::btnEqual_clicked );
50 connect( btnLessThan, &QPushButton::clicked, this, &QgsQueryBuilder::btnLessThan_clicked );
51 connect( btnGreaterThan, &QPushButton::clicked, this, &QgsQueryBuilder::btnGreaterThan_clicked );
52 connect( btnPct, &QPushButton::clicked, this, &QgsQueryBuilder::btnPct_clicked );
53 connect( btnIn, &QPushButton::clicked, this, &QgsQueryBuilder::btnIn_clicked );
54 connect( btnNotIn, &QPushButton::clicked, this, &QgsQueryBuilder::btnNotIn_clicked );
55 connect( btnLike, &QPushButton::clicked, this, &QgsQueryBuilder::btnLike_clicked );
56 connect( btnILike, &QPushButton::clicked, this, &QgsQueryBuilder::btnILike_clicked );
57 connect( lstFields, &QListView::clicked, this, &QgsQueryBuilder::lstFields_clicked );
58 connect( lstFields, &QListView::doubleClicked, this, &QgsQueryBuilder::lstFields_doubleClicked );
59 connect( lstValues, &QListView::doubleClicked, this, &QgsQueryBuilder::lstValues_doubleClicked );
60 connect( btnLessEqual, &QPushButton::clicked, this, &QgsQueryBuilder::btnLessEqual_clicked );
61 connect( btnGreaterEqual, &QPushButton::clicked, this, &QgsQueryBuilder::btnGreaterEqual_clicked );
62 connect( btnNotEqual, &QPushButton::clicked, this, &QgsQueryBuilder::btnNotEqual_clicked );
63 connect( btnAnd, &QPushButton::clicked, this, &QgsQueryBuilder::btnAnd_clicked );
64 connect( btnNot, &QPushButton::clicked, this, &QgsQueryBuilder::btnNot_clicked );
65 connect( btnOr, &QPushButton::clicked, this, &QgsQueryBuilder::btnOr_clicked );
66 connect( btnGetAllValues, &QPushButton::clicked, this, &QgsQueryBuilder::btnGetAllValues_clicked );
67 connect( btnSampleValues, &QPushButton::clicked, this, &QgsQueryBuilder::btnSampleValues_clicked );
68 connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsQueryBuilder::showHelp );
69
70 QPushButton *pbn = new QPushButton( tr( "&Test" ) );
71 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
72 connect( pbn, &QAbstractButton::clicked, this, &QgsQueryBuilder::test );
73
74 pbn = new QPushButton( tr( "&Clear" ) );
75 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
76 connect( pbn, &QAbstractButton::clicked, this, &QgsQueryBuilder::clear );
77
78 pbn = new QPushButton( tr( "&Save…" ) );
79 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
80 pbn->setToolTip( tr( "Save query to QQF file" ) );
81 connect( pbn, &QAbstractButton::clicked, this, &QgsQueryBuilder::saveQuery );
82
83 pbn = new QPushButton( tr( "&Load…" ) );
84 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
85 pbn->setToolTip( tr( "Load query from QQF file" ) );
86 connect( pbn, &QAbstractButton::clicked, this, &QgsQueryBuilder::loadQuery );
87
88 setupGuiViews();
89
90 mModelFields = new QgsFieldProxyModel();
92 mModelFields->sourceFieldModel()->setLayer( layer );
93 lstFields->setModel( mModelFields );
94
95 mOrigSubsetString = layer->subsetString();
96 connect( layer, &QgsVectorLayer::subsetStringChanged, this, &QgsQueryBuilder::layerSubsetStringChanged );
97 layerSubsetStringChanged();
98
99 QString subsetStringDialect;
100 QString subsetStringHelpUrl;
101
102 if ( QgsDataProvider *provider = layer->dataProvider() )
103 {
104 lblDataUri->setText( tr( "Set provider filter on %1 (provider: %2)" ).arg( layer->name(), provider->name() ) );
105 subsetStringDialect = provider->subsetStringDialect();
106 subsetStringHelpUrl = provider->subsetStringHelpUrl();
107 }
108 else
109 {
110 lblDataUri->setText( tr( "Set provider filter on %1 (provider: %2)" ).arg( layer->name(), layer->providerType() ) );
111 }
112
113 if ( !subsetStringDialect.isEmpty() && !subsetStringHelpUrl.isEmpty() )
114 {
115 lblProviderFilterInfo->setOpenExternalLinks( true );
116 lblProviderFilterInfo->setText( tr( "Enter a <a href=\"%1\">%2</a> to filter the layer" ).arg( subsetStringHelpUrl ).arg( subsetStringDialect ) );
117 }
118 else if ( !subsetStringDialect.isEmpty() )
119 {
120 lblProviderFilterInfo->setText( tr( "Enter a %1 to filter the layer" ).arg( subsetStringDialect ) );
121 }
122 else
123 {
124 lblProviderFilterInfo->hide();
125 }
126
127 mTxtSql->setText( mOrigSubsetString );
128
129 mFilterLineEdit->setShowSearchIcon( true );
130 mFilterLineEdit->setPlaceholderText( tr( "Search…" ) );
131 connect( mFilterLineEdit, &QgsFilterLineEdit::textChanged, this, &QgsQueryBuilder::onTextChanged );
132}
133
134void QgsQueryBuilder::showEvent( QShowEvent *event )
135{
136 mTxtSql->setFocus();
137 QDialog::showEvent( event );
138}
139
140void QgsQueryBuilder::setupGuiViews()
141{
142 //Initialize the models
143 mModelValues = new QStandardItemModel();
144 mProxyValues = new QSortFilterProxyModel();
145 mProxyValues->setSourceModel( mModelValues );
146 // Modes
147 lstFields->setViewMode( QListView::ListMode );
148 lstValues->setViewMode( QListView::ListMode );
149 lstFields->setSelectionBehavior( QAbstractItemView::SelectRows );
150 lstValues->setSelectionBehavior( QAbstractItemView::SelectRows );
151 // Performance tip since Qt 4.1
152 lstFields->setUniformItemSizes( true );
153 lstValues->setUniformItemSizes( true );
154 // Colored rows
155 lstFields->setAlternatingRowColors( true );
156 lstValues->setAlternatingRowColors( true );
157 lstValues->setModel( mProxyValues );
158}
159
160void QgsQueryBuilder::fillValues( const QString &field, int limit )
161{
162 // clear the model
163 mModelValues->clear();
164
165 const int fieldIndex = mLayer->fields().lookupField( field );
166
167 // determine the field type
168 QList<QVariant> values = qgis::setToList( mLayer->uniqueValues( fieldIndex, limit ) );
169 std::sort( values.begin(), values.end() );
170
171 const QString nullValue = QgsApplication::nullRepresentation();
172
173 QgsDebugMsgLevel( u"nullValue: %1"_s.arg( nullValue ), 2 );
174
175 const auto constValues = values;
176 for ( const QVariant &var : constValues )
177 {
178 QString value;
179 if ( QgsVariantUtils::isNull( var ) )
180 value = nullValue;
181 else if ( var.userType() == QMetaType::Type::QDate && mLayer->providerType() == "ogr"_L1 && mLayer->storageType() == "ESRI Shapefile"_L1 )
182 value = var.toDate().toString( u"yyyy/MM/dd"_s );
183 else if ( var.userType() == QMetaType::Type::QVariantList || var.userType() == QMetaType::Type::QStringList )
184 {
185 const QVariantList list = var.toList();
186 for ( const QVariant &val : list )
187 {
188 if ( !value.isEmpty() )
189 value.append( ", " );
190 value.append( QgsVariantUtils::isNull( val ) ? nullValue : val.toString() );
191 }
192 }
193 else
194 value = var.toString();
195
196 QStandardItem *myItem = new QStandardItem( value );
197 myItem->setEditable( false );
198 myItem->setData( var, Qt::UserRole + 1 );
199 mModelValues->insertRow( mModelValues->rowCount(), myItem );
200 QgsDebugMsgLevel( u"Value is null: %1\nvalue: %2"_s.arg( QgsVariantUtils::isNull( var ) ).arg( QgsVariantUtils::isNull( var ) ? nullValue : var.toString() ), 2 );
201 }
202}
203
204void QgsQueryBuilder::btnSampleValues_clicked()
205{
206 lstValues->setCursor( Qt::WaitCursor );
207
208 const QString prevSubsetString = mLayer->subsetString();
209 if ( mUseUnfilteredLayer->isChecked() && !prevSubsetString.isEmpty() )
210 {
211 mIgnoreLayerSubsetStringChangedSignal = true;
212 mLayer->setSubsetString( QString() );
213 }
214
215 //Clear and fill the mModelValues
216 fillValues( mModelFields->data( lstFields->currentIndex(), static_cast<int>( QgsFieldModel::CustomRole::FieldName ) ).toString(), 25 );
217
218 if ( prevSubsetString != mLayer->subsetString() )
219 {
220 mLayer->setSubsetString( prevSubsetString );
221 mIgnoreLayerSubsetStringChangedSignal = false;
222 }
223
224 lstValues->setCursor( Qt::ArrowCursor );
225}
226
227void QgsQueryBuilder::btnGetAllValues_clicked()
228{
229 lstValues->setCursor( Qt::WaitCursor );
230
231 const QString prevSubsetString = mLayer->subsetString();
232 if ( mUseUnfilteredLayer->isChecked() && !prevSubsetString.isEmpty() )
233 {
234 mIgnoreLayerSubsetStringChangedSignal = true;
235 mLayer->setSubsetString( QString() );
236 }
237
238 //Clear and fill the mModelValues
239 fillValues( mModelFields->data( lstFields->currentIndex(), static_cast<int>( QgsFieldModel::CustomRole::FieldName ) ).toString(), -1 );
240
241 if ( prevSubsetString != mLayer->subsetString() )
242 {
243 mLayer->setSubsetString( prevSubsetString );
244 mIgnoreLayerSubsetStringChangedSignal = false;
245 }
246
247 lstValues->setCursor( Qt::ArrowCursor );
248}
249
251{
252 // test the sql statement to see if it works
253 // by counting the number of records that would be
254 // returned
255
256 if ( mLayer->setSubsetString( mTxtSql->text() ) )
257 {
258 const long long featureCount { mLayer->featureCount() };
259 // Check for errors
260 if ( featureCount < 0 )
261 {
262 QMessageBox::warning( this, tr( "Query Result" ), tr( "An error occurred when executing the query, please check the expression syntax." ) );
263 }
264 else
265 {
266 QMessageBox::information( this, tr( "Query Result" ), tr( "The where clause returned %n row(s).", "returned test rows", featureCount ) );
267 }
268 }
269 else if ( mLayer->dataProvider()->hasErrors() )
270 {
271 QMessageBox::
272 warning( this, tr( "Query Result" ), tr( "An error occurred when executing the query." ) + tr( "\nThe data provider said:\n%1" ).arg( mLayer->dataProvider()->errors().join( QLatin1Char( '\n' ) ) ) );
273 mLayer->dataProvider()->clearErrors();
274 }
275 else
276 {
277 QMessageBox::warning( this, tr( "Query Result" ), tr( "An error occurred when executing the query." ) );
278 }
279}
280
282{
283 if ( mTxtSql->text() != mOrigSubsetString )
284 {
285 if ( !mLayer->setSubsetString( mTxtSql->text() ) )
286 {
287 //error in query - show the problem
288 if ( mLayer->dataProvider()->hasErrors() )
289 {
290 QMessageBox::
291 warning( this, tr( "Query Result" ), tr( "An error occurred when executing the query." ) + tr( "\nThe data provider said:\n%1" ).arg( mLayer->dataProvider()->errors().join( QLatin1Char( '\n' ) ) ) );
292 mLayer->dataProvider()->clearErrors();
293 }
294 else
295 {
296 QMessageBox::warning( this, tr( "Query Result" ), tr( "Error in query. The subset string could not be set." ) );
297 }
298
299 return;
300 }
301 }
302
303 QDialog::accept();
304}
305
307{
308 if ( mLayer->subsetString() != mOrigSubsetString )
309 mLayer->setSubsetString( mOrigSubsetString );
310
311 QDialog::reject();
312}
313
314void QgsQueryBuilder::btnEqual_clicked()
315{
316 mTxtSql->insertText( u" = "_s );
317 mTxtSql->setFocus();
318}
319
320void QgsQueryBuilder::btnLessThan_clicked()
321{
322 mTxtSql->insertText( u" < "_s );
323 mTxtSql->setFocus();
324}
325
326void QgsQueryBuilder::btnGreaterThan_clicked()
327{
328 mTxtSql->insertText( u" > "_s );
329 mTxtSql->setFocus();
330}
331
332void QgsQueryBuilder::btnPct_clicked()
333{
334 mTxtSql->insertText( u"%"_s );
335 mTxtSql->setFocus();
336}
337
338void QgsQueryBuilder::btnIn_clicked()
339{
340 mTxtSql->insertText( u" IN "_s );
341 mTxtSql->setFocus();
342}
343
344void QgsQueryBuilder::btnNotIn_clicked()
345{
346 mTxtSql->insertText( u" NOT IN "_s );
347 mTxtSql->setFocus();
348}
349
350void QgsQueryBuilder::btnLike_clicked()
351{
352 mTxtSql->insertText( u" LIKE "_s );
353 mTxtSql->setFocus();
354}
355
356QString QgsQueryBuilder::sql() const
357{
358 return mTxtSql->text();
359}
360
361void QgsQueryBuilder::setSql( const QString &sqlStatement )
362{
363 mTxtSql->setText( sqlStatement );
364}
365
366void QgsQueryBuilder::lstFields_clicked( const QModelIndex &index )
367{
368 if ( mPreviousFieldRow != index.row() )
369 {
370 mPreviousFieldRow = index.row();
371
372 btnSampleValues->setEnabled( true );
373 btnGetAllValues->setEnabled( true );
374
375 mModelValues->clear();
376 mFilterLineEdit->clear();
377 }
378}
379
380void QgsQueryBuilder::lstFields_doubleClicked( const QModelIndex &index )
381{
382 mTxtSql->insertText( '\"' + mModelFields->data( index, static_cast<int>( QgsFieldModel::CustomRole::FieldName ) ).toString() + '\"' );
383 mTxtSql->setFocus();
384}
385
386void QgsQueryBuilder::lstValues_doubleClicked( const QModelIndex &index )
387{
388 const QVariant value = index.data( Qt::UserRole + 1 );
389 if ( QgsVariantUtils::isNull( value ) )
390 mTxtSql->insertText( u"NULL"_s );
391 else if ( value.userType() == QMetaType::Type::QDate && mLayer->providerType() == "ogr"_L1 && mLayer->storageType() == "ESRI Shapefile"_L1 )
392 mTxtSql->insertText( '\'' + value.toDate().toString( u"yyyy/MM/dd"_s ) + '\'' );
393 else if ( value.userType() == QMetaType::Type::Int || value.userType() == QMetaType::Type::Double || value.userType() == QMetaType::Type::LongLong || value.userType() == QMetaType::Type::Bool )
394 mTxtSql->insertText( value.toString() );
395 else
396 mTxtSql->insertText( '\'' + value.toString().replace( '\'', "''"_L1 ) + '\'' );
397
398 mTxtSql->setFocus();
399}
400
401void QgsQueryBuilder::btnLessEqual_clicked()
402{
403 mTxtSql->insertText( u" <= "_s );
404 mTxtSql->setFocus();
405}
406
407void QgsQueryBuilder::btnGreaterEqual_clicked()
408{
409 mTxtSql->insertText( u" >= "_s );
410 mTxtSql->setFocus();
411}
412
413void QgsQueryBuilder::btnNotEqual_clicked()
414{
415 mTxtSql->insertText( u" != "_s );
416 mTxtSql->setFocus();
417}
418
419void QgsQueryBuilder::btnAnd_clicked()
420{
421 mTxtSql->insertText( u" AND "_s );
422 mTxtSql->setFocus();
423}
424
425void QgsQueryBuilder::btnNot_clicked()
426{
427 mTxtSql->insertText( u" NOT "_s );
428 mTxtSql->setFocus();
429}
430
431void QgsQueryBuilder::btnOr_clicked()
432{
433 mTxtSql->insertText( u" OR "_s );
434 mTxtSql->setFocus();
435}
436
437void QgsQueryBuilder::onTextChanged( const QString &text )
438{
439 mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
440 mProxyValues->setFilterWildcard( text );
441}
442
444{
445 mTxtSql->clear();
446 mLayer->setSubsetString( QString() );
447}
448
449void QgsQueryBuilder::btnILike_clicked()
450{
451 mTxtSql->insertText( u" ILIKE "_s );
452 mTxtSql->setFocus();
453}
454
456{
457 lblDataUri->setText( uri );
458}
459
460void QgsQueryBuilder::showHelp()
461{
462 QgsHelp::openHelp( u"working_with_vector/vector_properties.html#query-builder"_s );
463}
464
466{
467 const bool ok = saveQueryToFile( mTxtSql->text() );
468 Q_UNUSED( ok )
469}
470
471bool QgsQueryBuilder::saveQueryToFile( const QString &subset )
472{
473 QgsSettings s;
474 const QString lastQueryFileDir = s.value( u"/UI/lastQueryFileDir"_s, QDir::homePath() ).toString();
475 //save as qqf (QGIS query file)
476 QString saveFileName = QFileDialog::getSaveFileName( nullptr, tr( "Save Query to File" ), lastQueryFileDir, tr( "Query files (*.qqf *.QQF)" ) );
477 if ( saveFileName.isNull() )
478 {
479 return false;
480 }
481
482 if ( !saveFileName.endsWith( ".qqf"_L1, Qt::CaseInsensitive ) )
483 {
484 saveFileName += ".qqf"_L1;
485 }
486
487 QFile saveFile( saveFileName );
488 if ( !saveFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
489 {
490 QMessageBox::critical( nullptr, tr( "Save Query to File" ), tr( "Could not open file for writing." ) );
491 return false;
492 }
493
494 QDomDocument xmlDoc;
495 QDomElement queryElem = xmlDoc.createElement( u"Query"_s );
496 const QDomText queryTextNode = xmlDoc.createTextNode( subset );
497 queryElem.appendChild( queryTextNode );
498 xmlDoc.appendChild( queryElem );
499
500 QTextStream fileStream( &saveFile );
501 xmlDoc.save( fileStream, 2 );
502
503 const QFileInfo fi( saveFile );
504 s.setValue( u"/UI/lastQueryFileDir"_s, fi.absolutePath() );
505 return true;
506}
507
509{
510 QString subset;
511 if ( loadQueryFromFile( subset ) )
512 {
513 mTxtSql->clear();
514 mTxtSql->insertText( subset );
515 }
516}
517
519{
520 const QgsSettings s;
521 const QString lastQueryFileDir = s.value( u"/UI/lastQueryFileDir"_s, QDir::homePath() ).toString();
522
523 const QString queryFileName = QFileDialog::getOpenFileName( nullptr, tr( "Load Query from File" ), lastQueryFileDir, tr( "Query files" ) + " (*.qqf);;" + tr( "All files" ) + " (*)" );
524 if ( queryFileName.isNull() )
525 {
526 return false;
527 }
528
529 QFile queryFile( queryFileName );
530 if ( !queryFile.open( QIODevice::ReadOnly ) )
531 {
532 QMessageBox::critical( nullptr, tr( "Load Query from File" ), tr( "Could not open file for reading." ) );
533 return false;
534 }
535 QDomDocument queryDoc;
536 if ( !queryDoc.setContent( &queryFile ) )
537 {
538 QMessageBox::critical( nullptr, tr( "Load Query from File" ), tr( "File is not a valid xml document." ) );
539 return false;
540 }
541
542 const QDomElement queryElem = queryDoc.firstChildElement( u"Query"_s );
543 if ( queryElem.isNull() )
544 {
545 QMessageBox::critical( nullptr, tr( "Load Query from File" ), tr( "File is not a valid query document." ) );
546 return false;
547 }
548
549 subset = queryElem.text();
550 return true;
551}
552
553void QgsQueryBuilder::layerSubsetStringChanged()
554{
555 if ( mIgnoreLayerSubsetStringChangedSignal )
556 return;
557 mUseUnfilteredLayer->setDisabled( mLayer->subsetString().isEmpty() || mLayer->isSqlQuery() );
558}
static QString nullRepresentation()
Returns the string used to represent the value NULL throughout QGIS.
Abstract base class for spatial data provider implementations.
@ FieldName
Return field name if index corresponds to a field.
A proxy model to filter the list of fields of a layer.
@ AllTypes
All field types.
@ OriginProvider
Fields with a provider origin, since QGIS 3.38.
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:224
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition qgshelp.cpp:41
QString name
Definition qgsmaplayer.h:87
QString providerType() const
Returns the provider type (provider key) for this layer.
static bool loadQueryFromFile(QString &subset)
Load query from the XML file.
void loadQuery()
Load query from the XML file.
void saveQuery()
Save query to the XML file.
void accept() override
void reject() override
void setDatasourceDescription(const QString &uri)
void setSql(const QString &sqlStatement)
Set the sql statement to display in the dialog.
virtual void test()
The default implementation tests that the constructed sql statement to see if the vector layer data p...
static bool saveQueryToFile(const QString &subset)
Save query to the XML file.
void showEvent(QShowEvent *event) override
QgsQueryBuilder(QgsVectorLayer *layer, QWidget *parent=nullptr, Qt::WindowFlags fl=QgsGuiUtils::ModalDialogFlags)
This constructor is used when the query builder is called from the vector layer properties dialog.
QString sql() const
Returns the sql statement entered in the dialog.
Stores settings for use within QGIS.
Definition qgssettings.h:68
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.
QgsSubsetStringEditorInterface(QWidget *parent=nullptr, Qt::WindowFlags fl=QgsGuiUtils::ModalDialogFlags)
Constructor.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Represents a vector layer which manages a vector based dataset.
void subsetStringChanged()
Emitted when the layer's subset string has changed.
bool isSqlQuery() const
Returns true if the layer is a query (SQL) layer.
QgsVectorDataProvider * dataProvider() final
Returns the layer's data provider, it may be nullptr.
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63