Skip to content

Gh 5286 leftjoin skip rhs execution #5318

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
package org.eclipse.rdf4j.query.algebra.evaluation;

import java.util.function.Function;
import java.util.function.Predicate;

import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.QueryEvaluationException;
import org.eclipse.rdf4j.query.algebra.ValueConstant;
import org.eclipse.rdf4j.query.algebra.ValueExpr;
import org.eclipse.rdf4j.query.algebra.evaluation.util.QueryEvaluationUtility;

/**
* A step in the query evaluation that works on ValueExpresions.
Expand All @@ -26,6 +28,18 @@ public interface QueryValueEvaluationStep {
Value evaluate(BindingSet bindings)
throws QueryEvaluationException;

default Predicate<BindingSet> asPredicate() {
return bs -> {
try {
Value value = evaluate(bs);
return QueryEvaluationUtility.getEffectiveBooleanValue(value).orElse(false);
} catch (ValueExprEvaluationException e) {
// Ignore, condition not evaluated successfully
return false;
}
};
}

/**
* If an value expression results in a constant then it may be executed once per query invocation. This can reduce
* computation time significantly.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@
import org.eclipse.rdf4j.query.algebra.evaluation.QueryEvaluationStep;
import org.eclipse.rdf4j.query.algebra.evaluation.QueryValueEvaluationStep;
import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext;
import org.eclipse.rdf4j.query.algebra.evaluation.iterator.BadlyDesignedLeftJoinIterator;
import org.eclipse.rdf4j.query.algebra.evaluation.iterator.HashJoinIteration;
import org.eclipse.rdf4j.query.algebra.evaluation.iterator.LeftJoinIterator;
import org.eclipse.rdf4j.query.algebra.evaluation.impl.evaluationsteps.values.ScopedQueryValueEvaluationStep;
import org.eclipse.rdf4j.query.algebra.evaluation.iterator.*;
import org.eclipse.rdf4j.query.algebra.helpers.TupleExprs;
import org.eclipse.rdf4j.query.algebra.helpers.collectors.VarNameCollector;

Expand All @@ -32,6 +31,7 @@ public final class LeftJoinQueryEvaluationStep implements QueryEvaluationStep {
private final QueryEvaluationStep left;
private final LeftJoin leftJoin;
private final Set<String> optionalVars;
private final QueryEvaluationStep wellDesignedRightEvaluationStep;

public static QueryEvaluationStep supply(EvaluationStrategy strategy, LeftJoin leftJoin,
QueryEvaluationContext context) {
Expand Down Expand Up @@ -85,7 +85,11 @@ public LeftJoinQueryEvaluationStep(QueryEvaluationStep right, QueryValueEvaluati
}

this.optionalVars = optionalVars;

this.wellDesignedRightEvaluationStep = determineRightEvaluationStep(
leftJoin,
right,
condition,
leftJoin.getBindingNames());
}

@Override
Expand All @@ -103,13 +107,37 @@ public CloseableIteration<BindingSet> evaluate(BindingSet bindings) {
if (containsNone) {
// left join is "well designed"
leftJoin.setAlgorithm(LeftJoinIterator.class.getSimpleName());
return LeftJoinIterator.getInstance(left, right, condition, bindings, leftJoin.getBindingNames());
return LeftJoinIterator.getInstance(left, bindings, wellDesignedRightEvaluationStep);
} else {
Set<String> problemVars = new HashSet<>(optionalVars);
problemVars.retainAll(bindings.getBindingNames());

leftJoin.setAlgorithm(BadlyDesignedLeftJoinIterator.class.getSimpleName());
return new BadlyDesignedLeftJoinIterator(left, right, condition, bindings, problemVars);
var rightEvaluationStep = determineRightEvaluationStep(leftJoin, right, condition, problemVars);
return new BadlyDesignedLeftJoinIterator(left, bindings, problemVars, rightEvaluationStep);
}
}

public static QueryEvaluationStep determineRightEvaluationStep(
LeftJoin join,
QueryEvaluationStep prepareRightArg,
QueryValueEvaluationStep joinCondition,
Set<String> scopeBindingNames) {
if (joinCondition == null) {
return prepareRightArg;
} else if (canEvaluateConditionBasedOnLeftHandSide(join)) {
return new PreFilterQueryEvaluationStep(
prepareRightArg,
new ScopedQueryValueEvaluationStep(join.getAssuredBindingNames(), joinCondition));
} else {
return new PostFilterQueryEvaluationStep(
prepareRightArg,
new ScopedQueryValueEvaluationStep(scopeBindingNames, joinCondition));
}
}

private static boolean canEvaluateConditionBasedOnLeftHandSide(LeftJoin leftJoin) {
var varNames = VarNameCollector.process(leftJoin.getCondition());
return leftJoin.getAssuredBindingNames().containsAll(varNames);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*******************************************************************************
* Copyright (c) 2025 Eclipse RDF4J contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*******************************************************************************/
package org.eclipse.rdf4j.query.algebra.evaluation.impl.evaluationsteps;

import java.util.function.Predicate;

import org.eclipse.rdf4j.common.iteration.CloseableIteration;
import org.eclipse.rdf4j.common.iteration.FilterIteration;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.algebra.evaluation.QueryEvaluationStep;
import org.eclipse.rdf4j.query.algebra.evaluation.QueryValueEvaluationStep;

