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