QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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
34{
35 if ( exp->rootNode() )
36 return compileNode( exp->rootNode(), mResult );
37 else
38 return Fail;
39}
40
42{
43 return mResult;
44}
45
57
58QString QgsSqlExpressionCompiler::quotedIdentifier( const QString &identifier )
59{
60 QString quoted = identifier;
61 quoted.replace( '"', "\"\""_L1 );
62 quoted = quoted.prepend( '\"' ).append( '\"' );
63 return quoted;
64}
65
66QString QgsSqlExpressionCompiler::quotedValue( const QVariant &value, bool &ok )
67{
68 ok = true;
69
70 if ( QgsVariantUtils::isNull( value ) )
71 return u"NULL"_s;
72
73 switch ( value.userType() )
74 {
75 case QMetaType::Type::Int:
76 case QMetaType::Type::LongLong:
77 case QMetaType::Type::Double:
78 return value.toString();
79
80 case QMetaType::Type::Bool:
81 return value.toBool() ? u"TRUE"_s : u"FALSE"_s;
82
83 default:
84 case QMetaType::Type::QString:
85 QString v = value.toString();
86 v.replace( '\'', "''"_L1 );
87 if ( v.contains( '\\' ) )
88 return v.replace( '\\', "\\\\"_L1 ).prepend( "E'" ).append( '\'' );
89 else
90 return v.prepend( '\'' ).append( '\'' );
91 }
92}
93
95{
97 if ( staticRes != Fail )
98 return staticRes;
99
100 // This is just to identify the most simple cases where nodes are numeric
101 std::function<bool( const QgsExpressionNode * )> nodeIsNumeric;
102 nodeIsNumeric = [this, &nodeIsNumeric]( const QgsExpressionNode *node ) {
103 const QgsExpressionNode::NodeType nodeType { node->nodeType() };
104
105 switch ( nodeType )
106 {
108 {
109 const QgsExpressionNodeColumnRef *col = static_cast<const QgsExpressionNodeColumnRef *>( node );
110 const int idx = mFields.indexFromName( col->name() );
111 return idx >= 0 && QgsVariantUtils::isNumericType( mFields[idx].type() );
112 }
114 {
115 const QgsExpressionNodeLiteral *lit = static_cast<const QgsExpressionNodeLiteral *>( node );
116 return QgsVariantUtils::isNumericType( static_cast< QMetaType::Type >( lit->value().userType() ) );
117 }
119 {
120 const QgsExpressionNodeBinaryOperator *op = static_cast<const QgsExpressionNodeBinaryOperator *>( node );
121 return nodeIsNumeric( op->opLeft() ) && nodeIsNumeric( op->opRight() );
122 }
124 {
125 const QgsExpressionNodeUnaryOperator *op = static_cast<const QgsExpressionNodeUnaryOperator *>( node );
126 return nodeIsNumeric( op->operand() );
127 }
128
129 default:
130 return false;
131 }
132 };
133
134 switch ( node->nodeType() )
135 {
137 {
138 const QgsExpressionNodeUnaryOperator *n = static_cast<const QgsExpressionNodeUnaryOperator *>( node );
139 switch ( n->op() )
140 {
142 {
143 QString right;
144 if ( compileNode( n->operand(), right ) == Complete )
145 {
146 result = "( NOT " + right + ')';
147 return Complete;
148 }
149
150 return Fail;
151 }
152
154 {
155 if ( mFlags.testFlag( NoUnaryMinus ) )
156 return Fail;
157
158 QString right;
159 if ( compileNode( n->operand(), right ) == Complete )
160 {
161 result = "( - (" + right + "))";
162 return Complete;
163 }
164
165 return Fail;
166 }
167 }
168
169 break;
170 }
171
173 {
174 const QgsExpressionNodeBinaryOperator *n = static_cast<const QgsExpressionNodeBinaryOperator *>( node );
175
176 QString op;
177 bool partialCompilation = false;
178 bool failOnPartialNode = false;
179 switch ( n->op() )
180 {
183 {
184 // equality between column refs results in a partial compilation, since provider is performing
185 // case-insensitive matches between strings
186 partialCompilation = true;
187 }
188
189 op = u"="_s;
190 break;
191
193 op = u">="_s;
194 break;
195
197 op = u">"_s;
198 break;
199
201 op = u"<="_s;
202 break;
203
205 op = u"<"_s;
206 break;
207
209 op = u"IS"_s;
210 break;
211
213 op = u"IS NOT"_s;
214 failOnPartialNode = mFlags.testFlag( CaseInsensitiveStringMatch );
215 break;
216
218 op = u"LIKE"_s;
219 partialCompilation = mFlags.testFlag( LikeIsCaseInsensitive );
220 break;
221
223 if ( mFlags.testFlag( LikeIsCaseInsensitive ) )
224 op = u"LIKE"_s;
225 else
226 op = u"ILIKE"_s;
227 break;
228
230 op = u"NOT LIKE"_s;
231 partialCompilation = mFlags.testFlag( LikeIsCaseInsensitive );
232 failOnPartialNode = mFlags.testFlag( CaseInsensitiveStringMatch );
233 break;
234
236 failOnPartialNode = mFlags.testFlag( CaseInsensitiveStringMatch );
237 if ( mFlags.testFlag( LikeIsCaseInsensitive ) )
238 op = u"NOT LIKE"_s;
239 else
240 op = u"NOT ILIKE"_s;
241 break;
242
244 if ( mFlags.testFlag( NoNullInBooleanLogic ) )
245 {
246 if ( nodeIsNullLiteral( n->opLeft() ) || nodeIsNullLiteral( n->opRight() ) )
247 return Fail;
248 }
249
250 op = u"OR"_s;
251 break;
252
254 if ( mFlags.testFlag( NoNullInBooleanLogic ) )
255 {
256 if ( nodeIsNullLiteral( n->opLeft() ) || nodeIsNullLiteral( n->opRight() ) )
257 return Fail;
258 }
259
260 op = u"AND"_s;
261 break;
262
264 failOnPartialNode = mFlags.testFlag( CaseInsensitiveStringMatch );
265 op = u"<>"_s;
266 break;
267
269 op = u"*"_s;
270 break;
271
273 {
274 const QgsExpressionNodeBinaryOperator *nodeOp = static_cast<const QgsExpressionNodeBinaryOperator *>( node );
275 if ( nodeIsNumeric( nodeOp->opLeft() ) && nodeIsNumeric( nodeOp->opRight() ) )
276 {
277 op = u"+"_s;
278 }
279 break;
280 }
281
283 op = u"-"_s;
284 break;
285
287 op = u"/"_s;
288 break;
289
291 op = u"%"_s;
292 break;
293
295 op = u"||"_s;
296 break;
297
299 op = u"/"_s;
300 break;
301
303 op = u"^"_s;
304 break;
305
307 op = u"~"_s;
308 break;
309 }
310
311 if ( op.isNull() )
312 return Fail;
313
314 QString left;
315 const Result lr( compileNode( n->opLeft(), left ) );
316
317 if ( opIsStringComparison( n->op() ) )
318 left = castToText( left );
319
320 QString right;
321 const Result rr( compileNode( n->opRight(), right ) );
322
323 if ( failOnPartialNode && ( lr == Partial || rr == Partial ) )
324 return Fail;
325
327 {
328 right = castToReal( right );
329 if ( right.isEmpty() )
330 {
331 // not supported
332 return Fail;
333 }
334 }
335
336 result = '(' + left + ' ' + op + ' ' + right + ')';
338 {
340 if ( result.isEmpty() )
341 {
342 // not supported
343 return Fail;
344 }
345 }
346
347 if ( lr == Complete && rr == Complete )
348 return ( partialCompilation ? Partial : Complete );
349 else if ( ( lr == Partial && rr == Complete ) || ( lr == Complete && rr == Partial ) || ( lr == Partial && rr == Partial ) )
350 return Partial;
351 else
352 return Fail;
353 }
354
356 {
357 const QgsExpressionNodeBetweenOperator *n = static_cast<const QgsExpressionNodeBetweenOperator *>( node );
358 QString res;
359 Result betweenResult = Complete;
360
361 const Result rn = compileNode( n->node(), res );
362 if ( rn == Complete || rn == Partial )
363 {
364 if ( rn == Partial )
365 {
366 betweenResult = Partial;
367 }
368 }
369 else
370 {
371 return rn;
372 }
373
374 QString s;
375 const Result rl = compileNode( n->lowerBound(), s );
376 if ( rl == Complete || rl == Partial )
377 {
378 if ( rl == Partial )
379 {
380 betweenResult = Partial;
381 }
382 }
383 else
384 {
385 return rl;
386 }
387
388 res.append( n->negate() ? u" NOT BETWEEN %1"_s.arg( s ) : u" BETWEEN %1"_s.arg( s ) );
389
390 const Result rh = compileNode( n->higherBound(), s );
391 if ( rh == Complete || rh == Partial )
392 {
393 if ( rh == Partial )
394 {
395 betweenResult = Partial;
396 }
397 }
398 else
399 {
400 return rh;
401 }
402
403 res.append( u" AND %1"_s.arg( s ) );
404 result = res;
405 return betweenResult;
406 }
407
409 {
410 const QgsExpressionNodeLiteral *n = static_cast<const QgsExpressionNodeLiteral *>( node );
411 bool ok = false;
412 if ( mFlags.testFlag( CaseInsensitiveStringMatch ) && n->value().userType() == QMetaType::Type::QString )
413 {
414 // provider uses case insensitive matching, so if literal was a string then we only have a Partial compilation and need to
415 // double check results using QGIS' expression engine
416 result = quotedValue( n->value(), ok );
417 return ok ? Partial : Fail;
418 }
419 else
420 {
421 result = quotedValue( n->value(), ok );
422 return ok ? Complete : Fail;
423 }
424 }
425
427 {
428 const QgsExpressionNodeColumnRef *n = static_cast<const QgsExpressionNodeColumnRef *>( node );
429
430 // QGIS expressions don't care about case sensitive field naming, so we match case insensitively here to the
431 // layer's fields and then retrieve the actual case of the field name for use in the compilation
432 const int fieldIndex = mFields.lookupField( n->name() );
433 if ( fieldIndex == -1 )
434 // Not a provider field
435 return Fail;
436
437 result = quotedIdentifier( mFields.at( fieldIndex ).name() );
438
439 return Complete;
440 }
441
443 {
444 const QgsExpressionNodeInOperator *n = static_cast<const QgsExpressionNodeInOperator *>( node );
445 QStringList list;
446
447 Result inResult = Complete;
448 const auto constList = n->list()->list();
449 for ( const QgsExpressionNode *ln : constList )
450 {
451 QString s;
452 const Result r = compileNode( ln, s );
453 if ( r == Complete || r == Partial )
454 {
455 list << s;
456 if ( r == Partial )
457 inResult = Partial;
458 }
459 else
460 return r;
461 }
462
463 QString nd;
464 const Result rn = compileNode( n->node(), nd );
465 if ( rn != Complete && rn != Partial )
466 return rn;
467
468 result = u"%1 %2IN (%3)"_s.arg( nd, n->isNotIn() ? u"NOT "_s : QString(), list.join( ',' ) );
469 return ( inResult == Partial || rn == Partial ) ? Partial : Complete;
470 }
471
473 {
474 const QgsExpressionNodeFunction *n = static_cast<const QgsExpressionNodeFunction *>( node );
476
477 // get sql function to compile node expression
478 const QString nd = sqlFunctionFromFunctionName( fd->name() );
479 // if no sql function the node can't be compiled
480 if ( nd.isNull() )
481 return Fail;
482
483 // compile arguments
484 QStringList args;
485 Result inResult = Complete;
486 const auto constList = n->args()->list();
487 for ( const QgsExpressionNode *ln : constList )
488 {
489 QString s;
490 const Result r = compileNode( ln, s );
491 if ( r == Complete || r == Partial )
492 {
493 args << s;
494 if ( r == Partial )
495 inResult = Partial;
496 }
497 else
498 return r;
499 }
500
501 // update arguments to be adapted to SQL function
502 args = sqlArgumentsFromFunctionName( fd->name(), args );
503
504 // build result
505 result = !nd.isEmpty() ? u"%1(%2)"_s.arg( nd, args.join( ',' ) ) : args.join( ',' );
506 return inResult == Partial ? Partial : Complete;
507 }
508
510 break;
511
513 break;
514 }
515
516 return Fail;
517}
518
519QString QgsSqlExpressionCompiler::sqlFunctionFromFunctionName( const QString &fnName ) const
520{
521 Q_UNUSED( fnName )
522 return QString();
523}
524
525QStringList QgsSqlExpressionCompiler::sqlArgumentsFromFunctionName( const QString &fnName, const QStringList &fnArgs ) const
526{
527 Q_UNUSED( fnName )
528 return QStringList( fnArgs );
529}
530
531QString QgsSqlExpressionCompiler::castToReal( const QString &value ) const
532{
533 Q_UNUSED( value )
534 return QString();
535}
536
537QString QgsSqlExpressionCompiler::castToText( const QString &value ) const
538{
539 return value;
540}
541
542QString QgsSqlExpressionCompiler::castToInt( const QString &value ) const
543{
544 Q_UNUSED( value )
545 return QString();
546}
547
549{
550 if ( mIgnoreStaticNodes )
551 return Fail;
552
553 if ( node->hasCachedStaticValue() )
554 {
555 bool ok = false;
556 if ( mFlags.testFlag( CaseInsensitiveStringMatch ) && node->cachedStaticValue().userType() == QMetaType::Type::QString )
557 {
558 // provider uses case insensitive matching, so if literal was a string then we only have a Partial compilation and need to
559 // double check results using QGIS' expression engine
560 result = quotedValue( node->cachedStaticValue(), ok );
561 return ok ? Partial : Fail;
562 }
563 else
564 {
565 result = quotedValue( node->cachedStaticValue(), ok );
566 return ok ? Complete : Fail;
567 }
568 }
569 return Fail;
570}
571
572bool QgsSqlExpressionCompiler::nodeIsNullLiteral( const QgsExpressionNode *node ) const
573{
574 if ( node->nodeType() != QgsExpressionNode::ntLiteral )
575 return false;
576
577 const QgsExpressionNodeLiteral *nLit = static_cast<const QgsExpressionNodeLiteral *>( node );
578 return QgsVariantUtils::isNull( nLit->value() );
579}
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.