Skip to content

Commit

Permalink
Support OR reduce to IN in expressions
Browse files Browse the repository at this point in the history
Fixes #43181 while we wait for upstream GDAL fix.
  • Loading branch information
elpaso authored and nyalldawson committed Jun 9, 2021
1 parent 85345ee commit c7425b9
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 0 deletions.
116 changes: 116 additions & 0 deletions src/core/expression/qgsexpression.cpp
Expand Up @@ -1192,6 +1192,122 @@ bool QgsExpression::attemptReduceToInClause( const QStringList &expressions, QSt
}
}
}
// Collect ORs
else if ( const QgsExpressionNodeBinaryOperator *orOp = dynamic_cast<const QgsExpressionNodeBinaryOperator *>( e.rootNode() ) )
{

// OR Collector function: returns a possibly empty list of the left and right operands of an OR expression
std::function<QStringList( QgsExpressionNode *, QgsExpressionNode * )> collectOrs = [ &collectOrs ]( QgsExpressionNode * opLeft, QgsExpressionNode * opRight ) -> QStringList
{
QStringList orParts;
if ( const QgsExpressionNodeBinaryOperator *leftOrOp = dynamic_cast<const QgsExpressionNodeBinaryOperator *>( opLeft ) )
{
if ( leftOrOp->op( ) == QgsExpressionNodeBinaryOperator::BinaryOperator::boOr )
{
orParts.append( collectOrs( leftOrOp->opLeft(), leftOrOp->opRight() ) );
}
else
{
orParts.append( leftOrOp->dump() );
}
}
else if ( const QgsExpressionNodeInOperator *leftInOp = dynamic_cast<const QgsExpressionNodeInOperator *>( opLeft ) )
{
orParts.append( leftInOp->dump() );
}
else
{
return {};
}

if ( const QgsExpressionNodeBinaryOperator *rightOrOp = dynamic_cast<const QgsExpressionNodeBinaryOperator *>( opRight ) )
{
if ( rightOrOp->op( ) == QgsExpressionNodeBinaryOperator::BinaryOperator::boOr )
{
orParts.append( collectOrs( rightOrOp->opLeft(), rightOrOp->opRight() ) );
}
else
{
orParts.append( rightOrOp->dump() );
}
}
else if ( const QgsExpressionNodeInOperator *rightInOp = dynamic_cast<const QgsExpressionNodeInOperator *>( opRight ) )
{
orParts.append( rightInOp->dump() );
}
else
{
return {};
}

return orParts;
};

if ( orOp->op( ) == QgsExpressionNodeBinaryOperator::BinaryOperator::boOr )
{
// Try to collect all OR conditions
const QStringList orParts = collectOrs( orOp->opLeft(), orOp->opRight() );
if ( orParts.isEmpty() )
{
return false;
}
else
{
QString orPartsResult;
if ( attemptReduceToInClause( orParts, orPartsResult ) )
{
// Need to check if the IN field is correct,
QgsExpression inExp { orPartsResult };
if ( ! inExp.rootNode() )
{
return false;
}

if ( const QgsExpressionNodeInOperator *inOpInner = dynamic_cast<const QgsExpressionNodeInOperator *>( inExp.rootNode() ) )
{
if ( inOpInner->node()->nodeType() != QgsExpressionNode::NodeType::ntColumnRef || inOpInner->node()->referencedColumns().size() < 1 )
{
return false;
}

const QString innerInfield { inOpInner->node()->referencedColumns().values().first() };

if ( first )
{
inField = innerInfield;
first = false;
}

if ( innerInfield != inField )
{
return false;
}
else
{
const auto constInnerValuesList { inOpInner->list()->list() };
for ( const auto &innerInValueNode : std::as_const( constInnerValuesList ) )
{
values.append( innerInValueNode->dump() );
}
}

}
else
{
return false;
}
}
else
{
return false;
}
}
}
else
{
return false;
}
}
else
{
return false;
Expand Down
6 changes: 6 additions & 0 deletions tests/src/core/testqgsexpression.cpp
Expand Up @@ -4171,6 +4171,12 @@ class TestQgsExpression: public QObject
QTest::addColumn<QStringList>( "input" );
QTest::addColumn<bool>( "expected" );
QTest::addColumn<QString>( "expression" );
QTest::newRow( "OR conditions mixed IN" ) << ( QStringList() << QStringLiteral( "field = 'value' OR field IN( 'value2', 'value3' )" ) ) << true << "field IN ('value','value2','value3')";
QTest::newRow( "OR conditions mixed IN reverse" ) << ( QStringList() << QStringLiteral( "field IN ('value','value2') OR field = 'value3'" ) ) << true << "field IN ('value','value2','value3')";
QTest::newRow( "OR conditions mixed IN different fields" ) << ( QStringList() << QStringLiteral( "field2 IN ('value','value2') OR field = 'value3'" ) ) << false << QString();
QTest::newRow( "OR conditions" ) << ( QStringList() << QStringLiteral( "field = 'value' OR field = 'value2'" ) ) << true << "field IN ('value','value2')";
QTest::newRow( "OR conditions different fields" ) << ( QStringList() << QStringLiteral( "field = 'value' OR field2 = 'value2'" ) ) << false << QString();
QTest::newRow( "OR conditions three" ) << ( QStringList() << QStringLiteral( "field = 'value' OR field = 'value2' OR field = 'value3' " ) ) << true << "field IN ('value','value2','value3')";
QTest::newRow( "empty" ) << QStringList() << false << QString();
QTest::newRow( "invalid" ) << ( QStringList() << QStringLiteral( "a=" ) ) << false << QString();
QTest::newRow( "not equality" ) << ( QStringList() << QStringLiteral( "field <> 'value'" ) ) << false << "field";
Expand Down

0 comments on commit c7425b9

Please sign in to comment.