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