Branch data Line data Source code
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 : : 16 : : #include "qgssqlexpressioncompiler.h" 17 : : #include "qgsexpressionnodeimpl.h" 18 : : #include "qgsexpressionfunction.h" 19 : : #include "qgsexpression.h" 20 : : 21 : 0 : QgsSqlExpressionCompiler::QgsSqlExpressionCompiler( const QgsFields &fields, Flags flags, bool ignoreStaticNodes ) 22 : 0 : : mFields( fields ) 23 : 0 : , mFlags( flags ) 24 : 0 : , mIgnoreStaticNodes( ignoreStaticNodes ) 25 : 0 : { 26 : 0 : } 27 : : 28 : 0 : QgsSqlExpressionCompiler::Result QgsSqlExpressionCompiler::compile( const QgsExpression *exp ) 29 : : { 30 : 0 : if ( exp->rootNode() ) 31 : 0 : return compileNode( exp->rootNode(), mResult ); 32 : : else 33 : 0 : return Fail; 34 : 0 : } 35 : : 36 : 0 : QString QgsSqlExpressionCompiler::result() 37 : : { 38 : 0 : return mResult; 39 : : } 40 : : 41 : 0 : bool QgsSqlExpressionCompiler::opIsStringComparison( QgsExpressionNodeBinaryOperator::BinaryOperator op ) 42 : : { 43 : 0 : if ( op == QgsExpressionNodeBinaryOperator::BinaryOperator::boILike || 44 : 0 : op == QgsExpressionNodeBinaryOperator::BinaryOperator::boLike || 45 : 0 : op == QgsExpressionNodeBinaryOperator::BinaryOperator::boNotILike || 46 : 0 : op == QgsExpressionNodeBinaryOperator::BinaryOperator::boNotLike || 47 : 0 : op == QgsExpressionNodeBinaryOperator::BinaryOperator::boRegexp ) 48 : 0 : return true; 49 : : else 50 : 0 : return false; 51 : 0 : } 52 : : 53 : 0 : QString QgsSqlExpressionCompiler::quotedIdentifier( const QString &identifier ) 54 : : { 55 : 0 : QString quoted = identifier; 56 : 0 : quoted.replace( '"', QLatin1String( "\"\"" ) ); 57 : 0 : quoted = quoted.prepend( '\"' ).append( '\"' ); 58 : 0 : return quoted; 59 : 0 : } 60 : : 61 : 0 : QString QgsSqlExpressionCompiler::quotedValue( const QVariant &value, bool &ok ) 62 : : { 63 : 0 : ok = true; 64 : : 65 : 0 : if ( value.isNull() ) 66 : 0 : return QStringLiteral( "NULL" ); 67 : : 68 : 0 : switch ( value.type() ) 69 : : { 70 : : case QVariant::Int: 71 : : case QVariant::LongLong: 72 : : case QVariant::Double: 73 : 0 : return value.toString(); 74 : : 75 : : case QVariant::Bool: 76 : 0 : return value.toBool() ? QStringLiteral( "TRUE" ) : QStringLiteral( "FALSE" ); 77 : : 78 : : default: 79 : : case QVariant::String: 80 : 0 : QString v = value.toString(); 81 : 0 : v.replace( '\'', QLatin1String( "''" ) ); 82 : 0 : if ( v.contains( '\\' ) ) 83 : 0 : return v.replace( '\\', QLatin1String( "\\\\" ) ).prepend( "E'" ).append( '\'' ); 84 : : else 85 : 0 : return v.prepend( '\'' ).append( '\'' ); 86 : 0 : } 87 : 0 : } 88 : : 89 : 0 : QgsSqlExpressionCompiler::Result QgsSqlExpressionCompiler::compileNode( const QgsExpressionNode *node, QString &result ) 90 : : { 91 : 0 : QgsSqlExpressionCompiler::Result staticRes = replaceNodeByStaticCachedValueIfPossible( node, result ); 92 : 0 : if ( staticRes != Fail ) 93 : 0 : return staticRes; 94 : : 95 : 0 : switch ( node->nodeType() ) 96 : : { 97 : : case QgsExpressionNode::ntUnaryOperator: 98 : : { 99 : 0 : const QgsExpressionNodeUnaryOperator *n = static_cast<const QgsExpressionNodeUnaryOperator *>( node ); 100 : 0 : switch ( n->op() ) 101 : : { 102 : : case QgsExpressionNodeUnaryOperator::uoNot: 103 : : { 104 : 0 : QString right; 105 : 0 : if ( compileNode( n->operand(), right ) == Complete ) 106 : : { 107 : 0 : result = "( NOT " + right + ')'; 108 : 0 : return Complete; 109 : : } 110 : : 111 : 0 : return Fail; 112 : 0 : } 113 : : 114 : : case QgsExpressionNodeUnaryOperator::uoMinus: 115 : : { 116 : 0 : if ( mFlags.testFlag( NoUnaryMinus ) ) 117 : 0 : return Fail; 118 : : 119 : 0 : QString right; 120 : 0 : if ( compileNode( n->operand(), right ) == Complete ) 121 : : { 122 : 0 : result = "( - (" + right + "))"; 123 : 0 : return Complete; 124 : : } 125 : : 126 : 0 : return Fail; 127 : 0 : } 128 : : } 129 : : 130 : 0 : break; 131 : : } 132 : : 133 : : case QgsExpressionNodeBinaryOperator::ntBinaryOperator: 134 : : { 135 : 0 : const QgsExpressionNodeBinaryOperator *n = static_cast<const QgsExpressionNodeBinaryOperator *>( node ); 136 : : 137 : 0 : QString op; 138 : 0 : bool partialCompilation = false; 139 : 0 : bool failOnPartialNode = false; 140 : 0 : switch ( n->op() ) 141 : : { 142 : : case QgsExpressionNodeBinaryOperator::boEQ: 143 : 0 : if ( mFlags.testFlag( CaseInsensitiveStringMatch ) && n->opLeft()->nodeType() == QgsExpressionNode::ntColumnRef && n->opRight()->nodeType() == QgsExpressionNode::ntColumnRef ) 144 : : { 145 : : // equality between column refs results in a partial compilation, since provider is performing 146 : : // case-insensitive matches between strings 147 : 0 : partialCompilation = true; 148 : 0 : } 149 : : 150 : 0 : op = QStringLiteral( "=" ); 151 : 0 : break; 152 : : 153 : : case QgsExpressionNodeBinaryOperator::boGE: 154 : 0 : op = QStringLiteral( ">=" ); 155 : 0 : break; 156 : : 157 : : case QgsExpressionNodeBinaryOperator::boGT: 158 : 0 : op = QStringLiteral( ">" ); 159 : 0 : break; 160 : : 161 : : case QgsExpressionNodeBinaryOperator::boLE: 162 : 0 : op = QStringLiteral( "<=" ); 163 : 0 : break; 164 : : 165 : : case QgsExpressionNodeBinaryOperator::boLT: 166 : 0 : op = QStringLiteral( "<" ); 167 : 0 : break; 168 : : 169 : : case QgsExpressionNodeBinaryOperator::boIs: 170 : 0 : op = QStringLiteral( "IS" ); 171 : 0 : break; 172 : : 173 : : case QgsExpressionNodeBinaryOperator::boIsNot: 174 : 0 : op = QStringLiteral( "IS NOT" ); 175 : 0 : failOnPartialNode = mFlags.testFlag( CaseInsensitiveStringMatch ); 176 : 0 : break; 177 : : 178 : : case QgsExpressionNodeBinaryOperator::boLike: 179 : 0 : op = QStringLiteral( "LIKE" ); 180 : 0 : partialCompilation = mFlags.testFlag( LikeIsCaseInsensitive ); 181 : 0 : break; 182 : : 183 : : case QgsExpressionNodeBinaryOperator::boILike: 184 : 0 : if ( mFlags.testFlag( LikeIsCaseInsensitive ) ) 185 : 0 : op = QStringLiteral( "LIKE" ); 186 : : else 187 : 0 : op = QStringLiteral( "ILIKE" ); 188 : 0 : break; 189 : : 190 : : case QgsExpressionNodeBinaryOperator::boNotLike: 191 : 0 : op = QStringLiteral( "NOT LIKE" ); 192 : 0 : partialCompilation = mFlags.testFlag( LikeIsCaseInsensitive ); 193 : 0 : failOnPartialNode = mFlags.testFlag( CaseInsensitiveStringMatch ); 194 : 0 : break; 195 : : 196 : : case QgsExpressionNodeBinaryOperator::boNotILike: 197 : 0 : failOnPartialNode = mFlags.testFlag( CaseInsensitiveStringMatch ); 198 : 0 : if ( mFlags.testFlag( LikeIsCaseInsensitive ) ) 199 : 0 : op = QStringLiteral( "NOT LIKE" ); 200 : : else 201 : 0 : op = QStringLiteral( "NOT ILIKE" ); 202 : 0 : break; 203 : : 204 : : case QgsExpressionNodeBinaryOperator::boOr: 205 : 0 : if ( mFlags.testFlag( NoNullInBooleanLogic ) ) 206 : : { 207 : 0 : if ( nodeIsNullLiteral( n->opLeft() ) || nodeIsNullLiteral( n->opRight() ) ) 208 : 0 : return Fail; 209 : 0 : } 210 : : 211 : 0 : op = QStringLiteral( "OR" ); 212 : 0 : break; 213 : : 214 : : case QgsExpressionNodeBinaryOperator::boAnd: 215 : 0 : if ( mFlags.testFlag( NoNullInBooleanLogic ) ) 216 : : { 217 : 0 : if ( nodeIsNullLiteral( n->opLeft() ) || nodeIsNullLiteral( n->opRight() ) ) 218 : 0 : return Fail; 219 : 0 : } 220 : : 221 : 0 : op = QStringLiteral( "AND" ); 222 : 0 : break; 223 : : 224 : : case QgsExpressionNodeBinaryOperator::boNE: 225 : 0 : failOnPartialNode = mFlags.testFlag( CaseInsensitiveStringMatch ); 226 : 0 : op = QStringLiteral( "<>" ); 227 : 0 : break; 228 : : 229 : : case QgsExpressionNodeBinaryOperator::boMul: 230 : 0 : op = QStringLiteral( "*" ); 231 : 0 : break; 232 : : 233 : : case QgsExpressionNodeBinaryOperator::boPlus: 234 : 0 : op = QStringLiteral( "+" ); 235 : 0 : break; 236 : : 237 : : case QgsExpressionNodeBinaryOperator::boMinus: 238 : 0 : op = QStringLiteral( "-" ); 239 : 0 : break; 240 : : 241 : : case QgsExpressionNodeBinaryOperator::boDiv: 242 : 0 : op = QStringLiteral( "/" ); 243 : 0 : break; 244 : : 245 : : case QgsExpressionNodeBinaryOperator::boMod: 246 : 0 : op = QStringLiteral( "%" ); 247 : 0 : break; 248 : : 249 : : case QgsExpressionNodeBinaryOperator::boConcat: 250 : 0 : op = QStringLiteral( "||" ); 251 : 0 : break; 252 : : 253 : : case QgsExpressionNodeBinaryOperator::boIntDiv: 254 : 0 : op = QStringLiteral( "/" ); 255 : 0 : break; 256 : : 257 : : case QgsExpressionNodeBinaryOperator::boPow: 258 : 0 : op = QStringLiteral( "^" ); 259 : 0 : break; 260 : : 261 : : case QgsExpressionNodeBinaryOperator::boRegexp: 262 : 0 : op = QStringLiteral( "~" ); 263 : 0 : break; 264 : : } 265 : : 266 : 0 : if ( op.isNull() ) 267 : 0 : return Fail; 268 : : 269 : 0 : QString left; 270 : 0 : Result lr( compileNode( n->opLeft(), left ) ); 271 : : 272 : 0 : if ( opIsStringComparison( n ->op() ) ) 273 : 0 : left = castToText( left ); 274 : : 275 : 0 : QString right; 276 : 0 : Result rr( compileNode( n->opRight(), right ) ); 277 : : 278 : 0 : if ( failOnPartialNode && ( lr == Partial || rr == Partial ) ) 279 : 0 : return Fail; 280 : : 281 : 0 : if ( n->op() == QgsExpressionNodeBinaryOperator::boDiv && mFlags.testFlag( IntegerDivisionResultsInInteger ) ) 282 : : { 283 : 0 : right = castToReal( right ); 284 : 0 : if ( right.isEmpty() ) 285 : : { 286 : : // not supported 287 : 0 : return Fail; 288 : : } 289 : 0 : } 290 : : 291 : 0 : result = '(' + left + ' ' + op + ' ' + right + ')'; 292 : 0 : if ( n->op() == QgsExpressionNodeBinaryOperator::boIntDiv ) 293 : : { 294 : 0 : result = castToInt( result ); 295 : 0 : if ( result.isEmpty() ) 296 : : { 297 : : // not supported 298 : 0 : return Fail; 299 : : } 300 : 0 : } 301 : : 302 : 0 : if ( lr == Complete && rr == Complete ) 303 : 0 : return ( partialCompilation ? Partial : Complete ); 304 : 0 : else if ( ( lr == Partial && rr == Complete ) || ( lr == Complete && rr == Partial ) || ( lr == Partial && rr == Partial ) ) 305 : 0 : return Partial; 306 : : else 307 : 0 : return Fail; 308 : 0 : } 309 : : 310 : : case QgsExpressionNode::ntLiteral: 311 : : { 312 : 0 : const QgsExpressionNodeLiteral *n = static_cast<const QgsExpressionNodeLiteral *>( node ); 313 : 0 : bool ok = false; 314 : 0 : if ( mFlags.testFlag( CaseInsensitiveStringMatch ) && n->value().type() == QVariant::String ) 315 : : { 316 : : // provider uses case insensitive matching, so if literal was a string then we only have a Partial compilation and need to 317 : : // double check results using QGIS' expression engine 318 : 0 : result = quotedValue( n->value(), ok ); 319 : 0 : return ok ? Partial : Fail; 320 : : } 321 : : else 322 : : { 323 : 0 : result = quotedValue( n->value(), ok ); 324 : 0 : return ok ? Complete : Fail; 325 : : } 326 : : } 327 : : 328 : : case QgsExpressionNode::ntColumnRef: 329 : : { 330 : 0 : const QgsExpressionNodeColumnRef *n = static_cast<const QgsExpressionNodeColumnRef *>( node ); 331 : : 332 : : // QGIS expressions don't care about case sensitive field naming, so we match case insensitively here to the 333 : : // layer's fields and then retrieve the actual case of the field name for use in the compilation 334 : 0 : const int fieldIndex = mFields.lookupField( n->name() ); 335 : 0 : if ( fieldIndex == -1 ) 336 : : // Not a provider field 337 : 0 : return Fail; 338 : : 339 : 0 : result = quotedIdentifier( mFields.at( fieldIndex ).name() ); 340 : : 341 : 0 : return Complete; 342 : : } 343 : : 344 : : case QgsExpressionNode::ntInOperator: 345 : : { 346 : 0 : const QgsExpressionNodeInOperator *n = static_cast<const QgsExpressionNodeInOperator *>( node ); 347 : 0 : QStringList list; 348 : : 349 : 0 : Result inResult = Complete; 350 : 0 : const auto constList = n->list()->list(); 351 : 0 : for ( const QgsExpressionNode *ln : constList ) 352 : : { 353 : 0 : QString s; 354 : 0 : Result r = compileNode( ln, s ); 355 : 0 : if ( r == Complete || r == Partial ) 356 : : { 357 : 0 : list << s; 358 : 0 : if ( r == Partial ) 359 : 0 : inResult = Partial; 360 : 0 : } 361 : : else 362 : 0 : return r; 363 : 0 : } 364 : : 365 : 0 : QString nd; 366 : 0 : Result rn = compileNode( n->node(), nd ); 367 : 0 : if ( rn != Complete && rn != Partial ) 368 : 0 : return rn; 369 : : 370 : 0 : result = QStringLiteral( "%1 %2IN (%3)" ).arg( nd, n->isNotIn() ? QStringLiteral( "NOT " ) : QString(), list.join( ',' ) ); 371 : 0 : return ( inResult == Partial || rn == Partial ) ? Partial : Complete; 372 : 0 : } 373 : : 374 : : case QgsExpressionNode::ntFunction: 375 : : { 376 : 0 : const QgsExpressionNodeFunction *n = static_cast<const QgsExpressionNodeFunction *>( node ); 377 : 0 : QgsExpressionFunction *fd = QgsExpression::Functions()[n->fnIndex()]; 378 : : 379 : : // get sql function to compile node expression 380 : 0 : QString nd = sqlFunctionFromFunctionName( fd->name() ); 381 : : // if no sql function the node can't be compiled 382 : 0 : if ( nd.isNull() ) 383 : 0 : return Fail; 384 : : 385 : : // compile arguments 386 : 0 : QStringList args; 387 : 0 : Result inResult = Complete; 388 : 0 : const auto constList = n->args()->list(); 389 : 0 : for ( const QgsExpressionNode *ln : constList ) 390 : : { 391 : 0 : QString s; 392 : 0 : Result r = compileNode( ln, s ); 393 : 0 : if ( r == Complete || r == Partial ) 394 : : { 395 : 0 : args << s; 396 : 0 : if ( r == Partial ) 397 : 0 : inResult = Partial; 398 : 0 : } 399 : : else 400 : 0 : return r; 401 : 0 : } 402 : : 403 : : // update arguments to be adapted to SQL function 404 : 0 : args = sqlArgumentsFromFunctionName( fd->name(), args ); 405 : : 406 : : // build result 407 : 0 : result = !nd.isEmpty() ? QStringLiteral( "%1(%2)" ).arg( nd, args.join( ',' ) ) : args.join( ',' ); 408 : 0 : return inResult == Partial ? Partial : Complete; 409 : 0 : } 410 : : 411 : : case QgsExpressionNode::ntCondition: 412 : 0 : break; 413 : : 414 : : case QgsExpressionNode::ntIndexOperator: 415 : 0 : break; 416 : : } 417 : : 418 : 0 : return Fail; 419 : 0 : } 420 : : 421 : 0 : QString QgsSqlExpressionCompiler::sqlFunctionFromFunctionName( const QString &fnName ) const 422 : : { 423 : 0 : Q_UNUSED( fnName ) 424 : 0 : return QString(); 425 : : } 426 : : 427 : 0 : QStringList QgsSqlExpressionCompiler::sqlArgumentsFromFunctionName( const QString &fnName, const QStringList &fnArgs ) const 428 : : { 429 : 0 : Q_UNUSED( fnName ) 430 : 0 : return QStringList( fnArgs ); 431 : : } 432 : : 433 : 0 : QString QgsSqlExpressionCompiler::castToReal( const QString &value ) const 434 : : { 435 : 0 : Q_UNUSED( value ) 436 : 0 : return QString(); 437 : : } 438 : : 439 : 0 : QString QgsSqlExpressionCompiler::castToText( const QString &value ) const 440 : : { 441 : 0 : return value; 442 : : } 443 : : 444 : 0 : QString QgsSqlExpressionCompiler::castToInt( const QString &value ) const 445 : : { 446 : 0 : Q_UNUSED( value ) 447 : 0 : return QString(); 448 : : } 449 : : 450 : 0 : QgsSqlExpressionCompiler::Result QgsSqlExpressionCompiler::replaceNodeByStaticCachedValueIfPossible( const QgsExpressionNode *node, QString &result ) 451 : : { 452 : 0 : if ( mIgnoreStaticNodes ) 453 : 0 : return Fail; 454 : : 455 : 0 : if ( node->hasCachedStaticValue() ) 456 : : { 457 : 0 : bool ok = false; 458 : 0 : if ( mFlags.testFlag( CaseInsensitiveStringMatch ) && node->cachedStaticValue().type() == QVariant::String ) 459 : : { 460 : : // provider uses case insensitive matching, so if literal was a string then we only have a Partial compilation and need to 461 : : // double check results using QGIS' expression engine 462 : 0 : result = quotedValue( node->cachedStaticValue(), ok ); 463 : 0 : return ok ? Partial : Fail; 464 : : } 465 : : else 466 : : { 467 : 0 : result = quotedValue( node->cachedStaticValue(), ok ); 468 : 0 : return ok ? Complete : Fail; 469 : : } 470 : : } 471 : 0 : return Fail; 472 : 0 : } 473 : : 474 : 0 : bool QgsSqlExpressionCompiler::nodeIsNullLiteral( const QgsExpressionNode *node ) const 475 : : { 476 : 0 : if ( node->nodeType() != QgsExpressionNode::ntLiteral ) 477 : 0 : return false; 478 : : 479 : 0 : const QgsExpressionNodeLiteral *nLit = static_cast<const QgsExpressionNodeLiteral *>( node ); 480 : 0 : return nLit->value().isNull(); 481 : 0 : }