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