Skip to content
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

Filter optimisation doesn't respect scope changes #4872

Open
frensjan opened this issue Jan 20, 2024 · 0 comments
Open

Filter optimisation doesn't respect scope changes #4872

frensjan opened this issue Jan 20, 2024 · 0 comments
Labels
🐞 bug issue is a bug

Comments

@frensjan
Copy link
Contributor

frensjan commented Jan 20, 2024

Current Behavior

While investigating the support for LATERAL, I believe I have found an issue with how filter optimisation incorrectly handles scope changes.

The following query:

PREFIX : <http://example/>
SELECT ?s ?o WHERE {
    VALUES ?s { :S }
    {
        { VALUES ?o { :O } }
        { FILTER(BOUND(?s) && !BOUND(?o)) }
    }
}

Has this as unoptimised query plan:

Projection
╠══ ProjectionElemList
║     ProjectionElem "s"
║     ProjectionElem "o"
╚══ Join
   ├── BindingSetAssignment ([[s=http://example/S]]) [left]
   └── Join (new scope) [right]
      ╠══ BindingSetAssignment ([[o=http://example/O]]) (new scope) [left]
      ╚══ Filter (new scope) [right]
         ├── And
         │  ╠══ Bound
         │  ║     Var (name=s)
         │  ╚══ Not
         │        Bound
         │           Var (name=o)
         └── SingletonSet

However, the optimised query plan is:

Join (JoinIterator)
╠══ BindingSetAssignment ([[s=http://example/S]]) (costEstimate=1, resultSizeEstimate=1) [left]
╚══ Join (JoinIterator) [right]
   ├── BindingSetAssignment ([[o=http://example/O]]) (new scope) (costEstimate=1, resultSizeEstimate=1) [left]
   └── Filter [right]
      ╠══ And
      ║  ├── Bound
      ║  │     Var (name=s)
      ║  └── Not
      ║        Bound
      ║           Var (name=o)
      ╚══ SingletonSet

Note that the Filter [right] is no longer marked as a new scope.

Expected Behavior

Optimisation may split, move and merge filters, but only within the same scope (group graph pattern).

Version

develop

Are you interested in contributing a solution yourself?

Perhaps?

Anything else?

Irrelevance without LATERAL

Note that this issue is largely irrelevant without notion of LATERAL join as the evaluation of { FILTER(BOUND(?s) && !BOUND(?o)) } (with the bottom-up evaluation semantics of SPARQL) can never yield a result as s will never be bound. The work for #4769 by @hmottestad and @JervenBolleman fixes this issue for the situation without LATERAL join. I do think this will hinder the implementation of LATERAL though.

Variable scopes are difficult

I'm wondering whether having the VariableScopeChange with it's boolean state at the QueryModelNode level is easy enough to work with when it comes to creating optimisation rules. Could perhaps having an explicit node in the algebra make this area less error prone?

E.g., the solution for #4769 at least in part was to 'fix' the scope of bindings for Filter. However, I think scoping could be tackled more generically, also 'freeing' implementations such as FilterIterator from such details.

Details of the query plan optimisation

For reference, these are intermediary query plans:

Intermediary query plans

The first relevant optimisation is performed by ConjunctiveConstraintSplitterOptimizer:

QueryRoot
   Join
      BindingSetAssignment ([[s=http://example/S]])
      Join
         BindingSetAssignment ([[o=http://example/O]]) (new scope)
         Filter (new scope)
            Bound
               Var (name=s)
            Filter
               Not
                  Bound
                     Var (name=o)
               SingletonSet

Then the FilterOrganizer in the FilterOptimizer relocates the two nested Filter as deep as possible; but reverses their nesting! The FilterMerger then merges the conditions of the two Filters into the a new Filter, ignoring the isVariableScopeChange state.

QueryRoot
   Join
      BindingSetAssignment ([[s=http://example/S]])
      Join
         BindingSetAssignment ([[o=http://example/O]]) (new scope)
         Filter
            Not
               Bound
                  Var (name=o)
            Filter (new scope)
               Bound
                  Var (name=s)
               SingletonSet
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐞 bug issue is a bug
Projects
None yet
Development

No branches or pull requests

1 participant