public class PostFilterQueryEvaluationStep implements QueryEvaluationStep {

private final QueryEvaluationStep wrapped;
private final Predicate<BindingSet> condition;

public PostFilterQueryEvaluationStep(QueryEvaluationStep wrapped,
QueryValueEvaluationStep condition) {
this.wrapped = wrapped;
this.condition = condition.asPredicate();
}

@Override
public CloseableIteration<BindingSet> evaluate(BindingSet leftBindings) {
var rightIteration = wrapped.evaluate(leftBindings);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for myself: did we start using var in the code base?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with var. I've started using it where it makes sense. Best use case for me so far has been for connections or iterators that are wrapped in a try-with-resource, much easier to read.


if (rightIteration == QueryEvaluationStep.EMPTY_ITERATION) {
return rightIteration;
}

return new FilterIteration<>(rightIteration) {

@Override
protected boolean accept(BindingSet bindings) {
return condition.test(bindings);
}

@Override
protected void handleClose() {
// Nothing to close
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*******************************************************************************
* Copyright (c) 2025 Eclipse RDF4J contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*******************************************************************************/
package org.eclipse.rdf4j.query.algebra.evaluation.impl.evaluationsteps;

import java.util.function.Predicate;

import org.eclipse.rdf4j.common.iteration.CloseableIteration;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.algebra.evaluation.QueryEvaluationStep;
import org.eclipse.rdf4j.query.algebra.evaluation.QueryValueEvaluationStep;

public class PreFilterQueryEvaluationStep implements QueryEvaluationStep {

private final QueryEvaluationStep wrapped;
private final Predicate<BindingSet> condition;

public PreFilterQueryEvaluationStep(QueryEvaluationStep wrapped,
QueryValueEvaluationStep condition) {
this.wrapped = wrapped;
this.condition = condition.asPredicate();
}

@Override
public CloseableIteration<BindingSet> evaluate(BindingSet leftBindings) {
if (!condition.test(leftBindings)) {
// Usage of this method assume this instance is returned
return QueryEvaluationStep.EMPTY_ITERATION;
}

return wrapped.evaluate(leftBindings);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*******************************************************************************
* Copyright (c) 2025 Eclipse RDF4J contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*******************************************************************************/
package org.eclipse.rdf4j.query.algebra.evaluation.impl.evaluationsteps.values;

import java.util.Set;

import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.query.Binding;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.algebra.evaluation.QueryBindingSet;
import org.eclipse.rdf4j.query.algebra.evaluation.QueryValueEvaluationStep;

public class ScopedQueryValueEvaluationStep implements QueryValueEvaluationStep {

/**
* The set of binding names that are "in scope" for the filter. The filter must not include bindings that are (only)
* included because of the depth-first evaluation strategy in the evaluation of the constraint.
*/
private final Set<String> scopeBindingNames;
private final QueryValueEvaluationStep wrapped;

public ScopedQueryValueEvaluationStep(Set<String> scopeBindingNames, QueryValueEvaluationStep condition) {
this.scopeBindingNames = scopeBindingNames;
this.wrapped = condition;
}

@Override
public Value evaluate(BindingSet bindings) {
BindingSet scopeBindings = createScopeBindings(scopeBindingNames, bindings);

return wrapped.evaluate(scopeBindings);
}

private BindingSet createScopeBindings(Set<String> scopeBindingNames, BindingSet bindings) {
QueryBindingSet scopeBindings = new QueryBindingSet(scopeBindingNames.size());
for (String scopeBindingName : scopeBindingNames) {
Binding binding = bindings.getBinding(scopeBindingName);
if (binding != null) {
scopeBindings.addBinding(binding);
}
}

return scopeBindings;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,13 @@ public class BadlyDesignedLeftJoinIterator extends LeftJoinIterator {
* Constructors *
*--------------*/

public BadlyDesignedLeftJoinIterator(EvaluationStrategy strategy, LeftJoin join, BindingSet inputBindings,
Set<String> problemVars, QueryEvaluationContext context) throws QueryEvaluationException {
super(strategy, join, getFilteredBindings(inputBindings, problemVars), context);
public BadlyDesignedLeftJoinIterator(
EvaluationStrategy strategy,
LeftJoin join,
BindingSet inputBindings,
Set<String> problemVars,
QueryEvaluationStep rightEvaluationStep) throws QueryEvaluationException {
super(strategy, join, getFilteredBindings(inputBindings, problemVars), rightEvaluationStep);
this.inputBindings = inputBindings;
this.problemVars = problemVars;

Expand All @@ -52,10 +56,12 @@ public BadlyDesignedLeftJoinIterator(EvaluationStrategy strategy, LeftJoin join,
* Methods *
*---------*/

public BadlyDesignedLeftJoinIterator(QueryEvaluationStep left, QueryEvaluationStep right,
QueryValueEvaluationStep joinCondition, BindingSet inputBindings, Set<String> problemVars)
public BadlyDesignedLeftJoinIterator(QueryEvaluationStep left,
BindingSet inputBindings,
Set<String> problemVars,
QueryEvaluationStep rightEvaluationStep)
throws QueryEvaluationException {
super(left, right, joinCondition, getFilteredBindings(inputBindings, problemVars), problemVars);
super(left, getFilteredBindings(inputBindings, problemVars), rightEvaluationStep);
this.inputBindings = inputBindings;
this.problemVars = problemVars;
}
Expand Down
Loading
Loading