QGIS API Documentation  2.99.0-Master (7705179)
qgssearchquerybuilder.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgssearchquerybuilder.cpp - Query builder for search strings
3  ----------------------
4  begin : March 2006
5  copyright : (C) 2006 by Martin Dobias
6  email : wonder.sk at gmail dot 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 
16 #include <QDomDocument>
17 #include <QDomElement>
18 #include <QFileDialog>
19 #include <QFileInfo>
20 #include <QInputDialog>
21 #include <QListView>
22 #include <QMessageBox>
23 #include <QStandardItem>
24 #include <QTextStream>
25 
26 #include "qgssettings.h"
27 #include "qgsfeature.h"
28 #include "qgsfeatureiterator.h"
29 #include "qgsfields.h"
30 #include "qgssearchquerybuilder.h"
31 #include "qgsexpression.h"
32 #include "qgsvectorlayer.h"
33 #include "qgslogger.h"
34 #include "qgshelp.h"
35 
37  QWidget *parent, Qt::WindowFlags fl )
38  : QDialog( parent, fl )
39  , mLayer( layer )
40 {
41  setupUi( this );
42  setupListViews();
43  connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsSearchQueryBuilder::showHelp );
44 
45  setWindowTitle( tr( "Search Query Builder" ) );
46 
47  QPushButton *pbn = new QPushButton( tr( "&Test" ) );
48  buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
49  connect( pbn, &QAbstractButton::clicked, this, &QgsSearchQueryBuilder::on_btnTest_clicked );
50 
51  pbn = new QPushButton( tr( "&Clear" ) );
52  buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
53  connect( pbn, &QAbstractButton::clicked, this, &QgsSearchQueryBuilder::on_btnClear_clicked );
54 
55  pbn = new QPushButton( tr( "&Save..." ) );
56  buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
57  pbn->setToolTip( tr( "Save query to an xml file" ) );
58  connect( pbn, &QAbstractButton::clicked, this, &QgsSearchQueryBuilder::saveQuery );
59 
60  pbn = new QPushButton( tr( "&Load..." ) );
61  buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
62  pbn->setToolTip( tr( "Load query from xml file" ) );
63  connect( pbn, &QAbstractButton::clicked, this, &QgsSearchQueryBuilder::loadQuery );
64 
65  if ( layer )
66  lblDataUri->setText( layer->name() );
67  populateFields();
68 }
69 
70 void QgsSearchQueryBuilder::populateFields()
71 {
72  if ( !mLayer )
73  return;
74 
75  const QgsFields &fields = mLayer->fields();
76  for ( int idx = 0; idx < fields.count(); ++idx )
77  {
78  QString fieldName = fields.at( idx ).name();
79  mFieldMap[fieldName] = idx;
80  QStandardItem *myItem = new QStandardItem( fieldName );
81  myItem->setEditable( false );
82  mModelFields->insertRow( mModelFields->rowCount(), myItem );
83  }
84 }
85 
86 void QgsSearchQueryBuilder::setupListViews()
87 {
88  //Models
89  mModelFields = new QStandardItemModel();
90  mModelValues = new QStandardItemModel();
91  lstFields->setModel( mModelFields );
92  lstValues->setModel( mModelValues );
93  // Modes
94  lstFields->setViewMode( QListView::ListMode );
95  lstValues->setViewMode( QListView::ListMode );
96  lstFields->setSelectionBehavior( QAbstractItemView::SelectRows );
97  lstValues->setSelectionBehavior( QAbstractItemView::SelectRows );
98  // Performance tip since Qt 4.1
99  lstFields->setUniformItemSizes( true );
100  lstValues->setUniformItemSizes( true );
101 }
102 
103 void QgsSearchQueryBuilder::getFieldValues( int limit )
104 {
105  if ( !mLayer )
106  {
107  return;
108  }
109  // clear the values list
110  mModelValues->clear();
111 
112  // determine the field type
113  QString fieldName = mModelFields->data( lstFields->currentIndex() ).toString();
114  int fieldIndex = mFieldMap[fieldName];
115  QgsField field = mLayer->fields().at( fieldIndex );//provider->fields().at( fieldIndex );
116  bool numeric = ( field.type() == QVariant::Int || field.type() == QVariant::Double );
117 
118  QgsFeature feat;
119  QString value;
120 
121  QgsAttributeList attrs;
122  attrs.append( fieldIndex );
123 
124  QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( attrs ) );
125 
126  lstValues->setCursor( Qt::WaitCursor );
127  // Block for better performance
128  mModelValues->blockSignals( true );
129  lstValues->setUpdatesEnabled( false );
130 
131  // MH: keep already inserted values in a set. Querying is much faster compared to QStandardItemModel::findItems
132  QSet<QString> insertedValues;
133 
134  while ( fit.nextFeature( feat ) &&
135  ( limit == 0 || mModelValues->rowCount() != limit ) )
136  {
137  value = feat.attribute( fieldIndex ).toString();
138 
139  if ( !numeric )
140  {
141  // put string in single quotes and escape single quotes in the string
142  value = '\'' + value.replace( '\'', QLatin1String( "''" ) ) + '\'';
143  }
144 
145  // add item only if it's not there already
146  if ( !insertedValues.contains( value ) )
147  {
148  QStandardItem *myItem = new QStandardItem( value );
149  myItem->setEditable( false );
150  mModelValues->insertRow( mModelValues->rowCount(), myItem );
151  insertedValues.insert( value );
152  }
153  }
154  // Unblock for normal use
155  mModelValues->blockSignals( false );
156  lstValues->setUpdatesEnabled( true );
157  // TODO: already sorted, signal emit to refresh model
158  mModelValues->sort( 0 );
159  lstValues->setCursor( Qt::ArrowCursor );
160 }
161 
163 {
164  getFieldValues( 25 );
165 }
166 
168 {
169  getFieldValues( 0 );
170 }
171 
173 {
174  long count = countRecords( txtSQL->text() );
175 
176  // error?
177  if ( count == -1 )
178  return;
179 
180  QMessageBox::information( this, tr( "Search results" ), tr( "Found %n matching feature(s).", "test result", count ) );
181 }
182 
183 // This method tests the number of records that would be returned
184 long QgsSearchQueryBuilder::countRecords( const QString &searchString )
185 {
186  QgsExpression search( searchString );
187  if ( search.hasParserError() )
188  {
189  QMessageBox::critical( this, tr( "Search string parsing error" ), search.parserErrorString() );
190  return -1;
191  }
192 
193  if ( !mLayer )
194  return -1;
195 
196  bool fetchGeom = search.needsGeometry();
197 
198  int count = 0;
199  QgsFeature feat;
200 
202 
203  if ( !search.prepare( &context ) )
204  {
205  QMessageBox::critical( this, tr( "Evaluation error" ), search.evalErrorString() );
206  return -1;
207  }
208 
209  QApplication::setOverrideCursor( Qt::WaitCursor );
210 
212 
213  while ( fit.nextFeature( feat ) )
214  {
215  context.setFeature( feat );
216  QVariant value = search.evaluate( &context );
217  if ( value.toInt() != 0 )
218  {
219  count++;
220  }
221 
222  // check if there were errors during evaulating
223  if ( search.hasEvalError() )
224  break;
225  }
226 
227  QApplication::restoreOverrideCursor();
228 
229  if ( search.hasEvalError() )
230  {
231  QMessageBox::critical( this, tr( "Error during search" ), search.evalErrorString() );
232  return -1;
233  }
234 
235  return count;
236 }
237 
238 
240 {
241  // if user hits OK and there is no query, skip the validation
242  if ( txtSQL->text().trimmed().length() > 0 )
243  {
244  accept();
245  return;
246  }
247 
248  // test the query to see if it will result in a valid layer
249  long numRecs = countRecords( txtSQL->text() );
250  if ( numRecs == -1 )
251  {
252  // error shown in countRecords
253  }
254  else if ( numRecs == 0 )
255  {
256  QMessageBox::warning( this, tr( "No Records" ), tr( "The query you specified results in zero records being returned." ) );
257  }
258  else
259  {
260  accept();
261  }
262 
263 }
264 
266 {
267  txtSQL->insertText( QStringLiteral( " = " ) );
268 }
269 
271 {
272  txtSQL->insertText( QStringLiteral( " < " ) );
273 }
274 
276 {
277  txtSQL->insertText( QStringLiteral( " > " ) );
278 }
279 
281 {
282  txtSQL->insertText( QStringLiteral( "%" ) );
283 }
284 
286 {
287  txtSQL->insertText( QStringLiteral( " IN " ) );
288 }
289 
291 {
292  txtSQL->insertText( QStringLiteral( " NOT IN " ) );
293 }
294 
296 {
297  txtSQL->insertText( QStringLiteral( " LIKE " ) );
298 }
299 
301 {
302  return txtSQL->text();
303 }
304 
306 {
307  txtSQL->setText( searchString );
308 }
309 
311 {
312  txtSQL->insertText( QgsExpression::quotedColumnRef( mModelFields->data( index ).toString() ) );
313 }
314 
316 {
317  txtSQL->insertText( mModelValues->data( index ).toString() );
318 }
319 
321 {
322  txtSQL->insertText( QStringLiteral( " <= " ) );
323 }
324 
326 {
327  txtSQL->insertText( QStringLiteral( " >= " ) );
328 }
329 
331 {
332  txtSQL->insertText( QStringLiteral( " != " ) );
333 }
334 
336 {
337  txtSQL->insertText( QStringLiteral( " AND " ) );
338 }
339 
341 {
342  txtSQL->insertText( QStringLiteral( " NOT " ) );
343 }
344 
346 {
347  txtSQL->insertText( QStringLiteral( " OR " ) );
348 }
349 
351 {
352  txtSQL->clear();
353 }
354 
356 {
357  txtSQL->insertText( QStringLiteral( " ILIKE " ) );
358 }
359 
361 {
362  QgsSettings s;
363  QString lastQueryFileDir = s.value( QStringLiteral( "/UI/lastQueryFileDir" ), QDir::homePath() ).toString();
364  //save as qqt (QGIS query file)
365  QString saveFileName = QFileDialog::getSaveFileName( nullptr, tr( "Save query to file" ), lastQueryFileDir, QStringLiteral( "*.qqf" ) );
366  if ( saveFileName.isNull() )
367  {
368  return;
369  }
370 
371  if ( !saveFileName.endsWith( QLatin1String( ".qqf" ), Qt::CaseInsensitive ) )
372  {
373  saveFileName += QLatin1String( ".qqf" );
374  }
375 
376  QFile saveFile( saveFileName );
377  if ( !saveFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
378  {
379  QMessageBox::critical( nullptr, tr( "Error" ), tr( "Could not open file for writing" ) );
380  return;
381  }
382 
383  QDomDocument xmlDoc;
384  QDomElement queryElem = xmlDoc.createElement( QStringLiteral( "Query" ) );
385  QDomText queryTextNode = xmlDoc.createTextNode( txtSQL->text() );
386  queryElem.appendChild( queryTextNode );
387  xmlDoc.appendChild( queryElem );
388 
389  QTextStream fileStream( &saveFile );
390  xmlDoc.save( fileStream, 2 );
391 
392  QFileInfo fi( saveFile );
393  s.setValue( QStringLiteral( "/UI/lastQueryFileDir" ), fi.absolutePath() );
394 }
395 
397 {
398  QgsSettings s;
399  QString lastQueryFileDir = s.value( QStringLiteral( "/UI/lastQueryFileDir" ), QDir::homePath() ).toString();
400 
401  QString queryFileName = QFileDialog::getOpenFileName( nullptr, tr( "Load query from file" ), lastQueryFileDir, tr( "Query files" ) + " (*.qqf);;" + tr( "All files" ) + " (*)" );
402  if ( queryFileName.isNull() )
403  {
404  return;
405  }
406 
407  QFile queryFile( queryFileName );
408  if ( !queryFile.open( QIODevice::ReadOnly ) )
409  {
410  QMessageBox::critical( nullptr, tr( "Error" ), tr( "Could not open file for reading" ) );
411  return;
412  }
413  QDomDocument queryDoc;
414  if ( !queryDoc.setContent( &queryFile ) )
415  {
416  QMessageBox::critical( nullptr, tr( "Error" ), tr( "File is not a valid xml document" ) );
417  return;
418  }
419 
420  QDomElement queryElem = queryDoc.firstChildElement( QStringLiteral( "Query" ) );
421  if ( queryElem.isNull() )
422  {
423  QMessageBox::critical( nullptr, tr( "Error" ), tr( "File is not a valid query document" ) );
424  return;
425  }
426 
427  QString query = queryElem.text();
428 
429  //todo: test if all the attributes are valid
430  QgsExpression search( query );
431  if ( search.hasParserError() )
432  {
433  QMessageBox::critical( this, tr( "Search string parsing error" ), search.parserErrorString() );
434  return;
435  }
436 
437  QString newQueryText = query;
438 
439 #if 0
440  // TODO: implement with visitor pattern in QgsExpression
441 
442  QStringList attributes = searchTree->referencedColumns();
443  QMap< QString, QString> attributesToReplace;
444  QStringList existingAttributes;
445 
446  //get all existing fields
447  QMap<QString, int>::const_iterator fieldIt = mFieldMap.constBegin();
448  for ( ; fieldIt != mFieldMap.constEnd(); ++fieldIt )
449  {
450  existingAttributes.push_back( fieldIt.key() );
451  }
452 
453  //if a field does not exist, ask what field should be used instead
454  QStringList::const_iterator attIt = attributes.constBegin();
455  for ( ; attIt != attributes.constEnd(); ++attIt )
456  {
457  //test if attribute is there
458  if ( !mFieldMap.contains( attIt ) )
459  {
460  bool ok;
461  QString replaceAttribute = QInputDialog::getItem( 0, tr( "Select attribute" ), tr( "There is no attribute '%1' in the current vector layer. Please select an existing attribute" ).arg( *attIt ),
462  existingAttributes, 0, false, &ok );
463  if ( !ok || replaceAttribute.isEmpty() )
464  {
465  return;
466  }
467  attributesToReplace.insert( *attIt, replaceAttribute );
468  }
469  }
470 
471  //Now replace all the string in the query
472  QList<QgsSearchTreeNode *> columnRefList = searchTree->columnRefNodes();
473  QList<QgsSearchTreeNode *>::iterator columnIt = columnRefList.begin();
474  for ( ; columnIt != columnRefList.end(); ++columnIt )
475  {
476  QMap< QString, QString>::const_iterator replaceIt = attributesToReplace.find( ( *columnIt )->columnRef() );
477  if ( replaceIt != attributesToReplace.constEnd() )
478  {
479  ( *columnIt )->setColumnRef( replaceIt.value() );
480  }
481  }
482 
483  if ( attributesToReplace.size() > 0 )
484  {
485  newQueryText = query;
486  }
487 #endif
488 
489  txtSQL->clear();
490  txtSQL->insertText( newQueryText );
491 }
492 
493 void QgsSearchQueryBuilder::showHelp()
494 {
495  QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#query-builder" ) );
496 }
Wrapper for iterator of features from vector data provider or vector layer.
QString name
Definition: qgsfield.h:54
This class is a composition of two QSettings instances:
Definition: qgssettings.h:54
void on_lstFields_doubleClicked(const QModelIndex &index)
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
void setSearchString(const QString &searchString)
change search string shown in text field
Container of fields for a vector layer.
Definition: qgsfields.h:41
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:61
int count() const
Return number of items.
Definition: qgsfields.cpp:115
QgsField at(int i) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:135
void setValue(const QString &key, const QVariant &value, const QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
void on_btnSampleValues_clicked()
Get sample distinct values for the selected field.
QgsFields fields() const override
Returns the list of fields of this layer.
void on_btnGetAllValues_clicked()
Get all distinct values for the field.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
This class wraps a request for features to a vector layer (or directly its vector data provider)...
void on_btnTest_clicked()
Test the constructed search string to see if it&#39;s correct.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer&#39;s project and layer.
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:46
void on_lstValues_doubleClicked(const QModelIndex &index)
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const override
Query the layer for features specified in request.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), const Section section=NoSection) const
Returns the value for setting key.
QgsSearchQueryBuilder(QgsVectorLayer *layer, QWidget *parent SIP_TRANSFERTHIS=nullptr, Qt::WindowFlags fl=QgsGuiUtils::ModalDialogFlags)
Constructor - takes pointer to vector layer as a parameter.
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition: qgshelp.cpp:34
QString name
Definition: qgsmaplayer.h:58
QList< int > QgsAttributeList
Definition: qgsfield.h:27
bool nextFeature(QgsFeature &f)
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Represents a vector layer which manages a vector based data sets.
QVariant::Type type() const
Gets variant type of the field as it will be retrieved from data source.
Definition: qgsfield.cpp:93
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:255
QString searchString()
returns newly created search string