QGIS API Documentation 3.99.0-Master (09f76ad7019)
Loading...
Searching...
No Matches
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
17
18#include "qgsexpression.h"
20#include "qgsfeature.h"
21#include "qgsfeatureiterator.h"
22#include "qgsfields.h"
23#include "qgshelp.h"
24#include "qgsquerybuilder.h"
25#include "qgsvectorlayer.h"
26
27#include <QDomDocument>
28#include <QDomElement>
29#include <QFileDialog>
30#include <QFileInfo>
31#include <QInputDialog>
32#include <QListView>
33#include <QMessageBox>
34#include <QStandardItem>
35#include <QString>
36#include <QTextStream>
37
38#include "moc_qgssearchquerybuilder.cpp"
39
40using namespace Qt::StringLiterals;
41
42QgsSearchQueryBuilder::QgsSearchQueryBuilder( QgsVectorLayer *layer, QWidget *parent, Qt::WindowFlags fl )
43 : QDialog( parent, fl )
44 , mLayer( layer )
45{
46 setupUi( this );
47 connect( btnEqual, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnEqual_clicked );
48 connect( btnLessThan, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnLessThan_clicked );
49 connect( btnGreaterThan, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnGreaterThan_clicked );
50 connect( btnLike, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnLike_clicked );
51 connect( btnILike, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnILike_clicked );
52 connect( btnPct, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnPct_clicked );
53 connect( btnIn, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnIn_clicked );
54 connect( btnNotIn, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnNotIn_clicked );
55 connect( lstFields, &QListView::doubleClicked, this, &QgsSearchQueryBuilder::lstFields_doubleClicked );
56 connect( lstValues, &QListView::doubleClicked, this, &QgsSearchQueryBuilder::lstValues_doubleClicked );
57 connect( btnLessEqual, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnLessEqual_clicked );
58 connect( btnGreaterEqual, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnGreaterEqual_clicked );
59 connect( btnNotEqual, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnNotEqual_clicked );
60 connect( btnAnd, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnAnd_clicked );
61 connect( btnNot, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnNot_clicked );
62 connect( btnOr, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnOr_clicked );
63 connect( btnGetAllValues, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnGetAllValues_clicked );
64 connect( btnSampleValues, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnSampleValues_clicked );
65 setupListViews();
66 connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsSearchQueryBuilder::showHelp );
67
68 setWindowTitle( tr( "Search Query Builder" ) );
69
70 QPushButton *pbn = new QPushButton( tr( "&Test" ) );
71 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
72 connect( pbn, &QAbstractButton::clicked, this, &QgsSearchQueryBuilder::btnTest_clicked );
73
74 pbn = new QPushButton( tr( "&Clear" ) );
75 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
76 connect( pbn, &QAbstractButton::clicked, this, &QgsSearchQueryBuilder::btnClear_clicked );
77
78 pbn = new QPushButton( tr( "&Save…" ) );
79 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
80 pbn->setToolTip( tr( "Save query to an xml file" ) );
81 connect( pbn, &QAbstractButton::clicked, this, &QgsSearchQueryBuilder::saveQuery );
82
83 pbn = new QPushButton( tr( "&Load…" ) );
84 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
85 pbn->setToolTip( tr( "Load query from xml file" ) );
86 connect( pbn, &QAbstractButton::clicked, this, &QgsSearchQueryBuilder::loadQuery );
87
88 if ( layer )
89 lblDataUri->setText( layer->name() );
90 populateFields();
91}
92
93void QgsSearchQueryBuilder::populateFields()
94{
95 if ( !mLayer )
96 return;
97
98 const QgsFields &fields = mLayer->fields();
99 for ( int idx = 0; idx < fields.count(); ++idx )
100 {
101 const QString fieldName = fields.at( idx ).name();
102 mFieldMap[fieldName] = idx;
103 QStandardItem *myItem = new QStandardItem( fieldName );
104 myItem->setEditable( false );
105 mModelFields->insertRow( mModelFields->rowCount(), myItem );
106 }
107}
108
109void QgsSearchQueryBuilder::setupListViews()
110{
111 //Models
112 mModelFields = new QStandardItemModel();
113 mModelValues = new QStandardItemModel();
114 lstFields->setModel( mModelFields );
115 lstValues->setModel( mModelValues );
116 // Modes
117 lstFields->setViewMode( QListView::ListMode );
118 lstValues->setViewMode( QListView::ListMode );
119 lstFields->setSelectionBehavior( QAbstractItemView::SelectRows );
120 lstValues->setSelectionBehavior( QAbstractItemView::SelectRows );
121 // Performance tip since Qt 4.1
122 lstFields->setUniformItemSizes( true );
123 lstValues->setUniformItemSizes( true );
124}
125
126void QgsSearchQueryBuilder::getFieldValues( int limit )
127{
128 if ( !mLayer )
129 {
130 return;
131 }
132 // clear the values list
133 mModelValues->clear();
134
135 // determine the field type
136 const QString fieldName = mModelFields->data( lstFields->currentIndex() ).toString();
137 const int fieldIndex = mFieldMap[fieldName];
138 const QgsField field = mLayer->fields().at( fieldIndex ); //provider->fields().at( fieldIndex );
139 const bool numeric = ( field.type() == QMetaType::Type::Int || field.type() == QMetaType::Type::Double );
140
141 QgsFeature feat;
142 QString value;
143
144 QgsAttributeList attrs;
145 attrs.append( fieldIndex );
146
147 QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFlags( Qgis::FeatureRequestFlag::NoGeometry ).setSubsetOfAttributes( attrs ) );
148
149 lstValues->setCursor( Qt::WaitCursor );
150 // Block for better performance
151 mModelValues->blockSignals( true );
152 lstValues->setUpdatesEnabled( false );
153
154 // MH: keep already inserted values in a set. Querying is much faster compared to QStandardItemModel::findItems
155 QSet<QString> insertedValues;
156
157 while ( fit.nextFeature( feat ) && ( limit == 0 || mModelValues->rowCount() != limit ) )
158 {
159 value = feat.attribute( fieldIndex ).toString();
160
161 if ( !numeric )
162 {
163 // put string in single quotes and escape single quotes in the string
164 value = '\'' + value.replace( '\'', "''"_L1 ) + '\'';
165 }
166
167 // add item only if it's not there already
168 if ( !insertedValues.contains( value ) )
169 {
170 QStandardItem *myItem = new QStandardItem( value );
171 myItem->setEditable( false );
172 mModelValues->insertRow( mModelValues->rowCount(), myItem );
173 insertedValues.insert( value );
174 }
175 }
176 // Unblock for normal use
177 mModelValues->blockSignals( false );
178 lstValues->setUpdatesEnabled( true );
179 // TODO: already sorted, signal emit to refresh model
180 mModelValues->sort( 0 );
181 lstValues->setCursor( Qt::ArrowCursor );
182}
183
184void QgsSearchQueryBuilder::btnSampleValues_clicked()
185{
186 getFieldValues( 25 );
187}
188
189void QgsSearchQueryBuilder::btnGetAllValues_clicked()
190{
191 getFieldValues( 0 );
192}
193
194void QgsSearchQueryBuilder::btnTest_clicked()
195{
196 const long count = countRecords( mTxtSql->text() );
197
198 // error?
199 if ( count == -1 )
200 return;
201
202 QMessageBox::information( this, tr( "Test Query" ), tr( "Found %n matching feature(s).", "test result", count ) );
203}
204
205// This method tests the number of records that would be returned
206long QgsSearchQueryBuilder::countRecords( const QString &searchString )
207{
208 QgsExpression search( searchString );
209 if ( search.hasParserError() )
210 {
211 QMessageBox::critical( this, tr( "Query Result" ), search.parserErrorString() );
212 return -1;
213 }
214
215 if ( !mLayer )
216 return -1;
217
218 const bool fetchGeom = search.needsGeometry();
219
220 int count = 0;
221 QgsFeature feat;
222
223 QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) );
224
225 if ( !search.prepare( &context ) )
226 {
227 QMessageBox::critical( this, tr( "Query Result" ), search.evalErrorString() );
228 return -1;
229 }
230
231 QApplication::setOverrideCursor( Qt::WaitCursor );
232
233 QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFlags( fetchGeom ? Qgis::FeatureRequestFlag::NoFlags : Qgis::FeatureRequestFlag::NoGeometry ) );
234
235 while ( fit.nextFeature( feat ) )
236 {
237 context.setFeature( feat );
238 const QVariant value = search.evaluate( &context );
239 if ( value.toInt() != 0 )
240 {
241 count++;
242 }
243
244 // check if there were errors during evaluating
245 if ( search.hasEvalError() )
246 break;
247 }
248
249 QApplication::restoreOverrideCursor();
250
251 if ( search.hasEvalError() )
252 {
253 QMessageBox::critical( this, tr( "Query Result" ), search.evalErrorString() );
254 return -1;
255 }
256
257 return count;
258}
259
260
261void QgsSearchQueryBuilder::btnOk_clicked()
262{
263 // if user hits OK and there is no query, skip the validation
264 if ( mTxtSql->text().trimmed().length() > 0 )
265 {
266 accept();
267 return;
268 }
269
270 // test the query to see if it will result in a valid layer
271 const long numRecs = countRecords( mTxtSql->text() );
272 if ( numRecs == -1 )
273 {
274 // error shown in countRecords
275 }
276 else if ( numRecs == 0 )
277 {
278 QMessageBox::warning( this, tr( "Query Result" ), tr( "The query you specified results in zero records being returned." ) );
279 }
280 else
281 {
282 accept();
283 }
284}
285
286void QgsSearchQueryBuilder::btnEqual_clicked()
287{
288 mTxtSql->insertText( u" = "_s );
289}
290
291void QgsSearchQueryBuilder::btnLessThan_clicked()
292{
293 mTxtSql->insertText( u" < "_s );
294}
295
296void QgsSearchQueryBuilder::btnGreaterThan_clicked()
297{
298 mTxtSql->insertText( u" > "_s );
299}
300
301void QgsSearchQueryBuilder::btnPct_clicked()
302{
303 mTxtSql->insertText( u"%"_s );
304}
305
306void QgsSearchQueryBuilder::btnIn_clicked()
307{
308 mTxtSql->insertText( u" IN "_s );
309}
310
311void QgsSearchQueryBuilder::btnNotIn_clicked()
312{
313 mTxtSql->insertText( u" NOT IN "_s );
314}
315
316void QgsSearchQueryBuilder::btnLike_clicked()
317{
318 mTxtSql->insertText( u" LIKE "_s );
319}
320
322{
323 return mTxtSql->text();
324}
325
327{
328 mTxtSql->setText( searchString );
329}
330
331void QgsSearchQueryBuilder::lstFields_doubleClicked( const QModelIndex &index )
332{
333 mTxtSql->insertText( QgsExpression::quotedColumnRef( mModelFields->data( index ).toString() ) );
334}
335
336void QgsSearchQueryBuilder::lstValues_doubleClicked( const QModelIndex &index )
337{
338 mTxtSql->insertText( mModelValues->data( index ).toString() );
339}
340
341void QgsSearchQueryBuilder::btnLessEqual_clicked()
342{
343 mTxtSql->insertText( u" <= "_s );
344}
345
346void QgsSearchQueryBuilder::btnGreaterEqual_clicked()
347{
348 mTxtSql->insertText( u" >= "_s );
349}
350
351void QgsSearchQueryBuilder::btnNotEqual_clicked()
352{
353 mTxtSql->insertText( u" != "_s );
354}
355
356void QgsSearchQueryBuilder::btnAnd_clicked()
357{
358 mTxtSql->insertText( u" AND "_s );
359}
360
361void QgsSearchQueryBuilder::btnNot_clicked()
362{
363 mTxtSql->insertText( u" NOT "_s );
364}
365
366void QgsSearchQueryBuilder::btnOr_clicked()
367{
368 mTxtSql->insertText( u" OR "_s );
369}
370
371void QgsSearchQueryBuilder::btnClear_clicked()
372{
373 mTxtSql->clear();
374}
375
376void QgsSearchQueryBuilder::btnILike_clicked()
377{
378 mTxtSql->insertText( u" ILIKE "_s );
379}
380
385
387{
388 QString query;
390 {
391 mTxtSql->clear();
392 mTxtSql->insertText( query );
393 }
394}
395
396void QgsSearchQueryBuilder::showHelp()
397{
398 QgsHelp::openHelp( u"working_with_vector/vector_properties.html#query-builder"_s );
399}
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2254
@ NoFlags
No flags are set.
Definition qgis.h:2253
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes).
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
QMetaType::Type type
Definition qgsfield.h:63
QString name
Definition qgsfield.h:65
Container of fields for a vector layer.
Definition qgsfields.h:46
int count
Definition qgsfields.h:50
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
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
static bool loadQueryFromFile(QString &subset)
Load query from the XML file.
static bool saveQueryToFile(const QString &subset)
Save query to the XML file.
QgsSearchQueryBuilder(QgsVectorLayer *layer, QWidget *parent=nullptr, Qt::WindowFlags fl=QgsGuiUtils::ModalDialogFlags)
Constructor - takes pointer to vector layer as a parameter.
void setSearchString(const QString &searchString)
change search string shown in text field
QString searchString()
returns newly created search string
Represents a vector layer which manages a vector based dataset.
QList< int > QgsAttributeList
Definition qgsfield.h:30