Adds a naive standalone query optimizer

Temporarily drops the join reordering code
rules
Tpt 2 years ago
parent 2bd5da4a5a
commit 1a4adfc02a
  1. 4
      .github/workflows/tests.yml
  2. 10
      Cargo.lock
  3. 1
      Cargo.toml
  4. 1
      lib/Cargo.toml
  5. 24
      lib/spargebra/src/term.rs
  6. 28
      lib/sparopt/Cargo.toml
  7. 32
      lib/sparopt/README.md
  8. 1157
      lib/sparopt/src/algebra.rs
  9. 4
      lib/sparopt/src/lib.rs
  10. 225
      lib/sparopt/src/optimizer.rs
  11. 839
      lib/src/sparql/plan_builder.rs
  12. 1
      testsuite/tests/sparql.rs

@ -35,6 +35,8 @@ jobs:
working-directory: ./lib/sparesults
- run: cargo clippy
working-directory: ./lib/spargebra
- run: cargo clippy
working-directory: ./lib/sparopt
- run: cargo clippy --all-targets --all-features
clippy_wasm_js:
@ -75,6 +77,8 @@ jobs:
working-directory: ./lib/sparesults
- run: cargo clippy -- -D warnings -D clippy::all
working-directory: ./lib/spargebra
- run: cargo clippy -- -D warnings -D clippy::all
working-directory: ./lib/sparopt
- run: cargo clippy --all-targets -- -D warnings -D clippy::all
working-directory: ./server

10
Cargo.lock generated

@ -954,6 +954,7 @@ dependencies = [
"siphasher",
"sparesults",
"spargebra",
"sparopt",
"zstd",
]
@ -1647,6 +1648,15 @@ dependencies = [
"rand",
]
[[package]]
name = "sparopt"
version = "0.1.0-alpha.1"
dependencies = [
"oxrdf",
"rand",
"spargebra",
]
[[package]]
name = "sparql-smith"
version = "0.1.0-alpha.3"

