QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 #include "qgslogger.h"
17 #include "qgsproject.h"
18 #include "qgssettings.h"
19 #include "qgsvectorlayer.h"
20 #include "qgsvectordataprovider.h"
21 #include "qgsapplication.h"
22 #include "qgshelp.h"
23 #include "qgsgui.h"
24 
25 #include <QDomDocument>
26 #include <QDomElement>
27 #include <QFileDialog>
28 #include <QInputDialog>
29 #include <QListView>
30 #include <QMessageBox>
31 #include <QRegExp>
32 #include <QPushButton>
33 #include <QTextStream>
34 
35 
36 // constructor used when the query builder must make its own
37 // connection to the database
39  QWidget *parent, Qt::WindowFlags fl )
40  : QgsSubsetStringEditorInterface( parent, fl )
41  , mPreviousFieldRow( -1 )
42  , mLayer( layer )
43 {
44  setupUi( this );
46  connect( btnEqual, &QPushButton::clicked, this, &QgsQueryBuilder::btnEqual_clicked );
47  connect( btnLessThan, &QPushButton::clicked, this, &QgsQueryBuilder::btnLessThan_clicked );
48  connect( btnGreaterThan, &QPushButton::clicked, this, &QgsQueryBuilder::btnGreaterThan_clicked );
49  connect( btnPct, &QPushButton::clicked, this, &QgsQueryBuilder::btnPct_clicked );
50  connect( btnIn, &QPushButton::clicked, this, &QgsQueryBuilder::btnIn_clicked );
51  connect( btnNotIn, &QPushButton::clicked, this, &QgsQueryBuilder::btnNotIn_clicked );
52  connect( btnLike, &QPushButton::clicked, this, &QgsQueryBuilder::btnLike_clicked );
53  connect( btnILike, &QPushButton::clicked, this, &QgsQueryBuilder::btnILike_clicked );
54  connect( lstFields, &QListView::clicked, this, &QgsQueryBuilder::lstFields_clicked );
55  connect( lstFields, &QListView::doubleClicked, this, &QgsQueryBuilder::lstFields_doubleClicked );
56  connect( lstValues, &QListView::doubleClicked, this, &QgsQueryBuilder::lstValues_doubleClicked );
57  connect( btnLessEqual, &QPushButton::clicked, this, &QgsQueryBuilder::btnLessEqual_clicked );
58  connect( btnGreaterEqual, &QPushButton::clicked, this, &QgsQueryBuilder::btnGreaterEqual_clicked );
59  connect( btnNotEqual, &QPushButton::clicked, this, &QgsQueryBuilder::btnNotEqual_clicked );
60  connect( btnAnd, &QPushButton::clicked, this, &QgsQueryBuilder::btnAnd_clicked );
61  connect( btnNot, &QPushButton::clicked, this, &QgsQueryBuilder::btnNot_clicked );
62  connect( btnOr, &QPushButton::clicked, this, &QgsQueryBuilder::btnOr_clicked );
63  connect( btnGetAllValues, &QPushButton::clicked, this, &QgsQueryBuilder::btnGetAllValues_clicked );
64  connect( btnSampleValues, &QPushButton::clicked, this, &QgsQueryBuilder::btnSampleValues_clicked );
65  connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsQueryBuilder::showHelp );
66 
67  QPushButton *pbn = new QPushButton( tr( "&Test" ) );
68  buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
69  connect( pbn, &QAbstractButton::clicked, this, &QgsQueryBuilder::test );
70 
71  pbn = new QPushButton( tr( "&Clear" ) );
72  buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
73  connect( pbn, &QAbstractButton::clicked, this, &QgsQueryBuilder::clear );
74 
75  pbn = new QPushButton( tr( "&Save…" ) );
76  buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
77  pbn->setToolTip( tr( "Save query to QQF file" ) );
78  connect( pbn, &QAbstractButton::clicked, this, &QgsQueryBuilder::saveQuery );
79 
80  pbn = new QPushButton( tr( "&Load…" ) );
81  buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
82  pbn->setToolTip( tr( "Load query from QQF file" ) );
83  connect( pbn, &QAbstractButton::clicked, this, &QgsQueryBuilder::loadQuery );
84 
85  setupGuiViews();
86 
87  mOrigSubsetString = layer->subsetString();
88  connect( layer, &QgsVectorLayer::subsetStringChanged, this, &QgsQueryBuilder::layerSubsetStringChanged );
89  layerSubsetStringChanged();
90 
91  lblDataUri->setText( tr( "Set provider filter on %1" ).arg( layer->name() ) );
92  mTxtSql->setText( mOrigSubsetString );
93 
94  mFilterLineEdit->setShowSearchIcon( true );
95  mFilterLineEdit->setPlaceholderText( tr( "Search…" ) );
96  connect( mFilterLineEdit, &QgsFilterLineEdit::textChanged, this, &QgsQueryBuilder::onTextChanged );
97 
98  populateFields();
99 }
100 
101 void QgsQueryBuilder::showEvent( QShowEvent *event )
102 {
103  mTxtSql->setFocus();
104  QDialog::showEvent( event );
105 }
106 
107 void QgsQueryBuilder::populateFields()
108 {
109  const QgsFields &fields = mLayer->fields();
110  mTxtSql->setFields( fields );
111  for ( int idx = 0; idx < fields.count(); ++idx )
112  {
113  if ( fields.fieldOrigin( idx ) != QgsFields::OriginProvider )
114  {
115  // only consider native fields
116  continue;
117  }
118  QStandardItem *myItem = new QStandardItem( fields.at( idx ).displayNameWithAlias() );
119  myItem->setData( idx );
120  myItem->setEditable( false );
121  mModelFields->insertRow( mModelFields->rowCount(), myItem );
122  }
123 
124  // All fields get ... setup
125  setupLstFieldsModel();
126 }
127 
128 void QgsQueryBuilder::setupLstFieldsModel()
129 {
130  lstFields->setModel( mModelFields );
131 }
132 
133 void QgsQueryBuilder::setupGuiViews()
134 {
135  //Initialize the models
136  mModelFields = new QStandardItemModel();
137  mModelValues = new QStandardItemModel();
138  mProxyValues = new QSortFilterProxyModel();
139  mProxyValues->setSourceModel( mModelValues );
140  // Modes
141  lstFields->setViewMode( QListView::ListMode );
142  lstValues->setViewMode( QListView::ListMode );
143  lstFields->setSelectionBehavior( QAbstractItemView::SelectRows );
144  lstValues->setSelectionBehavior( QAbstractItemView::SelectRows );
145  // Performance tip since Qt 4.1
146  lstFields->setUniformItemSizes( true );
147  lstValues->setUniformItemSizes( true );
148  // Colored rows
149  lstFields->setAlternatingRowColors( true );
150  lstValues->setAlternatingRowColors( true );
151  lstValues->setModel( mProxyValues );
152 }
153 
154 void QgsQueryBuilder::fillValues( int idx, int limit )
155 {
156  // clear the model
157  mModelValues->clear();
158 
159  // determine the field type
160  QList<QVariant> values = qgis::setToList( mLayer->uniqueValues( idx, limit ) );
161  std::sort( values.begin(), values.end() );
162 
163  QString nullValue = QgsApplication::nullRepresentation();
164 
165  QgsDebugMsg( QStringLiteral( "nullValue: %1" ).arg( nullValue ) );
166 
167  const auto constValues = values;
168  for ( const QVariant &var : constValues )
169  {
170  QString value;
171  if ( var.isNull() )
172  value = nullValue;
173  else if ( var.type() == QVariant::Date && mLayer->providerType() == QLatin1String( "ogr" ) && mLayer->storageType() == QLatin1String( "ESRI Shapefile" ) )
174  value = var.toDate().toString( QStringLiteral( "yyyy/MM/dd" ) );
175  else if ( var.type() == QVariant::List || var.type() == QVariant::StringList )
176  {
177  const QVariantList list = var.toList();
178  for ( const QVariant &val : list )
179  {
180  if ( !value.isEmpty() )
181  value.append( ", " );
182  value.append( val.isNull() ? nullValue : val.toString() );
183  }
184  }
185  else
186  value = var.toString();
187 
188  QStandardItem *myItem = new QStandardItem( value );
189  myItem->setEditable( false );
190  myItem->setData( var, Qt::UserRole + 1 );
191  mModelValues->insertRow( mModelValues->rowCount(), myItem );
192  QgsDebugMsg( QStringLiteral( "Value is null: %1\nvalue: %2" ).arg( var.isNull() ).arg( var.isNull() ? nullValue : var.toString() ) );
193  }
194 }
195 
196 void QgsQueryBuilder::btnSampleValues_clicked()
197 {
198  lstValues->setCursor( Qt::WaitCursor );
199 
200  QString prevSubsetString = mLayer->subsetString();
201  if ( mUseUnfilteredLayer->isChecked() && !prevSubsetString.isEmpty() )
202  {
203  mIgnoreLayerSubsetStringChangedSignal = true;
204  mLayer->setSubsetString( QString() );
205  }
206 
207  //Clear and fill the mModelValues
208  fillValues( mModelFields->data( lstFields->currentIndex(), Qt::UserRole + 1 ).toInt(), 25 );
209 
210  if ( prevSubsetString != mLayer->subsetString() )
211  {
212  mLayer->setSubsetString( prevSubsetString );
213  mIgnoreLayerSubsetStringChangedSignal = false;
214  }
215 
216  lstValues->setCursor( Qt::ArrowCursor );
217 }
218 
219 void QgsQueryBuilder::btnGetAllValues_clicked()
220 {
221  lstValues->setCursor( Qt::WaitCursor );
222 
223  QString prevSubsetString = mLayer->subsetString();
224  if ( mUseUnfilteredLayer->isChecked() && !prevSubsetString.isEmpty() )
225  {
226  mIgnoreLayerSubsetStringChangedSignal = true;
227  mLayer->setSubsetString( QString() );
228  }
229 
230  //Clear and fill the mModelValues
231  fillValues( mModelFields->data( lstFields->currentIndex(), Qt::UserRole + 1 ).toInt(), -1 );
232 
233  if ( prevSubsetString != mLayer->subsetString() )
234  {
235  mLayer->setSubsetString( prevSubsetString );
236  mIgnoreLayerSubsetStringChangedSignal = false;
237  }
238 
239  lstValues->setCursor( Qt::ArrowCursor );
240 }
241 
243 {
244  // test the sql statement to see if it works
245  // by counting the number of records that would be
246  // returned
247 
248  if ( mLayer->setSubsetString( mTxtSql->text() ) )
249  {
250  const long long featureCount { mLayer->featureCount() };
251  // Check for errors
252  if ( featureCount < 0 )
253  {
254  QMessageBox::warning( this,
255  tr( "Query Result" ),
256  tr( "An error occurred when executing the query, please check the expression syntax." ) );
257  }
258  else
259  {
260  QMessageBox::information( this,
261  tr( "Query Result" ),
262  tr( "The where clause returned %1 row(s).", "returned test rows" ). arg( featureCount ) );
263  }
264  }
265  else if ( mLayer->dataProvider()->hasErrors() )
266  {
267  QMessageBox::warning( this,
268  tr( "Query Result" ),
269  tr( "An error occurred when executing the query." )
270  + tr( "\nThe data provider said:\n%1" ).arg( mLayer->dataProvider()->errors().join( QLatin1Char( '\n' ) ) ) );
271  mLayer->dataProvider()->clearErrors();
272  }
273  else
274  {
275  QMessageBox::warning( this,
276  tr( "Query Result" ),
277  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::warning( this,
291  tr( "Query Result" ),
292  tr( "An error occurred when executing the query." )
293  + tr( "\nThe data provider said:\n%1" ).arg( mLayer->dataProvider()->errors().join( QLatin1Char( '\n' ) ) ) );
294  mLayer->dataProvider()->clearErrors();
295  }
296  else
297  {
298  QMessageBox::warning( this, tr( "Query Result" ), tr( "Error in query. The subset string could not be set." ) );
299  }
300 
301  return;
302  }
303  }
304 
305  QDialog::accept();
306 }
307 
309 {
310  if ( mLayer->subsetString() != mOrigSubsetString )
311  mLayer->setSubsetString( mOrigSubsetString );
312 
313  QDialog::reject();
314 }
315 
316 void QgsQueryBuilder::btnEqual_clicked()
317 {
318  mTxtSql->insertText( QStringLiteral( " = " ) );
319  mTxtSql->setFocus();
320 }
321 
322 void QgsQueryBuilder::btnLessThan_clicked()
323 {
324  mTxtSql->insertText( QStringLiteral( " < " ) );
325  mTxtSql->setFocus();
326 }
327 
328 void QgsQueryBuilder::btnGreaterThan_clicked()
329 {
330  mTxtSql->insertText( QStringLiteral( " > " ) );
331  mTxtSql->setFocus();
332 }
333 
334 void QgsQueryBuilder::btnPct_clicked()
335 {
336  mTxtSql->insertText( QStringLiteral( "%" ) );
337  mTxtSql->setFocus();
338 }
339 
340 void QgsQueryBuilder::btnIn_clicked()
341 {
342  mTxtSql->insertText( QStringLiteral( " IN " ) );
343  mTxtSql->setFocus();
344 }
345 
346 void QgsQueryBuilder::btnNotIn_clicked()
347 {
348  mTxtSql->insertText( QStringLiteral( " NOT IN " ) );
349  mTxtSql->setFocus();
350 }
351 
352 void QgsQueryBuilder::btnLike_clicked()
353 {
354  mTxtSql->insertText( QStringLiteral( " LIKE " ) );
355  mTxtSql->setFocus();
356 }
357 
358 QString QgsQueryBuilder::sql() const
359 {
360  return mTxtSql->text();
361 }
362 
363 void QgsQueryBuilder::setSql( const QString &sqlStatement )
364 {
365  mTxtSql->setText( sqlStatement );
366 }
367 
368 void QgsQueryBuilder::lstFields_clicked( const QModelIndex &index )
369 {
370  if ( mPreviousFieldRow != index.row() )
371  {
372  mPreviousFieldRow = index.row();
373 
374  btnSampleValues->setEnabled( true );
375  btnGetAllValues->setEnabled( true );
376 
377  mModelValues->clear();
378  mFilterLineEdit->clear();
379  }
380 }
381 
382 void QgsQueryBuilder::lstFields_doubleClicked( const QModelIndex &index )
383 {
384  mTxtSql->insertText( '\"' + mLayer->fields().at( mModelFields->data( index, Qt::UserRole + 1 ).toInt() ).name() + '\"' );
385  mTxtSql->setFocus();
386 }
387 
388 void QgsQueryBuilder::lstValues_doubleClicked( const QModelIndex &index )
389 {
390  QVariant value = index.data( Qt::UserRole + 1 );
391  if ( value.isNull() )
392  mTxtSql->insertText( QStringLiteral( "NULL" ) );
393  else if ( value.type() == QVariant::Date && mLayer->providerType() == QLatin1String( "ogr" ) && mLayer->storageType() == QLatin1String( "ESRI Shapefile" ) )
394  mTxtSql->insertText( '\'' + value.toDate().toString( QStringLiteral( "yyyy/MM/dd" ) ) + '\'' );
395  else if ( value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong || value.type() == QVariant::Bool )
396  mTxtSql->insertText( value.toString() );
397  else
398  mTxtSql->insertText( '\'' + value.toString().replace( '\'', QLatin1String( "''" ) ) + '\'' );
399 
400  mTxtSql->setFocus();
401 }
402 
403 void QgsQueryBuilder::btnLessEqual_clicked()
404 {
405  mTxtSql->insertText( QStringLiteral( " <= " ) );
406  mTxtSql->setFocus();
407 }
408 
409 void QgsQueryBuilder::btnGreaterEqual_clicked()
410 {
411  mTxtSql->insertText( QStringLiteral( " >= " ) );
412  mTxtSql->setFocus();
413 }
414 
415 void QgsQueryBuilder::btnNotEqual_clicked()
416 {
417  mTxtSql->insertText( QStringLiteral( " != " ) );
418  mTxtSql->setFocus();
419 }
420 
421 void QgsQueryBuilder::btnAnd_clicked()
422 {
423  mTxtSql->insertText( QStringLiteral( " AND " ) );
424  mTxtSql->setFocus();
425 }
426 
427 void QgsQueryBuilder::btnNot_clicked()
428 {
429  mTxtSql->insertText( QStringLiteral( " NOT " ) );
430  mTxtSql->setFocus();
431 }
432 
433 void QgsQueryBuilder::btnOr_clicked()
434 {
435  mTxtSql->insertText( QStringLiteral( " OR " ) );
436  mTxtSql->setFocus();
437 }
438 
439 void QgsQueryBuilder::onTextChanged( const QString &text )
440 {
441  mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
442  mProxyValues->setFilterWildcard( text );
443 }
444 
446 {
447  mTxtSql->clear();
448  mLayer->setSubsetString( QString() );
449 }
450 
451 void QgsQueryBuilder::btnILike_clicked()
452 {
453  mTxtSql->insertText( QStringLiteral( " ILIKE " ) );
454  mTxtSql->setFocus();
455 }
456 
458 {
459  lblDataUri->setText( uri );
460 }
461 
462 void QgsQueryBuilder::showHelp()
463 {
464  QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#query-builder" ) );
465 }
466 
468 {
469  QgsSettings s;
470  QString lastQueryFileDir = s.value( QStringLiteral( "/UI/lastQueryFileDir" ), QDir::homePath() ).toString();
471  //save as qqf (QGIS query file)
472  QString saveFileName = QFileDialog::getSaveFileName( nullptr, tr( "Save Query to File" ), lastQueryFileDir, tr( "Query files (*.qqf *.QQF)" ) );
473  if ( saveFileName.isNull() )
474  {
475  return;
476  }
477 
478  if ( !saveFileName.endsWith( QLatin1String( ".qqf" ), Qt::CaseInsensitive ) )
479  {
480  saveFileName += QLatin1String( ".qqf" );
481  }
482 
483  QFile saveFile( saveFileName );
484  if ( !saveFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
485  {
486  QMessageBox::critical( nullptr, tr( "Save Query to File" ), tr( "Could not open file for writing." ) );
487  return;
488  }
489 
490  QDomDocument xmlDoc;
491  QDomElement queryElem = xmlDoc.createElement( QStringLiteral( "Query" ) );
492  QDomText queryTextNode = xmlDoc.createTextNode( mTxtSql->text() );
493  queryElem.appendChild( queryTextNode );
494  xmlDoc.appendChild( queryElem );
495 
496  QTextStream fileStream( &saveFile );
497  xmlDoc.save( fileStream, 2 );
498 
499  QFileInfo fi( saveFile );
500  s.setValue( QStringLiteral( "/UI/lastQueryFileDir" ), fi.absolutePath() );
501 }
502 
504 {
505  QgsSettings s;
506  QString lastQueryFileDir = s.value( QStringLiteral( "/UI/lastQueryFileDir" ), QDir::homePath() ).toString();
507 
508  QString queryFileName = QFileDialog::getOpenFileName( nullptr, tr( "Load Query from File" ), lastQueryFileDir, tr( "Query files" ) + " (*.qqf);;" + tr( "All files" ) + " (*)" );
509  if ( queryFileName.isNull() )
510  {
511  return;
512  }
513 
514  QFile queryFile( queryFileName );
515  if ( !queryFile.open( QIODevice::ReadOnly ) )
516  {
517  QMessageBox::critical( nullptr, tr( "Load Query from File" ), tr( "Could not open file for reading." ) );
518  return;
519  }
520  QDomDocument queryDoc;
521  if ( !queryDoc.setContent( &queryFile ) )
522  {
523  QMessageBox::critical( nullptr, tr( "Load Query from File" ), tr( "File is not a valid xml document." ) );
524  return;
525  }
526 
527  QDomElement queryElem = queryDoc.firstChildElement( QStringLiteral( "Query" ) );
528  if ( queryElem.isNull() )
529  {
530  QMessageBox::critical( nullptr, tr( "Load Query from File" ), tr( "File is not a valid query document." ) );
531  return;
532  }
533 
534  QString query = queryElem.text();
535 
536  //TODO: test if all the attributes are valid
537  QgsExpression search( query );
538  if ( search.hasParserError() )
539  {
540  QMessageBox::critical( this, tr( "Query Result" ), search.parserErrorString() );
541  return;
542  }
543 
544  mTxtSql->clear();
545  mTxtSql->insertText( query );
546 }
547 
548 void QgsQueryBuilder::layerSubsetStringChanged()
549 {
550  if ( mIgnoreLayerSubsetStringChangedSignal )
551  return;
552  mUseUnfilteredLayer->setDisabled( mLayer->subsetString().isEmpty() );
553 }
static QString nullRepresentation()
This string is used to represent the value NULL throughout QGIS.
Class for parsing and evaluation of expressions (formerly called "search strings").
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
QString parserErrorString() const
Returns parser error.
QString name
Definition: qgsfield.h:60
QString displayNameWithAlias() const
Returns the name to use when displaying this field and adds the alias in parenthesis if it is defined...
Definition: qgsfield.cpp:96
Container of fields for a vector layer.
Definition: qgsfields.h:45
@ OriginProvider
Field comes from the underlying data provider of the vector layer (originIndex = index in provider's ...
Definition: qgsfields.h:51
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
FieldOrigin fieldOrigin(int fieldIdx) const
Returns the field's origin (value from an enumeration).
Definition: qgsfields.cpp:189
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:163
static QgsGui * instance()
Returns a pointer to the singleton instance.
Definition: qgsgui.cpp:65
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:156
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition: qgshelp.cpp:36
QString name
Definition: qgsmaplayer.h:73
QString providerType() const
Returns the provider type (provider key) for this layer.
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...
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.
Interface for a dialog that can edit subset strings.
void clearErrors()
Clear recorded errors.
QStringList errors() const
Gets recorded errors.
bool hasErrors() const
Provider has errors to report.
Represents a vector layer which manages a vector based data sets.
long long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
void subsetStringChanged()
Emitted when the layer's subset string has changed.
QString storageType() const
Returns the permanent storage type for this layer as a friendly name.
QString subsetString
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
virtual bool setSubsetString(const QString &subset)
Sets the string (typically sql) used to define a subset of the layer.
QSet< QVariant > uniqueValues(int fieldIndex, int limit=-1) const FINAL
Calculates a list of unique values contained within an attribute in the layer.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38