QGIS API Documentation 3.99.0-Master (09f76ad7019)
Loading...
Searching...
No Matches
qgssqlexpressioncompiler.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgssqlexpressioncompiler.cpp
3 ----------------------------
4 begin : November 2015
5 copyright : (C) 2015 Nyall Dawson
6 email : nyall dot dawson 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"
21#include "qgsvariantutils.h"
22
23#include <QString>
24
25using namespace Qt::StringLiterals;
26
27QgsSqlExpressionCompiler::QgsSqlExpressionCompiler( const QgsFields &fields, Flags flags, bool ignoreStaticNodes )
28 : mFields( fields )
29 , mFlags( flags )
30 , mIgnoreStaticNodes( ignoreStaticNodes )
31{
32}
33
35{
36 if ( exp->rootNode() )
37 return compileNode( exp->rootNode(), mResult );
38 else
39 return Fail;
40}
41
43{
44 return mResult;
45}
46
58
59QString QgsSqlExpressionCompiler::quotedIdentifier( const QString &identifier )
60{
61 QString quoted = identifier;
62 quoted.replace( '"', "\"\""_L1 );
63 quoted = quoted.prepend( '\"' ).append( '\"' );
64 return quoted;
65}
66
67QString QgsSqlExpressionCompiler::quotedValue( const QVariant &value, bool &ok )
68{
69 ok = true;
70
71 if ( QgsVariantUtils::isNull( value ) )
72 return u"NULL"_s;
73
74 switch ( value.userType() )
75 {
76 case QMetaType::Type::Int:
77 case QMetaType::Type::LongLong:
78 case QMetaType::Type::Double:
79 return value.toString();
80
81 case QMetaType::Type::Bool:
82 return value.toBool() ? u"TRUE"_s : u"FALSE"_s;
83
84 default:
85 case QMetaType::Type::QString:
86 QString v = value.toString();
87 v.replace( '\'', "''"_L1 );
88 if ( v.contains( '\\' ) )
89 return v.replace( '\\', "\\\\"_L1 ).prepend( "E'" ).append( '\'' );
90 else
91 return v.prepend( '\'' ).append( '\'' );
92 }
93}
94
96{
98 if ( staticRes != Fail )
99 return staticRes;
100
101 // This is just to identify the most simple cases where nodes are numeric
102 std::function<bool( const QgsExpressionNode * )> nodeIsNumeric;
103 nodeIsNumeric = [this, &nodeIsNumeric]( const QgsExpressionNode * node )
104 {
105 const QgsExpressionNode::NodeType nodeType { node->nodeType() };
106
107 switch ( nodeType )
108 {
110 {
111 const QgsExpressionNodeColumnRef *col = static_cast<const QgsExpressionNodeColumnRef *>( node );
112 const int idx = mFields.indexFromName( col->name() );
113 return idx >= 0 && QgsVariantUtils::isNumericType( mFields[idx].type() );
114 }
116 {
117 const QgsExpressionNodeLiteral *lit = static_cast<const QgsExpressionNodeLiteral *>( node );
118 return QgsVariantUtils::isNumericType( static_cast< QMetaType::Type >( lit->value().userType() ) );
119 }
121 {
122 const QgsExpressionNodeBinaryOperator *op = static_cast<const QgsExpressionNodeBinaryOperator *>( node );
123 return nodeIsNumeric( op->opLeft() ) && nodeIsNumeric( op->opRight() );
124 }
126 {
127 const QgsExpressionNodeUnaryOperator *op = static_cast<const QgsExpressionNodeUnaryOperator *>( node );
128 return nodeIsNumeric( op->operand() );
129 }
130
131 default:
132 return false;
133 }
134
135 };
136
137 switch ( node->nodeType() )
138 {
140 {
141 const QgsExpressionNodeUnaryOperator *n = static_cast<const QgsExpressionNodeUnaryOperator *>( node );
142 switch ( n->op() )
143 {
145 {
146 QString right;
147 if ( compileNode( n->operand(), right ) == Complete )
148 {
149 result = "( NOT " + right + ')';
150 return Complete;
151 }
152
153 return Fail;
154 }
155
157 {
158 if ( mFlags.testFlag( NoUnaryMinus ) )
159 return Fail;
160
161 QString right;
162 if ( compileNode( n->operand(), right ) == Complete )
163 {
164 result = "( - (" + right + "))";
165 return Complete;
166 }
167
168 return Fail;
169 }
170 }
171
172 break;
173 }
174
176 {
177 const QgsExpressionNodeBinaryOperator *n = static_cast<const QgsExpressionNodeBinaryOperator *>( node );
178
179 QString op;
180 bool partialCompilation = false;
181 bool failOnPartialNode = false;
182 switch ( n->op() )
183 {
186 {
187 // equality between column refs results in a partial compilation, since provider is performing
188 // case-insensitive matches between strings
189 partialCompilation = true;
190 }
191
192 op = u"="_s;
193 break;
194
196 op = u">="_s;
197 break;
198
200 op = u">"_s;
201 break;
202
204 op = u"<="_s;
205 break;
206
208 op = u"<"_s;
209 break;
210
212 op = u"IS"_s;
213 break;
214
216 op = u"IS NOT"_s;
217 failOnPartialNode = mFlags.testFlag( CaseInsensitiveStringMatch );
218 break;
219
221 op = u"LIKE"_s;
222 partialCompilation = mFlags.testFlag( LikeIsCaseInsensitive );
223 break;
224
226 if ( mFlags.testFlag( LikeIsCaseInsensitive ) )
227 op = u"LIKE"_s;
228 else
229 op = u"ILIKE"_s;
230 break;
231
233 op = u"NOT LIKE"_s;
234 partialCompilation = mFlags.testFlag( LikeIsCaseInsensitive );
235 failOnPartialNode = mFlags.testFlag( CaseInsensitiveStringMatch );
236 break;
237
239 failOnPartialNode = mFlags.testFlag( CaseInsensitiveStringMatch );
240 if ( mFlags.testFlag( LikeIsCaseInsensitive ) )
241 op = u"NOT LIKE"_s;
242 else
243 op = u"NOT ILIKE"_s;
244 break;
245
247 if ( mFlags.testFlag( NoNullInBooleanLogic ) )
248 {
249 if ( nodeIsNullLiteral( n->opLeft() ) || nodeIsNullLiteral( n->opRight() ) )
250 return Fail;
251 }
252
253 op = u"OR"_s;
254 break;
255
257 if ( mFlags.testFlag( NoNullInBooleanLogic ) )
258 {
259 if ( nodeIsNullLiteral( n->opLeft() ) || nodeIsNullLiteral( n->opRight() ) )
260 return Fail;
261 }
262
263 op = u"AND"_s;
264 break;
265
267 failOnPartialNode = mFlags.testFlag( CaseInsensitiveStringMatch );
268 op = u"<>"_s;
269 break;
270
272 op = u"*"_s;
273 break;
274
276 {
277 const QgsExpressionNodeBinaryOperator *nodeOp = static_cast<const QgsExpressionNodeBinaryOperator *>( node );
278 if ( nodeIsNumeric( nodeOp->opLeft() ) && nodeIsNumeric( nodeOp->opRight() ) )
279 {
280 op = u"+"_s;
281 }
282 break;
283 }
284
286 op = u"-"_s;
287 break;
288
290 op = u"/"_s;
291 break;
292
294 op = u"%"_s;
295 break;
296
298 op = u"||"_s;
299 break;
300
302 op = u"/"_s;
303 break;
304
306 op = u"^"_s;
307 break;
308
310 op = u"~"_s;
311 break;
312 }
313
314 if ( op.isNull() )
315 return Fail;
316
317 QString left;
318 const Result lr( compileNode( n->opLeft(), left ) );
319
320 if ( opIsStringComparison( n ->op() ) )
321 left = castToText( left );
322
323 QString right;
324 const Result rr( compileNode( n->opRight(), right ) );
325
326 if ( failOnPartialNode && ( lr == Partial || rr == Partial ) )
327 return Fail;
328
330 {
331 right = castToReal( right );
332 if ( right.isEmpty() )
333 {
334 // not supported
335 return Fail;
336 }
337 }
338
339 result = '(' + left + ' ' + op + ' ' + right + ')';
341 {
343 if ( result.isEmpty() )
344 {
345 // not supported
346 return Fail;
347 }
348 }
349
350 if ( lr == Complete && rr == Complete )
351 return ( partialCompilation ? Partial : Complete );
352 else if ( ( lr == Partial && rr == Complete ) || ( lr == Complete && rr == Partial ) || ( lr == Partial && rr == Partial ) )
353 return Partial;
354 else
355 return Fail;
356 }
357
359 {
360 const QgsExpressionNodeBetweenOperator *n = static_cast<const QgsExpressionNodeBetweenOperator *>( node );
361 QString res;
362 Result betweenResult = Complete;
363
364 const Result rn = compileNode( n->node(), res );
365 if ( rn == Complete || rn == Partial )
366 {
367 if ( rn == Partial )
368 {
369 betweenResult = Partial;
370 }
371 }
372 else
373 {
374 return rn;
375 }
376
377 QString s;
378 const Result rl = compileNode( n->lowerBound(), s );
379 if ( rl == Complete || rl == Partial )
380 {
381 if ( rl == Partial )
382 {
383 betweenResult = Partial;
384 }
385 }
386 else
387 {
388 return rl;
389 }
390
391 res.append( n->negate() ? u" NOT BETWEEN %1"_s.arg( s ) : u" BETWEEN %1"_s.arg( s ) );
392
393 const Result rh = compileNode( n->higherBound(), s );
394 if ( rh == Complete || rh == Partial )
395 {
396 if ( rh == Partial )
397 {
398 betweenResult = Partial;
399 }
400 }
401 else
402 {
403 return rh;
404 }
405
406 res.append( u" AND %1"_s.arg( s ) );
407 result = res;
408 return betweenResult;
409 }
410
412 {
413 const QgsExpressionNodeLiteral *n = static_cast<const QgsExpressionNodeLiteral *>( node );
414 bool ok = false;
415 if ( mFlags.testFlag( CaseInsensitiveStringMatch ) && n->value().userType() == QMetaType::Type::QString )
416 {
417 // provider uses case insensitive matching, so if literal was a string then we only have a Partial compilation and need to
418 // double check results using QGIS' expression engine
419 result = quotedValue( n->value(), ok );
420 return ok ? Partial : Fail;
421 }
422 else
423 {
424 result = quotedValue( n->value(), ok );
425 return ok ? Complete : Fail;
426 }
427 }
428
430 {
431 const QgsExpressionNodeColumnRef *n = static_cast<const QgsExpressionNodeColumnRef *>( node );
432
433 // QGIS expressions don't care about case sensitive field naming, so we match case insensitively here to the
434 // layer's fields and then retrieve the actual case of the field name for use in the compilation
435 const int fieldIndex = mFields.lookupField( n->name() );
436 if ( fieldIndex == -1 )
437 // Not a provider field
438 return Fail;
439
440 result = quotedIdentifier( mFields.at( fieldIndex ).name() );
441
442 return Complete;
443 }
444
446 {
447 const QgsExpressionNodeInOperator *n = static_cast<const QgsExpressionNodeInOperator *>( node );
448 QStringList list;
449
450 Result inResult = Complete;
451 const auto constList = n->list()->list();
452 for ( const QgsExpressionNode *ln : constList )
453 {
454 QString s;
455 const Result r = compileNode( ln, s );
456 if ( r == Complete || r == Partial )
457 {
458 list << s;
459 if ( r == Partial )
460 inResult = Partial;
461 }
462 else
463 return r;
464 }
465
466 QString nd;
467 const Result rn = compileNode( n->node(), nd );
468 if ( rn != Complete && rn != Partial )
469 return rn;
470
471 result = u"%1 %2IN (%3)"_s.arg( nd, n->isNotIn() ? u"NOT "_s : QString(), list.join( ',' ) );
472 return ( inResult == Partial || rn == Partial ) ? Partial : Complete;
473 }
474
476 {
477 const QgsExpressionNodeFunction *n = static_cast<const QgsExpressionNodeFunction *>( node );
479
480 // get sql function to compile node expression
481 const QString nd = sqlFunctionFromFunctionName( fd->name() );
482 // if no sql function the node can't be compiled
483 if ( nd.isNull() )
484 return Fail;
485
486 // compile arguments
487 QStringList args;
488 Result inResult = Complete;
489 const auto constList = n->args()->list();
490 for ( const QgsExpressionNode *ln : constList )
491 {
492 QString s;
493 const Result r = compileNode( ln, s );
494 if ( r == Complete || r == Partial )
495 {
496 args << s;
497 if ( r == Partial )
498 inResult = Partial;
499 }
500 else
501 return r;
502 }
503
504 // update arguments to be adapted to SQL function
505 args = sqlArgumentsFromFunctionName( fd->name(), args );
506
507 // build result
508 result = !nd.isEmpty() ? u"%1(%2)"_s.arg( nd, args.join( ',' ) ) : args.join( ',' );
509 return inResult == Partial ? Partial : Complete;
510 }
511
513 break;
514
516 break;
517 }
518
519 return Fail;
520}
521
522QString QgsSqlExpressionCompiler::sqlFunctionFromFunctionName( const QString &fnName ) const
523{
524 Q_UNUSED( fnName )
525 return QString();
526}
527
528QStringList QgsSqlExpressionCompiler::sqlArgumentsFromFunctionName( const QString &fnName, const QStringList &fnArgs ) const
529{
530 Q_UNUSED( fnName )
531 return QStringList( fnArgs );
532}
533
534QString QgsSqlExpressionCompiler::castToReal( const QString &value ) const
535{
536 Q_UNUSED( value )
537 return QString();
538}
539
540QString QgsSqlExpressionCompiler::castToText( const QString &value ) const
541{
542 return value;
543}
544
545QString QgsSqlExpressionCompiler::castToInt( const QString &value ) const
546{
547 Q_UNUSED( value )
548 return QString();
549}
550
552{
553 if ( mIgnoreStaticNodes )
554 return Fail;
555
556 if ( node->hasCachedStaticValue() )
557 {
558 bool ok = false;
559 if ( mFlags.testFlag( CaseInsensitiveStringMatch ) && node->cachedStaticValue().userType() == QMetaType::Type::QString )
560 {
561 // provider uses case insensitive matching, so if literal was a string then we only have a Partial compilation and need to
562 // double check results using QGIS' expression engine
563 result = quotedValue( node->cachedStaticValue(), ok );
564 return ok ? Partial : Fail;
565 }
566 else
567 {
568 result = quotedValue( node->cachedStaticValue(), ok );
569 return ok ? Complete : Fail;
570 }
571 }
572 return Fail;
573}
574
575bool QgsSqlExpressionCompiler::nodeIsNullLiteral( const QgsExpressionNode *node ) const
576{
577 if ( node->nodeType() != QgsExpressionNode::ntLiteral )
578 return false;
579
580 const QgsExpressionNodeLiteral *nLit = static_cast<const QgsExpressionNodeLiteral *>( node );
581 return QgsVariantUtils::isNull( nLit->value() );
582}
An abstract base class for defining QgsExpression functions.
QString name() const
The name of the function.
SQL-like BETWEEN and NOT BETWEEN predicates.
bool negate() const
Returns true if the predicate is an exclusion test (NOT BETWEEN).
QgsExpressionNode * lowerBound() const
Returns the lower bound expression node of the range.
QgsExpressionNode * higherBound() const
Returns the higher bound expression node of the range.
QgsExpressionNode * node() const
Returns the expression node.
A binary expression operator, which operates on two values.
QgsExpressionNode * opLeft() const
Returns the node to the left of the operator.
QgsExpressionNode * opRight() const
Returns the node to the right of the operator.
QgsExpressionNodeBinaryOperator::BinaryOperator op() const
Returns the binary operator.
An expression node which takes its value from a feature's field.
QString name() const
The name of the column.
An expression node for expression functions.
int fnIndex() const
Returns the index of the node's function.
QgsExpressionNode::NodeList * args() const
Returns a list of arguments specified for the function.
An expression node for value IN or NOT IN clauses.
QgsExpressionNode * node() const
Returns the expression node.
QgsExpressionNode::NodeList * list() const
Returns the list of nodes to search for matching values within.
bool isNotIn() const
Returns true if this node is a "NOT IN" operator, or false if the node is a normal "IN" operator.
An expression node for literal values.
QVariant value() const
The value of the literal.
A unary node is either negative as in boolean (not) or as in numbers (minus).
QgsExpressionNodeUnaryOperator::UnaryOperator op() const
Returns the unary operator.
QgsExpressionNode * operand() const
Returns the node the operator will operate upon.
QList< QgsExpressionNode * > list()
Gets a list of all the nodes.
Abstract base class for all nodes that can appear in an expression.
bool hasCachedStaticValue() const
Returns true if the node can be replaced by a static cached value.
virtual QgsExpressionNode::NodeType nodeType() const =0
Gets the type of this node.
QVariant cachedStaticValue() const
Returns the node's static cached value.
NodeType
Known node types.
@ ntBetweenOperator
Between operator.
@ ntIndexOperator
Index operator.
Handles parsing and evaluation of expressions (formerly called "search strings").
static const QList< QgsExpressionFunction * > & Functions()
const QgsExpressionNode * rootNode() const
Returns the root node of the expression.
Container of fields for a vector layer.
Definition qgsfields.h:46
virtual Result compileNode(const QgsExpressionNode *node, QString &str)
Compiles an expression node and returns the result of the compilation.
virtual Result compile(const QgsExpression *exp)
Compiles an expression and returns the result of the compilation.
Result
Possible results from expression compilation.
@ Fail
Provider cannot handle expression.
@ Complete
Expression was successfully compiled and can be completely delegated to provider.
@ Partial
Expression was partially compiled, but provider will return extra records and results must be double-...
virtual QStringList sqlArgumentsFromFunctionName(const QString &fnName, const QStringList &fnArgs) const
Returns the Arguments for SQL function for the expression function.
virtual Result replaceNodeByStaticCachedValueIfPossible(const QgsExpressionNode *node, QString &str)
Tries to replace a node by its static cached value where possible.
virtual QString result()
Returns the compiled expression string for use by the provider.
virtual QString quotedValue(const QVariant &value, bool &ok)
Returns a quoted attribute value, in the format expected by the provider.
virtual QString castToText(const QString &value) const
Casts a value to a text result.
virtual QString castToInt(const QString &value) const
Casts a value to a integer result.
virtual QString quotedIdentifier(const QString &identifier)
Returns a quoted column identifier, in the format expected by the provider.
virtual QString sqlFunctionFromFunctionName(const QString &fnName) const
Returns the SQL function for the expression function.
QgsSqlExpressionCompiler(const QgsFields &fields, QgsSqlExpressionCompiler::Flags flags=Flags(), bool ignoreStaticNodes=false)
Constructor for expression compiler.
virtual QString castToReal(const QString &value) const
Casts a value to a real result.
bool opIsStringComparison(QgsExpressionNodeBinaryOperator::BinaryOperator op)
Returns true if op is one of.
@ LikeIsCaseInsensitive
Provider treats LIKE as case-insensitive.
@ NoUnaryMinus
Provider does not unary minus, e.g., " -( 100 * 2 ) = ...".
@ CaseInsensitiveStringMatch
Provider performs case-insensitive string matching for all strings.
@ NoNullInBooleanLogic
Provider does not support using NULL with boolean logic, e.g., "(...) OR NULL".
@ IntegerDivisionResultsInInteger
Dividing int by int results in int on provider. Subclass must implement the castToReal() function to ...
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
static bool isNumericType(QMetaType::Type metaType)
Returns true if the specified metaType is a numeric type.