@ -6,6 +6,7 @@ members = [
"lib/oxsdatatypes",
"lib/spargebra",
"lib/sparesults",
"lib/sparopt",
"lib/sparql-smith",
"oxrocksdb-sys",
"python",

@ -41,6 +41,7 @@ json-event-parser = "0.1"
oxrdf = { version = "0.1.5", path="oxrdf", features = ["rdf-star", "oxsdatatypes"] }
oxsdatatypes = { version = "0.1.1", path="oxsdatatypes" }
spargebra = { version = "0.2.8-dev", path="spargebra", features = ["rdf-star", "sep-0002", "sep-0006"] }
sparopt = { version = "0.1.0-alpha.1", path="sparopt", features = ["rdf-star", "sep-0002", "sep-0006"] }
sparesults = { version = "0.1.7", path="sparesults", features = ["rdf-star"] }
[target.'cfg(not(target_family = "wasm"))'.dependencies]

@ -541,6 +541,19 @@ impl From<NamedNodePattern> for TermPattern {
}
}
impl From<GroundTermPattern> for TermPattern {
#[inline]
fn from(element: GroundTermPattern) -> Self {
match element {
GroundTermPattern::NamedNode(node) => node.into(),
GroundTermPattern::Literal(literal) => literal.into(),
#[cfg(feature = "rdf-star")]
GroundTermPattern::Triple(t) => TriplePattern::from(*t).into(),
GroundTermPattern::Variable(variable) => variable.into(),
}
}
}
impl TryFrom<TermPattern> for Subject {
type Error = ();
@ -799,6 +812,17 @@ impl From<Triple> for TriplePattern {
}
}
impl From<GroundTriplePattern> for TriplePattern {
#[inline]
fn from(triple: GroundTriplePattern) -> Self {
Self {
subject: triple.subject.into(),
predicate: triple.predicate,
object: triple.object.into(),
}
}
}
impl TryFrom<TriplePattern> for Triple {
type Error = ();

@ -0,0 +1,28 @@
[package]
name = "sparopt"
version = "0.1.0-alpha.1"
authors = ["Tpt <thomas@pellissier-tanon.fr>"]
license = "MIT OR Apache-2.0"
readme = "README.md"
keywords = ["SPARQL"]
repository = "https://github.com/oxigraph/oxigraph/tree/main/lib/sparopt"
homepage = "https://oxigraph.org/"
description = """
A SPARQL optimizer
"""
edition = "2021"
rust-version = "1.60"
[features]
default = []
rdf-star = ["oxrdf/rdf-star", "spargebra/rdf-star"]
sep-0002 = ["spargebra/sep-0002"]
sep-0006 = ["spargebra/sep-0006"]
[dependencies]
oxrdf = { version = "0.1.5", path="../oxrdf" }
rand = "0.8"
spargebra = { version = "0.2.8-dev", path="../spargebra" }
[package.metadata.docs.rs]
all-features = true

@ -0,0 +1,32 @@
sparopt
=========
[![Latest Version](https://img.shields.io/crates/v/sparopt.svg)](https://crates.io/crates/sparopt)
[![Released API docs](https://docs.rs/sparopt/badge.svg)](https://docs.rs/sparopt)
[![Crates.io downloads](https://img.shields.io/crates/d/sparopt)](https://crates.io/crates/sparopt)
[![actions status](https://github.com/oxigraph/oxigraph/workflows/build/badge.svg)](https://github.com/oxigraph/oxigraph/actions)
[![Gitter](https://badges.gitter.im/oxigraph/community.svg)](https://gitter.im/oxigraph/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
sparopt is a work in progress [SPARQL Query](https://www.w3.org/TR/sparql11-query/) optimizer.
It relies on the output of [spargebra](https://crates.io/crates/spargebra).
Support for [SPARQL-star](https://w3c.github.io/rdf-star/cg-spec/#sparql-star) is also available behind the `rdf-star` feature.
This crate is intended to be a building piece for SPARQL implementations in Rust like [Oxigraph](https://oxigraph.org).
## License
This project is licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](../LICENSE-APACHE) or
`<http://www.apache.org/licenses/LICENSE-2.0>`)
* MIT license ([LICENSE-MIT](../LICENSE-MIT) or
`<http://opensource.org/licenses/MIT>`)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Oxigraph by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

File diff suppressed because it is too large Load Diff

@ -0,0 +1,4 @@
pub use crate::optimizer::Optimizer;
pub mod algebra;
mod optimizer;

@ -0,0 +1,225 @@
use crate::algebra::{Expression, GraphPattern};
#[derive(Default)]
pub struct Optimizer {}
impl Optimizer {
pub fn optimize(&mut self, pattern: GraphPattern) -> GraphPattern {
Self::normalize_pattern(pattern)
}
fn normalize_pattern(pattern: GraphPattern) -> GraphPattern {
match pattern {
GraphPattern::QuadPattern {
subject,
predicate,
object,
graph_name,
} => GraphPattern::QuadPattern {
subject,
predicate,
object,
graph_name,
},
GraphPattern::Path {
subject,
path,
object,
graph_name,
} => GraphPattern::Path {
subject,
path,
object,
graph_name,
},
GraphPattern::Join { left, right } => GraphPattern::join(
Self::normalize_pattern(*left),
Self::normalize_pattern(*right),
),
GraphPattern::LeftJoin {
left,
right,
expression,
} => GraphPattern::left_join(
Self::normalize_pattern(*left),
Self::normalize_pattern(*right),
Self::normalize_expression(expression),
),
#[cfg(feature = "sep-0006")]
GraphPattern::Lateral { left, right } => GraphPattern::lateral(
Self::normalize_pattern(*left),
Self::normalize_pattern(*right),
),
GraphPattern::Filter { inner, expression } => GraphPattern::filter(
Self::normalize_pattern(*inner),
Self::normalize_expression(expression),
),
GraphPattern::Union { inner } => inner
.into_iter()
.map(Self::normalize_pattern)
.reduce(GraphPattern::union)
.unwrap_or_else(GraphPattern::empty),
GraphPattern::Extend {
inner,
variable,
expression,
} => GraphPattern::extend(Self::normalize_pattern(*inner), variable, expression),
GraphPattern::Minus { left, right } => GraphPattern::minus(
Self::normalize_pattern(*left),
Self::normalize_pattern(*right),
),
GraphPattern::Values {
variables,
bindings,
} => GraphPattern::values(variables, bindings),
GraphPattern::OrderBy { inner, expression } => {
GraphPattern::order_by(Self::normalize_pattern(*inner), expression)
}
GraphPattern::Project { inner, variables } => {
GraphPattern::project(Self::normalize_pattern(*inner), variables)
}
GraphPattern::Distinct { inner } => {
GraphPattern::distinct(Self::normalize_pattern(*inner))
}
GraphPattern::Reduced { inner } => {
GraphPattern::reduced(Self::normalize_pattern(*inner))
}
GraphPattern::Slice {
inner,
start,
length,
} => GraphPattern::slice(Self::normalize_pattern(*inner), start, length),
GraphPattern::Group {
inner,
variables,
aggregates,
} => GraphPattern::group(Self::normalize_pattern(*inner), variables, aggregates),
GraphPattern::Service {
name,
inner,
silent,
} => GraphPattern::service(Self::normalize_pattern(*inner), name, silent),
}
}
fn normalize_expression(expression: Expression) -> Expression {
match expression {
Expression::NamedNode(node) => node.into(),
Expression::Literal(literal) => literal.into(),
Expression::Variable(variable) => variable.into(),
Expression::Or(left, right) => {
let left = Self::normalize_expression(*left);
let right = Self::normalize_expression(*right);
match (
left.effective_boolean_value(),
right.effective_boolean_value(),
) {
(Some(true), _) | (_, Some(true)) => true.into(),
(Some(false), Some(false)) => false.into(),
_ => Expression::Or(Box::new(left), Box::new(right)),
}
}
Expression::And(left, right) => {
let left = Self::normalize_expression(*left);
let right = Self::normalize_expression(*right);
match (
left.effective_boolean_value(),
right.effective_boolean_value(),
) {
(Some(false), _) | (_, Some(false)) => false.into(),
(Some(true), Some(true)) => true.into(),
_ => Expression::And(Box::new(left), Box::new(right)),
}
}
Expression::Equal(left, right) => {
let left = Self::normalize_expression(*left);
let right = Self::normalize_expression(*right);
Expression::Equal(Box::new(left), Box::new(right))
}
Expression::SameTerm(left, right) => {
let left = Self::normalize_expression(*left);
let right = Self::normalize_expression(*right);
Expression::SameTerm(Box::new(left), Box::new(right))
}
Expression::Greater(left, right) => {
let left = Self::normalize_expression(*left);
let right = Self::normalize_expression(*right);
Expression::Greater(Box::new(left), Box::new(right))
}
Expression::GreaterOrEqual(left, right) => {
let left = Self::normalize_expression(*left);
let right = Self::normalize_expression(*right);
Expression::GreaterOrEqual(Box::new(left), Box::new(right))
}
Expression::Less(left, right) => {
let left = Self::normalize_expression(*left);
let right = Self::normalize_expression(*right);
Expression::Less(Box::new(left), Box::new(right))
}
Expression::LessOrEqual(left, right) => {
let left = Self::normalize_expression(*left);
let right = Self::normalize_expression(*right);
Expression::LessOrEqual(Box::new(left), Box::new(right))
}
Expression::In(left, right) => {
let left = Self::normalize_expression(*left);
let right = right.into_iter().map(Self::normalize_expression).collect();
Expression::In(Box::new(left), right)
}
Expression::Add(left, right) => {
let left = Self::normalize_expression(*left);
let right = Self::normalize_expression(*right);
Expression::Add(Box::new(left), Box::new(right))
}
Expression::Subtract(left, right) => {
let left = Self::normalize_expression(*left);
let right = Self::normalize_expression(*right);
Expression::Subtract(Box::new(left), Box::new(right))
}
Expression::Multiply(left, right) => {
let left = Self::normalize_expression(*left);
let right = Self::normalize_expression(*right);
Expression::Multiply(Box::new(left), Box::new(right))
}
Expression::Divide(left, right) => {
let left = Self::normalize_expression(*left);
let right = Self::normalize_expression(*right);
Expression::Divide(Box::new(left), Box::new(right))
}
Expression::UnaryPlus(inner) => {
let inner = Self::normalize_expression(*inner);
Expression::UnaryPlus(Box::new(inner))
}
Expression::UnaryMinus(inner) => {
let inner = Self::normalize_expression(*inner);
Expression::UnaryMinus(Box::new(inner))
}
Expression::Not(inner) => {
let inner = Self::normalize_expression(*inner);
Expression::Not(Box::new(inner))
}
Expression::Exists(inner) => {
let inner = Self::normalize_pattern(*inner);
Expression::Exists(Box::new(inner))
}
Expression::Bound(variable) => Expression::Bound(variable),
Expression::If(cond, then, els) => {
let cond = Self::normalize_expression(*cond);
let then = Self::normalize_expression(*then);
let els = Self::normalize_expression(*els);
match cond.effective_boolean_value() {
Some(true) => then,
Some(false) => els,
None => Expression::If(Box::new(cond), Box::new(then), Box::new(els)),
}
}
Expression::Coalesce(inners) => {
Expression::Coalesce(inners.into_iter().map(Self::normalize_expression).collect())
}
Expression::FunctionCall(name, args) => Expression::FunctionCall(
name,
args.into_iter().map(Self::normalize_expression).collect(),
),
}
}
}

File diff suppressed because it is too large Load Diff

@ -67,6 +67,7 @@ fn sparql10_w3c_query_evaluation_testsuite() -> Result<()> {
// We choose to simplify first the nested group patterns in OPTIONAL
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/optional-filter/manifest#dawg-optional-filter-005-not-simplified",
// This test relies on naive iteration on the input file
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/reduced/manifest#reduced-1",
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/reduced/manifest#reduced-2"
])
}

Loading…
Cancel
Save