QGIS API Documentation 3.99.0-Master (8e76e220402)
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::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' ) ) ) );
272 mLayer->dataProvider()->clearErrors();
273 }
274 else
275 {
276 QMessageBox::warning( this, tr( "Query Result" ), tr( "An error occurred when executing the query." ) );
277 }
278}
279
281{
282 if ( mTxtSql->text() != mOrigSubsetString )
283 {
284 if ( !mLayer->setSubsetString( mTxtSql->text() ) )
285 {
286 //error in query - show the problem
287 if ( mLayer->dataProvider()->hasErrors() )
288 {
289 QMessageBox::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' ) ) ) );
290 mLayer->dataProvider()->clearErrors();
291 }
292 else
293 {
294 QMessageBox::warning( this, tr( "Query Result" ), tr( "Error in query. The subset string could not be set." ) );
295 }
296
297 return;
298 }
299 }
300
301 QDialog::accept();
302}
303
305{
306 if ( mLayer->subsetString() != mOrigSubsetString )
307 mLayer->setSubsetString( mOrigSubsetString );
308
309 QDialog::reject();
310}
311
312void QgsQueryBuilder::btnEqual_clicked()
313{
314 mTxtSql->insertText( u" = "_s );
315 mTxtSql->setFocus();
316}
317
318void QgsQueryBuilder::btnLessThan_clicked()
319{
320 mTxtSql->insertText( u" < "_s );
321 mTxtSql->setFocus();
322}
323
324void QgsQueryBuilder::btnGreaterThan_clicked()
325{
326 mTxtSql->insertText( u" > "_s );
327 mTxtSql->setFocus();
328}
329
330void QgsQueryBuilder::btnPct_clicked()
331{
332 mTxtSql->insertText( u"%"_s );
333 mTxtSql->setFocus();
334}
335
336void QgsQueryBuilder::btnIn_clicked()
337{
338 mTxtSql->insertText( u" IN "_s );
339 mTxtSql->setFocus();
340}
341
342void QgsQueryBuilder::btnNotIn_clicked()
343{
344 mTxtSql->insertText( u" NOT IN "_s );
345 mTxtSql->setFocus();
346}
347
348void QgsQueryBuilder::btnLike_clicked()
349{
350 mTxtSql->insertText( u" LIKE "_s );
351 mTxtSql->setFocus();
352}
353
354QString QgsQueryBuilder::sql() const
355{
356 return mTxtSql->text();
357}
358
359void QgsQueryBuilder::setSql( const QString &sqlStatement )
360{
361 mTxtSql->setText( sqlStatement );
362}
363
364void QgsQueryBuilder::lstFields_clicked( const QModelIndex &index )
365{
366 if ( mPreviousFieldRow != index.row() )
367 {
368 mPreviousFieldRow = index.row();
369
370 btnSampleValues->setEnabled( true );
371 btnGetAllValues->setEnabled( true );
372
373 mModelValues->clear();
374 mFilterLineEdit->clear();
375 }
376}
377
378void QgsQueryBuilder::lstFields_doubleClicked( const QModelIndex &index )
379{
380 mTxtSql->insertText( '\"' + mModelFields->data( index, static_cast<int>( QgsFieldModel::CustomRole::FieldName ) ).toString() + '\"' );
381 mTxtSql->setFocus();
382}
383
384void QgsQueryBuilder::lstValues_doubleClicked( const QModelIndex &index )
385{
386 const QVariant value = index.data( Qt::UserRole + 1 );
387 if ( QgsVariantUtils::isNull( value ) )
388 mTxtSql->insertText( u"NULL"_s );
389 else if ( value.userType() == QMetaType::Type::QDate && mLayer->providerType() == "ogr"_L1 && mLayer->storageType() == "ESRI Shapefile"_L1 )
390 mTxtSql->insertText( '\'' + value.toDate().toString( u"yyyy/MM/dd"_s ) + '\'' );
391 else if ( value.userType() == QMetaType::Type::Int || value.userType() == QMetaType::Type::Double || value.userType() == QMetaType::Type::LongLong || value.userType() == QMetaType::Type::Bool )
392 mTxtSql->insertText( value.toString() );
393 else
394 mTxtSql->insertText( '\'' + value.toString().replace( '\'', "''"_L1 ) + '\'' );
395
396 mTxtSql->setFocus();
397}
398
399void QgsQueryBuilder::btnLessEqual_clicked()
400{
401 mTxtSql->insertText( u" <= "_s );
402 mTxtSql->setFocus();
403}
404
405void QgsQueryBuilder::btnGreaterEqual_clicked()
406{
407 mTxtSql->insertText( u" >= "_s );
408 mTxtSql->setFocus();
409}
410
411void QgsQueryBuilder::btnNotEqual_clicked()
412{
413 mTxtSql->insertText( u" != "_s );
414 mTxtSql->setFocus();
415}
416
417void QgsQueryBuilder::btnAnd_clicked()
418{
419 mTxtSql->insertText( u" AND "_s );
420 mTxtSql->setFocus();
421}
422
423void QgsQueryBuilder::btnNot_clicked()
424{
425 mTxtSql->insertText( u" NOT "_s );
426 mTxtSql->setFocus();
427}
428
429void QgsQueryBuilder::btnOr_clicked()
430{
431 mTxtSql->insertText( u" OR "_s );
432 mTxtSql->setFocus();
433}
434
435void QgsQueryBuilder::onTextChanged( const QString &text )
436{
437 mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
438 mProxyValues->setFilterWildcard( text );
439}
440
442{
443 mTxtSql->clear();
444 mLayer->setSubsetString( QString() );
445}
446
447void QgsQueryBuilder::btnILike_clicked()
448{
449 mTxtSql->insertText( u" ILIKE "_s );
450 mTxtSql->setFocus();
451}
452
454{
455 lblDataUri->setText( uri );
456}
457
458void QgsQueryBuilder::showHelp()
459{
460 QgsHelp::openHelp( u"working_with_vector/vector_properties.html#query-builder"_s );
461}
462
464{
465 const bool ok = saveQueryToFile( mTxtSql->text() );
466 Q_UNUSED( ok )
467}
468
469bool QgsQueryBuilder::saveQueryToFile( const QString &subset )
470{
471 QgsSettings s;
472 const QString lastQueryFileDir = s.value( u"/UI/lastQueryFileDir"_s, QDir::homePath() ).toString();
473 //save as qqf (QGIS query file)
474 QString saveFileName = QFileDialog::getSaveFileName( nullptr, tr( "Save Query to File" ), lastQueryFileDir, tr( "Query files (*.qqf *.QQF)" ) );
475 if ( saveFileName.isNull() )
476 {
477 return false;
478 }
479
480 if ( !saveFileName.endsWith( ".qqf"_L1, Qt::CaseInsensitive ) )
481 {
482 saveFileName += ".qqf"_L1;
483 }
484
485 QFile saveFile( saveFileName );
486 if ( !saveFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
487 {
488 QMessageBox::critical( nullptr, tr( "Save Query to File" ), tr( "Could not open file for writing." ) );
489 return false;
490 }
491
492 QDomDocument xmlDoc;
493 QDomElement queryElem = xmlDoc.createElement( u"Query"_s );
494 const QDomText queryTextNode = xmlDoc.createTextNode( subset );
495 queryElem.appendChild( queryTextNode );
496 xmlDoc.appendChild( queryElem );
497
498 QTextStream fileStream( &saveFile );
499 xmlDoc.save( fileStream, 2 );
500
501 const QFileInfo fi( saveFile );
502 s.setValue( u"/UI/lastQueryFileDir"_s, fi.absolutePath() );
503 return true;
504}
505
507{
508 QString subset;
509 if ( loadQueryFromFile( subset ) )
510 {
511 mTxtSql->clear();
512 mTxtSql->insertText( subset );
513 }
514}
515
517{
518 const QgsSettings s;
519 const QString lastQueryFileDir = s.value( u"/UI/lastQueryFileDir"_s, QDir::homePath() ).toString();
520
521 const QString queryFileName = QFileDialog::getOpenFileName( nullptr, tr( "Load Query from File" ), lastQueryFileDir, tr( "Query files" ) + " (*.qqf);;" + tr( "All files" ) + " (*)" );
522 if ( queryFileName.isNull() )
523 {
524 return false;
525 }
526
527 QFile queryFile( queryFileName );
528 if ( !queryFile.open( QIODevice::ReadOnly ) )
529 {
530 QMessageBox::critical( nullptr, tr( "Load Query from File" ), tr( "Could not open file for reading." ) );
531 return false;
532 }
533 QDomDocument queryDoc;
534 if ( !queryDoc.setContent( &queryFile ) )
535 {
536 QMessageBox::critical( nullptr, tr( "Load Query from File" ), tr( "File is not a valid xml document." ) );
537 return false;
538 }
539
540 const QDomElement queryElem = queryDoc.firstChildElement( u"Query"_s );
541 if ( queryElem.isNull() )
542 {
543 QMessageBox::critical( nullptr, tr( "Load Query from File" ), tr( "File is not a valid query document." ) );
544 return false;
545 }
546
547 subset = queryElem.text();
548 return true;
549}
550
551void QgsQueryBuilder::layerSubsetStringChanged()
552{
553 if ( mIgnoreLayerSubsetStringChangedSignal )
554 return;
555 mUseUnfilteredLayer->setDisabled( mLayer->subsetString().isEmpty() || mLayer->isSqlQuery() );
556}
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