Fork of https://github.com/oxigraph/oxigraph.git for the purpose of NextGraph project
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1839 lines
62 KiB
1839 lines
62 KiB
//! [SPARQL 1.1 Query Algebra](https://www.w3.org/TR/sparql11-query/#sparqlQuery) representation.
|
|
|
|
use oxrdf::vocab::xsd;
|
|
use rand::random;
|
|
use spargebra::algebra::{
|
|
AggregateExpression as AlAggregateExpression, Expression as AlExpression,
|
|
GraphPattern as AlGraphPattern, OrderExpression as AlOrderExpression,
|
|
};
|
|
pub use spargebra::algebra::{Function, PropertyPathExpression};
|
|
use spargebra::term::{BlankNode, GroundSubject, TermPattern, TriplePattern};
|
|
pub use spargebra::term::{
|
|
GroundTerm, GroundTermPattern, Literal, NamedNode, NamedNodePattern, Variable,
|
|
};
|
|
#[cfg(feature = "rdf-star")]
|
|
use spargebra::term::{GroundTriple, GroundTriplePattern};
|
|
use std::collections::HashMap;
|
|
|
|
/// An [expression](https://www.w3.org/TR/sparql11-query/#expressions).
|
|
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
|
|
pub enum Expression {
|
|
NamedNode(NamedNode),
|
|
Literal(Literal),
|
|
Variable(Variable),
|
|
/// [Logical-or](https://www.w3.org/TR/sparql11-query/#func-logical-or).
|
|
Or(Box<Self>, Box<Self>),
|
|
/// [Logical-and](https://www.w3.org/TR/sparql11-query/#func-logical-and).
|
|
And(Box<Self>, Box<Self>),
|
|
/// [RDFterm-equal](https://www.w3.org/TR/sparql11-query/#func-RDFterm-equal) and all the XSD equalities.
|
|
Equal(Box<Self>, Box<Self>),
|
|
/// [sameTerm](https://www.w3.org/TR/sparql11-query/#func-sameTerm).
|
|
SameTerm(Box<Self>, Box<Self>),
|
|
/// [op:numeric-greater-than](https://www.w3.org/TR/xpath-functions/#func-numeric-greater-than) and other XSD greater than operators.
|
|
Greater(Box<Self>, Box<Self>),
|
|
GreaterOrEqual(Box<Self>, Box<Self>),
|
|
/// [op:numeric-less-than](https://www.w3.org/TR/xpath-functions/#func-numeric-less-than) and other XSD greater than operators.
|
|
Less(Box<Self>, Box<Self>),
|
|
LessOrEqual(Box<Self>, Box<Self>),
|
|
/// [op:numeric-add](https://www.w3.org/TR/xpath-functions/#func-numeric-add) and other XSD additions.
|
|
Add(Box<Self>, Box<Self>),
|
|
/// [op:numeric-subtract](https://www.w3.org/TR/xpath-functions/#func-numeric-subtract) and other XSD subtractions.
|
|
Subtract(Box<Self>, Box<Self>),
|
|
/// [op:numeric-multiply](https://www.w3.org/TR/xpath-functions/#func-numeric-multiply) and other XSD multiplications.
|
|
Multiply(Box<Self>, Box<Self>),
|
|
/// [op:numeric-divide](https://www.w3.org/TR/xpath-functions/#func-numeric-divide) and other XSD divides.
|
|
Divide(Box<Self>, Box<Self>),
|
|
/// [op:numeric-unary-plus](https://www.w3.org/TR/xpath-functions/#func-numeric-unary-plus) and other XSD unary plus.
|
|
UnaryPlus(Box<Self>),
|
|
/// [op:numeric-unary-minus](https://www.w3.org/TR/xpath-functions/#func-numeric-unary-minus) and other XSD unary minus.
|
|
UnaryMinus(Box<Self>),
|
|
/// [fn:not](https://www.w3.org/TR/xpath-functions/#func-not).
|
|
Not(Box<Self>),
|
|
/// [EXISTS](https://www.w3.org/TR/sparql11-query/#func-filter-exists).
|
|
Exists(Box<GraphPattern>),
|
|
/// [BOUND](https://www.w3.org/TR/sparql11-query/#func-bound).
|
|
Bound(Variable),
|
|
/// [IF](https://www.w3.org/TR/sparql11-query/#func-if).
|
|
If(Box<Self>, Box<Self>, Box<Self>),
|
|
/// [COALESCE](https://www.w3.org/TR/sparql11-query/#func-coalesce).
|
|
Coalesce(Vec<Self>),
|
|
/// A regular function call.
|
|
FunctionCall(Function, Vec<Self>),
|
|
}
|
|
|
|
impl Expression {
|
|
pub(crate) fn or(left: Self, right: Self) -> Self {
|
|
match (
|
|
left.effective_boolean_value(),
|
|
right.effective_boolean_value(),
|
|
) {
|
|
(Some(true), _) | (_, Some(true)) => true.into(),
|
|
(Some(false), Some(false)) => false.into(),
|
|
_ => Self::Or(Box::new(left), Box::new(right)),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn and(left: Self, right: Self) -> Self {
|
|
match (
|
|
left.effective_boolean_value(),
|
|
right.effective_boolean_value(),
|
|
) {
|
|
(Some(false), _) | (_, Some(false)) => false.into(),
|
|
(Some(true), Some(true)) => true.into(),
|
|
_ => Self::And(Box::new(left), Box::new(right)),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn equal(left: Self, right: Self) -> Self {
|
|
Self::Equal(Box::new(left), Box::new(right))
|
|
}
|
|
|
|
pub(crate) fn same_term(left: Self, right: Self) -> Self {
|
|
Self::SameTerm(Box::new(left), Box::new(right))
|
|
}
|
|
|
|
pub(crate) fn greater(left: Self, right: Self) -> Self {
|
|
Self::Greater(Box::new(left), Box::new(right))
|
|
}
|
|
|
|
pub(crate) fn greater_or_equal(left: Self, right: Self) -> Self {
|
|
Self::GreaterOrEqual(Box::new(left), Box::new(right))
|
|
}
|
|
|
|
pub(crate) fn less(left: Self, right: Self) -> Self {
|
|
Self::Less(Box::new(left), Box::new(right))
|
|
}
|
|
|
|
pub(crate) fn less_or_equal(left: Self, right: Self) -> Self {
|
|
Self::LessOrEqual(Box::new(left), Box::new(right))
|
|
}
|
|
|
|
pub(crate) fn add(left: Self, right: Self) -> Self {
|
|
Self::Add(Box::new(left), Box::new(right))
|
|
}
|
|
|
|
pub(crate) fn subtract(left: Self, right: Self) -> Self {
|
|
Self::Subtract(Box::new(left), Box::new(right))
|
|
}
|
|
|
|
pub(crate) fn multiply(left: Self, right: Self) -> Self {
|
|
Self::Multiply(Box::new(left), Box::new(right))
|
|
}
|
|
|
|
pub(crate) fn divide(left: Self, right: Self) -> Self {
|
|
Self::Divide(Box::new(left), Box::new(right))
|
|
}
|
|
|
|
pub(crate) fn unary_plus(inner: Self) -> Self {
|
|
Self::UnaryPlus(Box::new(inner))
|
|
}
|
|
|
|
pub(crate) fn unary_minus(inner: Self) -> Self {
|
|
Self::UnaryMinus(Box::new(inner))
|
|
}
|
|
|
|
pub(crate) fn not(inner: Self) -> Self {
|
|
Self::Not(Box::new(inner))
|
|
}
|
|
|
|
pub(crate) fn exists(inner: GraphPattern) -> Self {
|
|
if inner.is_empty() {
|
|
return false.into();
|
|
}
|
|
Self::Exists(Box::new(inner))
|
|
}
|
|
|
|
pub(crate) fn if_cond(cond: Self, then: Self, els: Self) -> Self {
|
|
match cond.effective_boolean_value() {
|
|
Some(true) => then,
|
|
Some(false) => els,
|
|
None => Self::If(Box::new(cond), Box::new(then), Box::new(els)),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn coalesce(args: Vec<Self>) -> Self {
|
|
Self::Coalesce(args)
|
|
}
|
|
|
|
pub(crate) fn call(name: Function, args: Vec<Self>) -> Self {
|
|
Self::FunctionCall(name, args)
|
|
}
|
|
|
|
pub(crate) fn effective_boolean_value(&self) -> Option<bool> {
|
|
match self {
|
|
Self::NamedNode(_) => Some(true),
|
|
Self::Literal(literal) => {
|
|
if literal.datatype() == xsd::BOOLEAN {
|
|
match literal.value() {
|
|
"true" | "1" => Some(true),
|
|
"false" | "0" => Some(false),
|
|
_ => None, //TODO
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
_ => None, // We assume the expression has been normalized
|
|
}
|
|
}
|
|
|
|
fn from_sparql_algebra(
|
|
expression: &AlExpression,
|
|
graph_name: Option<&NamedNodePattern>,
|
|
) -> Self {
|
|
match expression {
|
|
AlExpression::NamedNode(node) => Self::NamedNode(node.clone()),
|
|
AlExpression::Literal(literal) => Self::Literal(literal.clone()),
|
|
AlExpression::Variable(variable) => Self::Variable(variable.clone()),
|
|
AlExpression::Or(left, right) => Self::Or(
|
|
Box::new(Self::from_sparql_algebra(left, graph_name)),
|
|
Box::new(Self::from_sparql_algebra(right, graph_name)),
|
|
),
|
|
AlExpression::And(left, right) => Self::And(
|
|
Box::new(Self::from_sparql_algebra(left, graph_name)),
|
|
Box::new(Self::from_sparql_algebra(right, graph_name)),
|
|
),
|
|
AlExpression::Equal(left, right) => Self::Equal(
|
|
Box::new(Self::from_sparql_algebra(left, graph_name)),
|
|
Box::new(Self::from_sparql_algebra(right, graph_name)),
|
|
),
|
|
AlExpression::SameTerm(left, right) => Self::SameTerm(
|
|
Box::new(Self::from_sparql_algebra(left, graph_name)),
|
|
Box::new(Self::from_sparql_algebra(right, graph_name)),
|
|
),
|
|
AlExpression::Greater(left, right) => Self::Greater(
|
|
Box::new(Self::from_sparql_algebra(left, graph_name)),
|
|
Box::new(Self::from_sparql_algebra(right, graph_name)),
|
|
),
|
|
AlExpression::GreaterOrEqual(left, right) => Self::GreaterOrEqual(
|
|
Box::new(Self::from_sparql_algebra(left, graph_name)),
|
|
Box::new(Self::from_sparql_algebra(right, graph_name)),
|
|
),
|
|
AlExpression::Less(left, right) => Self::Less(
|
|
Box::new(Self::from_sparql_algebra(left, graph_name)),
|
|
Box::new(Self::from_sparql_algebra(right, graph_name)),
|
|
),
|
|
AlExpression::LessOrEqual(left, right) => Self::LessOrEqual(
|
|
Box::new(Self::from_sparql_algebra(left, graph_name)),
|
|
Box::new(Self::from_sparql_algebra(right, graph_name)),
|
|
),
|
|
AlExpression::In(left, right) => {
|
|
let left = Self::from_sparql_algebra(left, graph_name);
|
|
right
|
|
.iter()
|
|
.map(|e| {
|
|
Self::Equal(
|
|
Box::new(left.clone()),
|
|
Box::new(Self::from_sparql_algebra(e, graph_name)),
|
|
)
|
|
})
|
|
.reduce(|a, b| Self::Or(Box::new(a), Box::new(b)))
|
|
.unwrap_or_else(|| false.into())
|
|
}
|
|
AlExpression::Add(left, right) => Self::Add(
|
|
Box::new(Self::from_sparql_algebra(left, graph_name)),
|
|
Box::new(Self::from_sparql_algebra(right, graph_name)),
|
|
),
|
|
AlExpression::Subtract(left, right) => Self::Subtract(
|
|
Box::new(Self::from_sparql_algebra(left, graph_name)),
|
|
Box::new(Self::from_sparql_algebra(right, graph_name)),
|
|
),
|
|
AlExpression::Multiply(left, right) => Self::Multiply(
|
|
Box::new(Self::from_sparql_algebra(left, graph_name)),
|
|
Box::new(Self::from_sparql_algebra(right, graph_name)),
|
|
),
|
|
AlExpression::Divide(left, right) => Self::Divide(
|
|
Box::new(Self::from_sparql_algebra(left, graph_name)),
|
|
Box::new(Self::from_sparql_algebra(right, graph_name)),
|
|
),
|
|
AlExpression::UnaryPlus(inner) => {
|
|
Self::UnaryPlus(Box::new(Self::from_sparql_algebra(inner, graph_name)))
|
|
}
|
|
AlExpression::UnaryMinus(inner) => {
|
|
Self::UnaryMinus(Box::new(Self::from_sparql_algebra(inner, graph_name)))
|
|
}
|
|
AlExpression::Not(inner) => {
|
|
Self::Not(Box::new(Self::from_sparql_algebra(inner, graph_name)))
|
|
}
|
|
AlExpression::Exists(inner) => Self::Exists(Box::new(
|
|
GraphPattern::from_sparql_algebra(inner, graph_name, &mut HashMap::new()),
|
|
)),
|
|
AlExpression::Bound(variable) => Self::Bound(variable.clone()),
|
|
AlExpression::If(cond, yes, no) => Self::If(
|
|
Box::new(Self::from_sparql_algebra(cond, graph_name)),
|
|
Box::new(Self::from_sparql_algebra(yes, graph_name)),
|
|
Box::new(Self::from_sparql_algebra(no, graph_name)),
|
|
),
|
|
AlExpression::Coalesce(inner) => Self::Coalesce(
|
|
inner
|
|
.iter()
|
|
.map(|e| Self::from_sparql_algebra(e, graph_name))
|
|
.collect(),
|
|
),
|
|
AlExpression::FunctionCall(name, args) => Self::FunctionCall(
|
|
name.clone(),
|
|
args.iter()
|
|
.map(|e| Self::from_sparql_algebra(e, graph_name))
|
|
.collect(),
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<NamedNode> for Expression {
|
|
fn from(value: NamedNode) -> Self {
|
|
Self::NamedNode(value)
|
|
}
|
|
}
|
|
|
|
impl From<Literal> for Expression {
|
|
fn from(value: Literal) -> Self {
|
|
Self::Literal(value)
|
|
}
|
|
}
|
|
|
|
impl From<GroundSubject> for Expression {
|
|
fn from(value: GroundSubject) -> Self {
|
|
match value {
|
|
GroundSubject::NamedNode(value) => value.into(),
|
|
#[cfg(feature = "rdf-star")]
|
|
GroundSubject::Triple(value) => (*value).into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<GroundTerm> for Expression {
|
|
fn from(value: GroundTerm) -> Self {
|
|
match value {
|
|
GroundTerm::NamedNode(value) => value.into(),
|
|
GroundTerm::Literal(value) => value.into(),
|
|
#[cfg(feature = "rdf-star")]
|
|
GroundTerm::Triple(value) => (*value).into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<NamedNodePattern> for Expression {
|
|
fn from(value: NamedNodePattern) -> Self {
|
|
match value {
|
|
NamedNodePattern::NamedNode(value) => value.into(),
|
|
NamedNodePattern::Variable(variable) => variable.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<GroundTermPattern> for Expression {
|
|
fn from(value: GroundTermPattern) -> Self {
|
|
match value {
|
|
GroundTermPattern::NamedNode(value) => value.into(),
|
|
GroundTermPattern::Literal(value) => value.into(),
|
|
#[cfg(feature = "rdf-star")]
|
|
GroundTermPattern::Triple(value) => (*value).into(),
|
|
GroundTermPattern::Variable(variable) => variable.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "rdf-star")]
|
|
impl From<GroundTriple> for Expression {
|
|
fn from(value: GroundTriple) -> Self {
|
|
Self::FunctionCall(
|
|
Function::Triple,
|
|
vec![
|
|
value.subject.into(),
|
|
value.predicate.into(),
|
|
value.object.into(),
|
|
],
|
|
)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "rdf-star")]
|
|
impl From<GroundTriplePattern> for Expression {
|
|
fn from(value: GroundTriplePattern) -> Self {
|
|
Self::FunctionCall(
|
|
Function::Triple,
|
|
vec![
|
|
value.subject.into(),
|
|
value.predicate.into(),
|
|
value.object.into(),
|
|
],
|
|
)
|
|
}
|
|
}
|
|
|
|
impl From<Variable> for Expression {
|
|
fn from(value: Variable) -> Self {
|
|
Self::Variable(value)
|
|
}
|
|
}
|
|
|
|
impl From<bool> for Expression {
|
|
fn from(value: bool) -> Self {
|
|
Literal::from(value).into()
|
|
}
|
|
}
|
|
|
|
impl From<&Expression> for AlExpression {
|
|
fn from(expression: &Expression) -> Self {
|
|
match expression {
|
|
Expression::NamedNode(node) => Self::NamedNode(node.clone()),
|
|
Expression::Literal(literal) => Self::Literal(literal.clone()),
|
|
Expression::Variable(variable) => Self::Variable(variable.clone()),
|
|
Expression::Or(left, right) => Self::Or(
|
|
Box::new(left.as_ref().into()),
|
|
Box::new(right.as_ref().into()),
|
|
),
|
|
Expression::And(left, right) => Self::And(
|
|
Box::new(left.as_ref().into()),
|
|
Box::new(right.as_ref().into()),
|
|
),
|
|
Expression::Equal(left, right) => Self::Equal(
|
|
Box::new(left.as_ref().into()),
|
|
Box::new(right.as_ref().into()),
|
|
),
|
|
Expression::SameTerm(left, right) => Self::SameTerm(
|
|
Box::new(left.as_ref().into()),
|
|
Box::new(right.as_ref().into()),
|
|
),
|
|
Expression::Greater(left, right) => Self::Greater(
|
|
Box::new(left.as_ref().into()),
|
|
Box::new(right.as_ref().into()),
|
|
),
|
|
Expression::GreaterOrEqual(left, right) => Self::GreaterOrEqual(
|
|
Box::new(left.as_ref().into()),
|
|
Box::new(right.as_ref().into()),
|
|
),
|
|
Expression::Less(left, right) => Self::Less(
|
|
Box::new(left.as_ref().into()),
|
|
Box::new(right.as_ref().into()),
|
|
),
|
|
Expression::LessOrEqual(left, right) => Self::LessOrEqual(
|
|
Box::new(left.as_ref().into()),
|
|
Box::new(right.as_ref().into()),
|
|
),
|
|
Expression::Add(left, right) => Self::Add(
|
|
Box::new(left.as_ref().into()),
|
|
Box::new(right.as_ref().into()),
|
|
),
|
|
Expression::Subtract(left, right) => Self::Subtract(
|
|
Box::new(left.as_ref().into()),
|
|
Box::new(right.as_ref().into()),
|
|
),
|
|
Expression::Multiply(left, right) => Self::Multiply(
|
|
Box::new(left.as_ref().into()),
|
|
Box::new(right.as_ref().into()),
|
|
),
|
|
Expression::Divide(left, right) => Self::Divide(
|
|
Box::new(left.as_ref().into()),
|
|
Box::new(right.as_ref().into()),
|
|
),
|
|
Expression::UnaryPlus(inner) => Self::UnaryPlus(Box::new(inner.as_ref().into())),
|
|
Expression::UnaryMinus(inner) => Self::UnaryMinus(Box::new(inner.as_ref().into())),
|
|
Expression::Not(inner) => Self::Not(Box::new(inner.as_ref().into())),
|
|
Expression::Exists(inner) => Self::Exists(Box::new(inner.as_ref().into())),
|
|
Expression::Bound(variable) => Self::Bound(variable.clone()),
|
|
Expression::If(cond, yes, no) => Self::If(
|
|
Box::new(cond.as_ref().into()),
|
|
Box::new(yes.as_ref().into()),
|
|
Box::new(no.as_ref().into()),
|
|
),
|
|
Expression::Coalesce(inner) => Self::Coalesce(inner.iter().map(|e| e.into()).collect()),
|
|
Expression::FunctionCall(name, args) => {
|
|
Self::FunctionCall(name.clone(), args.iter().map(|e| e.into()).collect())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A SPARQL query [graph pattern](https://www.w3.org/TR/sparql11-query/#sparqlQuery).
|
|
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
|
|
pub enum GraphPattern {
|
|
/// A [basic graph pattern](https://www.w3.org/TR/sparql11-query/#defn_BasicGraphPattern).
|
|
QuadPattern {
|
|
subject: GroundTermPattern,
|
|
predicate: NamedNodePattern,
|
|
object: GroundTermPattern,
|
|
graph_name: Option<NamedNodePattern>,
|
|
},
|
|
/// A [property path pattern](https://www.w3.org/TR/sparql11-query/#defn_evalPP_predicate).
|
|
Path {
|
|
subject: GroundTermPattern,
|
|
path: PropertyPathExpression,
|
|
object: GroundTermPattern,
|
|
graph_name: Option<NamedNodePattern>,
|
|
},
|
|
/// [Join](https://www.w3.org/TR/sparql11-query/#defn_algJoin).
|
|
Join { left: Box<Self>, right: Box<Self> },
|
|
/// [LeftJoin](https://www.w3.org/TR/sparql11-query/#defn_algLeftJoin).
|
|
LeftJoin {
|
|
left: Box<Self>,
|
|
right: Box<Self>,
|
|
expression: Expression,
|
|
},
|
|
/// Lateral join i.e. evaluate right for all result row of left
|
|
#[cfg(feature = "sep-0006")]
|
|
Lateral { left: Box<Self>, right: Box<Self> },
|
|
/// [Filter](https://www.w3.org/TR/sparql11-query/#defn_algFilter).
|
|
Filter {
|
|
expression: Expression,
|
|
inner: Box<Self>,
|
|
},
|
|
/// [Union](https://www.w3.org/TR/sparql11-query/#defn_algUnion).
|
|
Union { inner: Vec<Self> },
|
|
/// [Extend](https://www.w3.org/TR/sparql11-query/#defn_extend).
|
|
Extend {
|
|
inner: Box<Self>,
|
|
variable: Variable,
|
|
expression: Expression,
|
|
},
|
|
/// [Minus](https://www.w3.org/TR/sparql11-query/#defn_algMinus).
|
|
Minus { left: Box<Self>, right: Box<Self> },
|
|
/// A table used to provide inline values
|
|
Values {
|
|
variables: Vec<Variable>,
|
|
bindings: Vec<Vec<Option<GroundTerm>>>,
|
|
},
|
|
/// [OrderBy](https://www.w3.org/TR/sparql11-query/#defn_algOrdered).
|
|
OrderBy {
|
|
inner: Box<Self>,
|
|
expression: Vec<OrderExpression>,
|
|
},
|
|
/// [Project](https://www.w3.org/TR/sparql11-query/#defn_algProjection).
|
|
Project {
|
|
inner: Box<Self>,
|
|
variables: Vec<Variable>,
|
|
},
|
|
/// [Distinct](https://www.w3.org/TR/sparql11-query/#defn_algDistinct).
|
|
Distinct { inner: Box<Self> },
|
|
/// [Reduced](https://www.w3.org/TR/sparql11-query/#defn_algReduced).
|
|
Reduced { inner: Box<Self> },
|
|
/// [Slice](https://www.w3.org/TR/sparql11-query/#defn_algSlice).
|
|
Slice {
|
|
inner: Box<Self>,
|
|
start: usize,
|
|
length: Option<usize>,
|
|
},
|
|
/// [Group](https://www.w3.org/TR/sparql11-federated-query/#aggregateAlgebra).
|
|
Group {
|
|
inner: Box<Self>,
|
|
variables: Vec<Variable>,
|
|
aggregates: Vec<(Variable, AggregateExpression)>,
|
|
},
|
|
/// [Service](https://www.w3.org/TR/sparql11-federated-query/#defn_evalService).
|
|
Service {
|
|
name: NamedNodePattern,
|
|
inner: Box<Self>,
|
|
silent: bool,
|
|
},
|
|
/// Fix point operator
|
|
#[cfg(feature = "fixed-point")]
|
|
FixedPoint {
|
|
id: FixedPointId,
|
|
/// invariant: the inner plans should always return the all the listed variables and only them
|
|
variables: Vec<Variable>,
|
|
constant: Box<FixedPointGraphPattern>,
|
|
recursive: Box<FixedPointGraphPattern>,
|
|
},
|
|
}
|
|
|
|
impl GraphPattern {
|
|
pub(crate) fn empty() -> Self {
|
|
Self::Values {
|
|
variables: Vec::new(),
|
|
bindings: Vec::new(),
|
|
}
|
|
}
|
|
|
|
/// Check if the pattern is the empty table
|
|
fn is_empty(&self) -> bool {
|
|
if let Self::Values { bindings, .. } = self {
|
|
bindings.is_empty()
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
fn singleton() -> Self {
|
|
Self::Values {
|
|
variables: Vec::new(),
|
|
bindings: vec![Vec::new()],
|
|
}
|
|
}
|
|
|
|
pub(crate) fn join(left: Self, right: Self) -> Self {
|
|
if left.is_empty() || right.is_empty() {
|
|
return Self::empty();
|
|
}
|
|
Self::Join {
|
|
left: Box::new(left),
|
|
right: Box::new(right),
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "sep-0006")]
|
|
pub(crate) fn lateral(left: Self, right: Self) -> Self {
|
|
if left.is_empty() || right.is_empty() {
|
|
return Self::empty();
|
|
}
|
|
Self::Lateral {
|
|
left: Box::new(left),
|
|
right: Box::new(right),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn left_join(left: Self, right: Self, expression: Expression) -> Self {
|
|
let expression_ebv = expression.effective_boolean_value();
|
|
if left.is_empty() || right.is_empty() || expression_ebv == Some(false) {
|
|
return left;
|
|
}
|
|
Self::LeftJoin {
|
|
left: Box::new(left),
|
|
right: Box::new(right),
|
|
expression: if expression_ebv == Some(true) {
|
|
true.into()
|
|
} else {
|
|
expression
|
|
},
|
|
}
|
|
}
|
|
|
|
pub(crate) fn minus(left: Self, right: Self) -> Self {
|
|
if left.is_empty() {
|
|
return Self::empty();
|
|
}
|
|
if right.is_empty() {
|
|
return left;
|
|
}
|
|
Self::Minus {
|
|
left: Box::new(left),
|
|
right: Box::new(right),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn union(left: Self, right: Self) -> Self {
|
|
if left.is_empty() {
|
|
return right;
|
|
}
|
|
if right.is_empty() {
|
|
return left;
|
|
}
|
|
Self::Union {
|
|
inner: match (left, right) {
|
|
(Self::Union { inner: mut left }, Self::Union { inner: right }) => {
|
|
left.extend(right);
|
|
left
|
|
}
|
|
(Self::Union { inner: mut left }, right) => {
|
|
left.push(right);
|
|
left
|
|
}
|
|
(left, Self::Union { inner: mut right }) => {
|
|
right.insert(0, left);
|
|
right
|
|
}
|
|
(left, right) => vec![left, right],
|
|
},
|
|
}
|
|
}
|
|
|
|
pub(crate) fn filter(inner: Self, expression: Expression) -> Self {
|
|
if inner.is_empty() {
|
|
return Self::empty();
|
|
}
|
|
match expression.effective_boolean_value() {
|
|
Some(true) => inner,
|
|
Some(false) => Self::empty(),
|
|
None => Self::Filter {
|
|
inner: Box::new(inner),
|
|
expression,
|
|
},
|
|
}
|
|
}
|
|
|
|
pub(crate) fn extend(inner: Self, variable: Variable, expression: Expression) -> Self {
|
|
if inner.is_empty() {
|
|
return Self::empty();
|
|
}
|
|
Self::Extend {
|
|
inner: Box::new(inner),
|
|
variable,
|
|
expression,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn values(
|
|
mut variables: Vec<Variable>,
|
|
mut bindings: Vec<Vec<Option<GroundTerm>>>,
|
|
) -> Self {
|
|
let empty_rows = (0..variables.len())
|
|
.filter(|row| !bindings.iter().any(|binding| binding.get(*row).is_some()))
|
|
.collect::<Vec<_>>();
|
|
if !empty_rows.is_empty() {
|
|
// We remove empty rows
|
|
variables = variables
|
|
.into_iter()
|
|
.enumerate()
|
|
.filter_map(|(i, v)| {
|
|
if empty_rows.contains(&i) {
|
|
None
|
|
} else {
|
|
Some(v)
|
|
}
|
|
})
|
|
.collect();
|
|
bindings = bindings
|
|
.into_iter()
|
|
.map(|binding| {
|
|
binding
|
|
.into_iter()
|
|
.enumerate()
|
|
.filter_map(|(i, v)| {
|
|
if empty_rows.contains(&i) {
|
|
None
|
|
} else {
|
|
Some(v)
|
|
}
|
|
})
|
|
.collect()
|
|
})
|
|
.collect();
|
|
}
|
|
Self::Values {
|
|
variables,
|
|
bindings,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn order_by(inner: Self, expression: Vec<OrderExpression>) -> Self {
|
|
if inner.is_empty() {
|
|
return Self::empty();
|
|
}
|
|
if expression.is_empty() {
|
|
return inner;
|
|
}
|
|
Self::OrderBy {
|
|
inner: Box::new(inner),
|
|
expression,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn project(inner: Self, variables: Vec<Variable>) -> Self {
|
|
if inner.is_empty() {
|
|
return Self::empty();
|
|
}
|
|
Self::Project {
|
|
inner: Box::new(inner),
|
|
variables,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn distinct(inner: Self) -> Self {
|
|
if inner.is_empty() {
|
|
return Self::empty();
|
|
}
|
|
Self::Distinct {
|
|
inner: Box::new(inner),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn reduced(inner: Self) -> Self {
|
|
if inner.is_empty() {
|
|
return Self::empty();
|
|
}
|
|
Self::Reduced {
|
|
inner: Box::new(inner),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn slice(inner: Self, start: usize, length: Option<usize>) -> Self {
|
|
if inner.is_empty() {
|
|
return Self::empty();
|
|
}
|
|
if start == 0 && length.is_none() {
|
|
return inner;
|
|
}
|
|
Self::Slice {
|
|
inner: Box::new(inner),
|
|
start,
|
|
length,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn group(
|
|
inner: Self,
|
|
variables: Vec<Variable>,
|
|
aggregates: Vec<(Variable, AggregateExpression)>,
|
|
) -> Self {
|
|
if inner.is_empty() {
|
|
return Self::empty();
|
|
}
|
|
Self::Group {
|
|
inner: Box::new(inner),
|
|
variables,
|
|
aggregates,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn service(inner: Self, name: NamedNodePattern, silent: bool) -> Self {
|
|
if inner.is_empty() {
|
|
return Self::empty();
|
|
}
|
|
Self::Service {
|
|
inner: Box::new(inner),
|
|
name,
|
|
silent,
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "fixed-point")]
|
|
pub(crate) fn fixed_point(
|
|
id: FixedPointId,
|
|
inner: FixedPointGraphPattern,
|
|
variables: Vec<Variable>,
|
|
) -> Self {
|
|
FixedPointGraphPattern::fixed_point(id, inner, variables)
|
|
.try_into()
|
|
.unwrap()
|
|
}
|
|
|
|
fn from_sparql_algebra(
|
|
pattern: &AlGraphPattern,
|
|
graph_name: Option<&NamedNodePattern>,
|
|
blank_nodes: &mut HashMap<BlankNode, Variable>,
|
|
) -> Self {
|
|
match pattern {
|
|
AlGraphPattern::Bgp { patterns } => patterns
|
|
.iter()
|
|
.map(|p| {
|
|
let (subject, predicate, object) =
|
|
Self::triple_pattern_from_algebra(p, blank_nodes);
|
|
Self::QuadPattern {
|
|
subject,
|
|
predicate,
|
|
object,
|
|
graph_name: graph_name.cloned(),
|
|
}
|
|
})
|
|
.reduce(|a, b| Self::Join {
|
|
left: Box::new(a),
|
|
right: Box::new(b),
|
|
})
|
|
.unwrap_or_else(Self::singleton),
|
|
AlGraphPattern::Path {
|
|
subject,
|
|
path,
|
|
object,
|
|
} => Self::Path {
|
|
subject: Self::term_pattern_from_algebra(subject, blank_nodes),
|
|
path: path.clone(),
|
|
object: Self::term_pattern_from_algebra(object, blank_nodes),
|
|
graph_name: graph_name.cloned(),
|
|
},
|
|
AlGraphPattern::Join { left, right } => Self::Join {
|
|
left: Box::new(Self::from_sparql_algebra(left, graph_name, blank_nodes)),
|
|
right: Box::new(Self::from_sparql_algebra(right, graph_name, blank_nodes)),
|
|
},
|
|
AlGraphPattern::LeftJoin {
|
|
left,
|
|
right,
|
|
expression,
|
|
} => Self::LeftJoin {
|
|
left: Box::new(Self::from_sparql_algebra(left, graph_name, blank_nodes)),
|
|
right: Box::new(Self::from_sparql_algebra(right, graph_name, blank_nodes)),
|
|
expression: expression.as_ref().map_or_else(
|
|
|| true.into(),
|
|
|e| Expression::from_sparql_algebra(e, graph_name),
|
|
),
|
|
},
|
|
#[cfg(feature = "sep-0006")]
|
|
AlGraphPattern::Lateral { left, right } => Self::Lateral {
|
|
left: Box::new(Self::from_sparql_algebra(left, graph_name, blank_nodes)),
|
|
right: Box::new(Self::from_sparql_algebra(right, graph_name, blank_nodes)),
|
|
},
|
|
AlGraphPattern::Filter { inner, expr } => Self::Filter {
|
|
inner: Box::new(Self::from_sparql_algebra(inner, graph_name, blank_nodes)),
|
|
expression: Expression::from_sparql_algebra(expr, graph_name),
|
|
},
|
|
AlGraphPattern::Union { left, right } => Self::Union {
|
|
inner: vec![
|
|
Self::from_sparql_algebra(left, graph_name, blank_nodes),
|
|
Self::from_sparql_algebra(right, graph_name, blank_nodes),
|
|
],
|
|
},
|
|
AlGraphPattern::Graph { inner, name } => {
|
|
Self::from_sparql_algebra(inner, Some(name), blank_nodes)
|
|
}
|
|
AlGraphPattern::Extend {
|
|
inner,
|
|
expression,
|
|
variable,
|
|
} => Self::Extend {
|
|
inner: Box::new(Self::from_sparql_algebra(inner, graph_name, blank_nodes)),
|
|
expression: Expression::from_sparql_algebra(expression, graph_name),
|
|
variable: variable.clone(),
|
|
},
|
|
AlGraphPattern::Minus { left, right } => Self::Minus {
|
|
left: Box::new(Self::from_sparql_algebra(left, graph_name, blank_nodes)),
|
|
right: Box::new(Self::from_sparql_algebra(right, graph_name, blank_nodes)),
|
|
},
|
|
AlGraphPattern::Values {
|
|
variables,
|
|
bindings,
|
|
} => Self::Values {
|
|
variables: variables.clone(),
|
|
bindings: bindings.clone(),
|
|
},
|
|
AlGraphPattern::OrderBy { inner, expression } => Self::OrderBy {
|
|
inner: Box::new(Self::from_sparql_algebra(inner, graph_name, blank_nodes)),
|
|
expression: expression
|
|
.iter()
|
|
.map(|e| OrderExpression::from_sparql_algebra(e, graph_name))
|
|
.collect(),
|
|
},
|
|
AlGraphPattern::Project { inner, variables } => {
|
|
let graph_name = if let Some(NamedNodePattern::Variable(graph_name)) = graph_name {
|
|
Some(NamedNodePattern::Variable(
|
|
if variables.contains(graph_name) {
|
|
graph_name.clone()
|
|
} else {
|
|
new_var()
|
|
},
|
|
))
|
|
} else {
|
|
graph_name.cloned()
|
|
};
|
|
Self::Project {
|
|
inner: Box::new(Self::from_sparql_algebra(
|
|
inner,
|
|
graph_name.as_ref(),
|
|
&mut HashMap::new(),
|
|
)),
|
|
variables: variables.clone(),
|
|
}
|
|
}
|
|
AlGraphPattern::Distinct { inner } => Self::Distinct {
|
|
inner: Box::new(Self::from_sparql_algebra(inner, graph_name, blank_nodes)),
|
|
},
|
|
AlGraphPattern::Reduced { inner } => Self::Distinct {
|
|
inner: Box::new(Self::from_sparql_algebra(inner, graph_name, blank_nodes)),
|
|
},
|
|
AlGraphPattern::Slice {
|
|
inner,
|
|
start,
|
|
length,
|
|
} => Self::Slice {
|
|
inner: Box::new(Self::from_sparql_algebra(inner, graph_name, blank_nodes)),
|
|
start: *start,
|
|
length: *length,
|
|
},
|
|
AlGraphPattern::Group {
|
|
inner,
|
|
variables,
|
|
aggregates,
|
|
} => Self::Group {
|
|
inner: Box::new(Self::from_sparql_algebra(inner, graph_name, blank_nodes)),
|
|
variables: variables.clone(),
|
|
aggregates: aggregates
|
|
.iter()
|
|
.map(|(var, expr)| {
|
|
(
|
|
var.clone(),
|
|
AggregateExpression::from_sparql_algebra(expr, graph_name),
|
|
)
|
|
})
|
|
.collect(),
|
|
},
|
|
AlGraphPattern::Service {
|
|
inner,
|
|
name,
|
|
silent,
|
|
} => Self::Service {
|
|
inner: Box::new(Self::from_sparql_algebra(inner, graph_name, blank_nodes)),
|
|
name: name.clone(),
|
|
silent: *silent,
|
|
},
|
|
}
|
|
}
|
|
|
|
pub(crate) fn triple_pattern_from_algebra(
|
|
pattern: &TriplePattern,
|
|
blank_nodes: &mut HashMap<BlankNode, Variable>,
|
|
) -> (GroundTermPattern, NamedNodePattern, GroundTermPattern) {
|
|
(
|
|
Self::term_pattern_from_algebra(&pattern.subject, blank_nodes),
|
|
pattern.predicate.clone(),
|
|
Self::term_pattern_from_algebra(&pattern.object, blank_nodes),
|
|
)
|
|
}
|
|
|
|
fn term_pattern_from_algebra(
|
|
pattern: &TermPattern,
|
|
blank_nodes: &mut HashMap<BlankNode, Variable>,
|
|
) -> GroundTermPattern {
|
|
match pattern {
|
|
TermPattern::NamedNode(node) => node.clone().into(),
|
|
TermPattern::BlankNode(node) => blank_nodes
|
|
.entry(node.clone())
|
|
.or_insert_with(new_var)
|
|
.clone()
|
|
.into(),
|
|
TermPattern::Literal(literal) => literal.clone().into(),
|
|
#[cfg(feature = "rdf-star")]
|
|
TermPattern::Triple(pattern) => {
|
|
let (subject, predicate, object) =
|
|
Self::triple_pattern_from_algebra(pattern, blank_nodes);
|
|
GroundTriplePattern {
|
|
subject,
|
|
predicate,
|
|
object,
|
|
}
|
|
.into()
|
|
}
|
|
TermPattern::Variable(variable) => variable.clone().into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<&AlGraphPattern> for GraphPattern {
|
|
fn from(pattern: &AlGraphPattern) -> Self {
|
|
Self::from_sparql_algebra(pattern, None, &mut HashMap::new())
|
|
}
|
|
}
|
|
|
|
impl From<&GraphPattern> for AlGraphPattern {
|
|
fn from(pattern: &GraphPattern) -> Self {
|
|
match pattern {
|
|
GraphPattern::QuadPattern {
|
|
subject,
|
|
predicate,
|
|
object,
|
|
graph_name,
|
|
} => {
|
|
let pattern = Self::Bgp {
|
|
patterns: vec![TriplePattern {
|
|
subject: subject.clone().into(),
|
|
predicate: predicate.clone(),
|
|
object: object.clone().into(),
|
|
}],
|
|
};
|
|
if let Some(graph_name) = graph_name {
|
|
Self::Graph {
|
|
inner: Box::new(pattern),
|
|
name: graph_name.clone(),
|
|
}
|
|
} else {
|
|
pattern
|
|
}
|
|
}
|
|
GraphPattern::Path {
|
|
subject,
|
|
path,
|
|
object,
|
|
graph_name,
|
|
} => {
|
|
let pattern = Self::Path {
|
|
subject: subject.clone().into(),
|
|
path: path.clone(),
|
|
object: object.clone().into(),
|
|
};
|
|
if let Some(graph_name) = graph_name {
|
|
Self::Graph {
|
|
inner: Box::new(pattern),
|
|
name: graph_name.clone(),
|
|
}
|
|
} else {
|
|
pattern
|
|
}
|
|
}
|
|
GraphPattern::Join { left, right } => Self::Join {
|
|
left: Box::new(left.as_ref().into()),
|
|
right: Box::new(right.as_ref().into()),
|
|
},
|
|
GraphPattern::LeftJoin {
|
|
left,
|
|
right,
|
|
expression,
|
|
} => {
|
|
let empty_expr = if let Expression::Literal(l) = expression {
|
|
l.datatype() == xsd::BOOLEAN && l.value() == "true"
|
|
} else {
|
|
false
|
|
};
|
|
Self::LeftJoin {
|
|
left: Box::new(left.as_ref().into()),
|
|
right: Box::new(right.as_ref().into()),
|
|
expression: if empty_expr {
|
|
None
|
|
} else {
|
|
Some(expression.into())
|
|
},
|
|
}
|
|
}
|
|
#[cfg(feature = "sep-0006")]
|
|
GraphPattern::Lateral { left, right } => Self::Lateral {
|
|
left: Box::new(left.as_ref().into()),
|
|
right: Box::new(right.as_ref().into()),
|
|
},
|
|
GraphPattern::Filter { inner, expression } => Self::Filter {
|
|
inner: Box::new(inner.as_ref().into()),
|
|
expr: expression.into(),
|
|
},
|
|
GraphPattern::Union { inner } => inner
|
|
.iter()
|
|
.map(|c| c.into())
|
|
.reduce(|a, b| Self::Union {
|
|
left: Box::new(a),
|
|
right: Box::new(b),
|
|
})
|
|
.unwrap_or_else(|| Self::Values {
|
|
variables: Vec::new(),
|
|
bindings: Vec::new(),
|
|
}),
|
|
GraphPattern::Extend {
|
|
inner,
|
|
expression,
|
|
variable,
|
|
} => Self::Extend {
|
|
inner: Box::new(inner.as_ref().into()),
|
|
expression: expression.into(),
|
|
variable: variable.clone(),
|
|
},
|
|
GraphPattern::Minus { left, right } => Self::Minus {
|
|
left: Box::new(left.as_ref().into()),
|
|
right: Box::new(right.as_ref().into()),
|
|
},
|
|
GraphPattern::Values {
|
|
variables,
|
|
bindings,
|
|
} => Self::Values {
|
|
variables: variables.clone(),
|
|
bindings: bindings.clone(),
|
|
},
|
|
GraphPattern::OrderBy { inner, expression } => Self::OrderBy {
|
|
inner: Box::new(inner.as_ref().into()),
|
|
expression: expression.iter().map(|e| e.into()).collect(),
|
|
},
|
|
GraphPattern::Project { inner, variables } => Self::Project {
|
|
inner: Box::new(inner.as_ref().into()),
|
|
variables: variables.clone(),
|
|
},
|
|
GraphPattern::Distinct { inner } => Self::Distinct {
|
|
inner: Box::new(inner.as_ref().into()),
|
|
},
|
|
GraphPattern::Reduced { inner } => Self::Distinct {
|
|
inner: Box::new(inner.as_ref().into()),
|
|
},
|
|
GraphPattern::Slice {
|
|
inner,
|
|
start,
|
|
length,
|
|
} => Self::Slice {
|
|
inner: Box::new(inner.as_ref().into()),
|
|
start: *start,
|
|
length: *length,
|
|
},
|
|
GraphPattern::Group {
|
|
inner,
|
|
variables,
|
|
aggregates,
|
|
} => Self::Group {
|
|
inner: Box::new(inner.as_ref().into()),
|
|
variables: variables.clone(),
|
|
aggregates: aggregates
|
|
.iter()
|
|
.map(|(var, expr)| (var.clone(), expr.into()))
|
|
.collect(),
|
|
},
|
|
GraphPattern::Service {
|
|
inner,
|
|
name,
|
|
silent,
|
|
} => Self::Service {
|
|
inner: Box::new(inner.as_ref().into()),
|
|
name: name.clone(),
|
|
silent: *silent,
|
|
},
|
|
#[cfg(feature = "fixed-point")]
|
|
GraphPattern::FixedPoint { .. } => unimplemented!(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)]
|
|
pub struct FixedPointId(pub usize);
|
|
|
|
/// A SPARQL query [graph pattern](https://www.w3.org/TR/sparql11-query/#sparqlQuery) inside of a fixed-point operation.
|
|
#[cfg(feature = "fixed-point")]
|
|
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
|
|
pub enum FixedPointGraphPattern {
|
|
/// A [basic graph pattern](https://www.w3.org/TR/sparql11-query/#defn_BasicGraphPattern).
|
|
QuadPattern {
|
|
subject: GroundTermPattern,
|
|
predicate: NamedNodePattern,
|
|
object: GroundTermPattern,
|
|
graph_name: Option<NamedNodePattern>,
|
|
},
|
|
/// [Join](https://www.w3.org/TR/sparql11-query/#defn_algJoin).
|
|
Join {
|
|
left: Box<Self>,
|
|
right: Box<Self>,
|
|
},
|
|
/// [Filter](https://www.w3.org/TR/sparql11-query/#defn_algFilter).
|
|
Filter {
|
|
expression: FixedPointExpression,
|
|
inner: Box<Self>,
|
|
},
|
|
/// [Union](https://www.w3.org/TR/sparql11-query/#defn_algUnion).
|
|
Union {
|
|
inner: Vec<Self>,
|
|
},
|
|
/// [Extend](https://www.w3.org/TR/sparql11-query/#defn_extend).
|
|
Extend {
|
|
inner: Box<Self>,
|
|
variable: Variable,
|
|
expression: FixedPointExpression,
|
|
},
|
|
/// A table used to provide inline values
|
|
Values {
|
|
variables: Vec<Variable>,
|
|
bindings: Vec<Vec<Option<GroundTerm>>>,
|
|
},
|
|
/// [Project](https://www.w3.org/TR/sparql11-query/#defn_algProjection).
|
|
Project {
|
|
inner: Box<Self>,
|
|
variables: Vec<Variable>,
|
|
},
|
|
/// Inner fixed point operator
|
|
FixedPoint {
|
|
id: FixedPointId,
|
|
/// invariant: the inner plans should always return the all the listed variables and only them
|
|
variables: Vec<Variable>,
|
|
constant: Box<Self>,
|
|
recursive: Box<Self>,
|
|
},
|
|
FixedPointEntry(FixedPointId),
|
|
}
|
|
|
|
#[cfg(feature = "fixed-point")]
|
|
impl FixedPointGraphPattern {
|
|
pub(crate) fn empty() -> Self {
|
|
Self::Values {
|
|
variables: Vec::new(),
|
|
bindings: Vec::new(),
|
|
}
|
|
}
|
|
|
|
/// Check if the pattern is the empty table
|
|
fn is_empty(&self) -> bool {
|
|
if let Self::Values { bindings, .. } = self {
|
|
bindings.is_empty()
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
pub(crate) fn singleton() -> Self {
|
|
Self::Values {
|
|
variables: Vec::new(),
|
|
bindings: vec![Vec::new()],
|
|
}
|
|
}
|
|
|
|
pub(crate) fn join(left: Self, right: Self) -> Self {
|
|
if left.is_empty() || right.is_empty() {
|
|
return Self::empty();
|
|
}
|
|
Self::Join {
|
|
left: Box::new(left),
|
|
right: Box::new(right),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn union(left: Self, right: Self) -> Self {
|
|
if left.is_empty() {
|
|
return right;
|
|
}
|
|
if right.is_empty() {
|
|
return left;
|
|
}
|
|
Self::Union {
|
|
inner: match (left, right) {
|
|
(Self::Union { inner: mut left }, Self::Union { inner: right }) => {
|
|
left.extend(right);
|
|
left
|
|
}
|
|
(Self::Union { inner: mut left }, right) => {
|
|
left.push(right);
|
|
left
|
|
}
|
|
(left, Self::Union { inner: mut right }) => {
|
|
right.insert(0, left);
|
|
right
|
|
}
|
|
(left, right) => vec![left, right],
|
|
},
|
|
}
|
|
}
|
|
|
|
pub(crate) fn filter(inner: Self, expression: FixedPointExpression) -> Self {
|
|
if inner.is_empty() {
|
|
return Self::empty();
|
|
}
|
|
match expression.effective_boolean_value() {
|
|
Some(true) => inner,
|
|
Some(false) => Self::empty(),
|
|
None => Self::Filter {
|
|
inner: Box::new(inner),
|
|
expression,
|
|
},
|
|
}
|
|
}
|
|
|
|
pub(crate) fn extend(
|
|
inner: Self,
|
|
variable: Variable,
|
|
expression: FixedPointExpression,
|
|
) -> Self {
|
|
if inner.is_empty() {
|
|
return Self::empty();
|
|
}
|
|
Self::Extend {
|
|
inner: Box::new(inner),
|
|
variable,
|
|
expression,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn values(
|
|
mut variables: Vec<Variable>,
|
|
mut bindings: Vec<Vec<Option<GroundTerm>>>,
|
|
) -> Self {
|
|
let empty_rows = (0..variables.len())
|
|
.filter(|row| !bindings.iter().any(|binding| binding.get(*row).is_some()))
|
|
.collect::<Vec<_>>();
|
|
if !empty_rows.is_empty() {
|
|
// We remove empty rows
|
|
variables = variables
|
|
.into_iter()
|
|
.enumerate()
|
|
.filter_map(|(i, v)| {
|
|
if empty_rows.contains(&i) {
|
|
None
|
|
} else {
|
|
Some(v)
|
|
}
|
|
})
|
|
.collect();
|
|
bindings = bindings
|
|
.into_iter()
|
|
.map(|binding| {
|
|
binding
|
|
.into_iter()
|
|
.enumerate()
|
|
.filter_map(|(i, v)| {
|
|
if empty_rows.contains(&i) {
|
|
None
|
|
} else {
|
|
Some(v)
|
|
}
|
|
})
|
|
.collect()
|
|
})
|
|
.collect();
|
|
}
|
|
Self::Values {
|
|
variables,
|
|
bindings,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn project(inner: Self, variables: Vec<Variable>) -> Self {
|
|
if inner.is_empty() {
|
|
return Self::empty();
|
|
}
|
|
Self::Project {
|
|
inner: Box::new(inner),
|
|
variables,
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "fixed-point")]
|
|
pub(crate) fn fixed_point(id: FixedPointId, inner: Self, variables: Vec<Variable>) -> Self {
|
|
if inner.is_empty() {
|
|
return Self::empty();
|
|
}
|
|
let children = if let Self::Union { inner } = inner {
|
|
inner
|
|
} else {
|
|
vec![inner]
|
|
};
|
|
let mut constant = Self::empty();
|
|
let mut recursive = Self::empty();
|
|
for child in children {
|
|
if child.is_recursion_used(&id) {
|
|
recursive = Self::union(recursive, child);
|
|
} else {
|
|
constant = Self::union(constant, child);
|
|
}
|
|
}
|
|
if recursive.is_empty() {
|
|
return constant;
|
|
}
|
|
Self::FixedPoint {
|
|
id,
|
|
variables,
|
|
constant: Box::new(constant),
|
|
recursive: Box::new(recursive),
|
|
}
|
|
}
|
|
|
|
fn is_recursion_used(&self, id: &FixedPointId) -> bool {
|
|
match self {
|
|
Self::QuadPattern { .. } | Self::Values { .. } => false,
|
|
Self::Filter { inner, .. }
|
|
| Self::Extend { inner, .. }
|
|
| Self::Project { inner, .. } => Self::is_recursion_used(inner, id),
|
|
Self::Join { left, right } => {
|
|
Self::is_recursion_used(left, id) || Self::is_recursion_used(right, id)
|
|
}
|
|
Self::Union { inner } => inner.iter().any(|p| Self::is_recursion_used(p, id)),
|
|
Self::FixedPoint {
|
|
constant,
|
|
recursive,
|
|
..
|
|
} => Self::is_recursion_used(constant, id) || Self::is_recursion_used(recursive, id),
|
|
Self::FixedPointEntry(tid) => id == tid,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<FixedPointGraphPattern> for GraphPattern {
|
|
type Error = ();
|
|
|
|
fn try_from(pattern: FixedPointGraphPattern) -> Result<Self, ()> {
|
|
Ok(match pattern {
|
|
FixedPointGraphPattern::QuadPattern {
|
|
subject,
|
|
predicate,
|
|
object,
|
|
graph_name,
|
|
} => Self::QuadPattern {
|
|
subject,
|
|
predicate,
|
|
object,
|
|
graph_name,
|
|
},
|
|
FixedPointGraphPattern::Join { left, right } => Self::Join {
|
|
left: Box::new((*left).try_into()?),
|
|
right: Box::new((*right).try_into()?),
|
|
},
|
|
FixedPointGraphPattern::Union { inner } => Self::Union {
|
|
inner: inner
|
|
.into_iter()
|
|
.map(TryInto::try_into)
|
|
.collect::<Result<_, ()>>()?,
|
|
},
|
|
FixedPointGraphPattern::Filter { inner, expression } => Self::Filter {
|
|
expression: expression.into(),
|
|
inner: Box::new((*inner).try_into()?),
|
|
},
|
|
FixedPointGraphPattern::Extend {
|
|
inner,
|
|
variable,
|
|
expression,
|
|
} => Self::Extend {
|
|
expression: expression.into(),
|
|
variable,
|
|
inner: Box::new((*inner).try_into()?),
|
|
},
|
|
FixedPointGraphPattern::Values {
|
|
variables,
|
|
bindings,
|
|
} => Self::Values {
|
|
variables,
|
|
bindings,
|
|
},
|
|
FixedPointGraphPattern::Project { inner, variables } => Self::Project {
|
|
variables,
|
|
inner: Box::new((*inner).try_into()?),
|
|
},
|
|
FixedPointGraphPattern::FixedPoint {
|
|
id,
|
|
variables,
|
|
constant,
|
|
recursive,
|
|
} => Self::FixedPoint {
|
|
id,
|
|
variables,
|
|
constant,
|
|
recursive,
|
|
},
|
|
FixedPointGraphPattern::FixedPointEntry(_) => return Err(()),
|
|
})
|
|
}
|
|
}
|
|
|
|
/// An [expression](https://www.w3.org/TR/sparql11-query/#expressions) inside of a fix point.
|
|
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
|
|
pub enum FixedPointExpression {
|
|
NamedNode(NamedNode),
|
|
Literal(Literal),
|
|
Variable(Variable),
|
|
/// [Logical-or](https://www.w3.org/TR/sparql11-query/#func-logical-or).
|
|
Or(Box<Self>, Box<Self>),
|
|
/// [Logical-and](https://www.w3.org/TR/sparql11-query/#func-logical-and).
|
|
And(Box<Self>, Box<Self>),
|
|
/// [sameTerm](https://www.w3.org/TR/sparql11-query/#func-sameTerm).
|
|
SameTerm(Box<Self>, Box<Self>),
|
|
/// [fn:not](https://www.w3.org/TR/xpath-functions/#func-not).
|
|
Not(Box<Self>),
|
|
/// A regular function call.
|
|
FunctionCall(Function, Vec<Self>),
|
|
}
|
|
|
|
impl From<NamedNode> for FixedPointExpression {
|
|
fn from(value: NamedNode) -> Self {
|
|
Self::NamedNode(value)
|
|
}
|
|
}
|
|
|
|
impl From<Literal> for FixedPointExpression {
|
|
fn from(value: Literal) -> Self {
|
|
Self::Literal(value)
|
|
}
|
|
}
|
|
|
|
impl From<GroundSubject> for FixedPointExpression {
|
|
fn from(value: GroundSubject) -> Self {
|
|
match value {
|
|
GroundSubject::NamedNode(value) => value.into(),
|
|
#[cfg(feature = "rdf-star")]
|
|
GroundSubject::Triple(value) => (*value).into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<GroundTerm> for FixedPointExpression {
|
|
fn from(value: GroundTerm) -> Self {
|
|
match value {
|
|
GroundTerm::NamedNode(value) => value.into(),
|
|
GroundTerm::Literal(value) => value.into(),
|
|
#[cfg(feature = "rdf-star")]
|
|
GroundTerm::Triple(value) => (*value).into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<NamedNodePattern> for FixedPointExpression {
|
|
fn from(value: NamedNodePattern) -> Self {
|
|
match value {
|
|
NamedNodePattern::NamedNode(value) => value.into(),
|
|
NamedNodePattern::Variable(variable) => variable.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<GroundTermPattern> for FixedPointExpression {
|
|
fn from(value: GroundTermPattern) -> Self {
|
|
match value {
|
|
GroundTermPattern::NamedNode(value) => value.into(),
|
|
GroundTermPattern::Literal(value) => value.into(),
|
|
#[cfg(feature = "rdf-star")]
|
|
GroundTermPattern::Triple(value) => (*value).into(),
|
|
GroundTermPattern::Variable(variable) => variable.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "rdf-star")]
|
|
impl From<GroundTriple> for FixedPointExpression {
|
|
fn from(value: GroundTriple) -> Self {
|
|
Self::FunctionCall(
|
|
Function::Triple,
|
|
vec![
|
|
value.subject.into(),
|
|
value.predicate.into(),
|
|
value.object.into(),
|
|
],
|
|
)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "rdf-star")]
|
|
impl From<GroundTriplePattern> for FixedPointExpression {
|
|
fn from(value: GroundTriplePattern) -> Self {
|
|
Self::FunctionCall(
|
|
Function::Triple,
|
|
vec![
|
|
value.subject.into(),
|
|
value.predicate.into(),
|
|
value.object.into(),
|
|
],
|
|
)
|
|
}
|
|
}
|
|
|
|
impl From<Variable> for FixedPointExpression {
|
|
fn from(value: Variable) -> Self {
|
|
Self::Variable(value)
|
|
}
|
|
}
|
|
|
|
impl From<bool> for FixedPointExpression {
|
|
fn from(value: bool) -> Self {
|
|
Literal::from(value).into()
|
|
}
|
|
}
|
|
|
|
impl FixedPointExpression {
|
|
pub(crate) fn or(left: Self, right: Self) -> Self {
|
|
match (
|
|
left.effective_boolean_value(),
|
|
right.effective_boolean_value(),
|
|
) {
|
|
(Some(true), _) | (_, Some(true)) => true.into(),
|
|
(Some(false), Some(false)) => false.into(),
|
|
_ => Self::Or(Box::new(left), Box::new(right)),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn same_term(left: Self, right: Self) -> Self {
|
|
Self::SameTerm(Box::new(left), Box::new(right))
|
|
}
|
|
|
|
pub(crate) fn not(inner: Self) -> Self {
|
|
Self::Not(Box::new(inner))
|
|
}
|
|
|
|
pub(crate) fn call(name: Function, args: Vec<Self>) -> Self {
|
|
Self::FunctionCall(name, args)
|
|
}
|
|
|
|
pub(crate) fn effective_boolean_value(&self) -> Option<bool> {
|
|
match self {
|
|
Self::NamedNode(_) => Some(true),
|
|
Self::Literal(literal) => {
|
|
if literal.datatype() == xsd::BOOLEAN {
|
|
match literal.value() {
|
|
"true" | "1" => Some(true),
|
|
"false" | "0" => Some(false),
|
|
_ => None, //TODO
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
_ => None, // We assume the expression has been normalized
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<FixedPointExpression> for Expression {
|
|
fn from(expression: FixedPointExpression) -> Self {
|
|
match expression {
|
|
FixedPointExpression::NamedNode(node) => Self::NamedNode(node),
|
|
FixedPointExpression::Literal(literal) => Self::Literal(literal),
|
|
FixedPointExpression::Variable(variable) => Self::Variable(variable),
|
|
FixedPointExpression::Or(left, right) => {
|
|
Self::Or(Box::new((*left).into()), Box::new((*right).into()))
|
|
}
|
|
FixedPointExpression::And(left, right) => {
|
|
Self::And(Box::new((*left).into()), Box::new((*right).into()))
|
|
}
|
|
FixedPointExpression::SameTerm(left, right) => {
|
|
Self::SameTerm(Box::new((*left).into()), Box::new((*right).into()))
|
|
}
|
|
FixedPointExpression::Not(inner) => Self::Not(Box::new((*inner).into())),
|
|
FixedPointExpression::FunctionCall(name, args) => {
|
|
Self::FunctionCall(name, args.into_iter().map(Into::into).collect())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A set function used in aggregates (c.f. [`GraphPattern::Group`]).
|
|
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
|
|
pub enum AggregateExpression {
|
|
/// [Count](https://www.w3.org/TR/sparql11-query/#defn_aggCount).
|
|
Count {
|
|
expr: Option<Box<Expression>>,
|
|
distinct: bool,
|
|
},
|
|
/// [Sum](https://www.w3.org/TR/sparql11-query/#defn_aggSum).
|
|
Sum {
|
|
expr: Box<Expression>,
|
|
distinct: bool,
|
|
},
|
|
/// [Avg](https://www.w3.org/TR/sparql11-query/#defn_aggAvg).
|
|
Avg {
|
|
expr: Box<Expression>,
|
|
distinct: bool,
|
|
},
|
|
/// [Min](https://www.w3.org/TR/sparql11-query/#defn_aggMin).
|
|
Min {
|
|
expr: Box<Expression>,
|
|
distinct: bool,
|
|
},
|
|
/// [Max](https://www.w3.org/TR/sparql11-query/#defn_aggMax).
|
|
Max {
|
|
expr: Box<Expression>,
|
|
distinct: bool,
|
|
},
|
|
/// [GroupConcat](https://www.w3.org/TR/sparql11-query/#defn_aggGroupConcat).
|
|
GroupConcat {
|
|
expr: Box<Expression>,
|
|
distinct: bool,
|
|
separator: Option<String>,
|
|
},
|
|
/// [Sample](https://www.w3.org/TR/sparql11-query/#defn_aggSample).
|
|
Sample {
|
|
expr: Box<Expression>,
|
|
distinct: bool,
|
|
},
|
|
/// Custom function.
|
|
Custom {
|
|
name: NamedNode,
|
|
expr: Box<Expression>,
|
|
distinct: bool,
|
|
},
|
|
}
|
|
|
|
impl AggregateExpression {
|
|
fn from_sparql_algebra(
|
|
expression: &AlAggregateExpression,
|
|
graph_name: Option<&NamedNodePattern>,
|
|
) -> Self {
|
|
match expression {
|
|
AlAggregateExpression::Count { expr, distinct } => Self::Count {
|
|
expr: expr
|
|
.as_ref()
|
|
.map(|e| Box::new(Expression::from_sparql_algebra(e, graph_name))),
|
|
distinct: *distinct,
|
|
},
|
|
AlAggregateExpression::Sum { expr, distinct } => Self::Sum {
|
|
expr: Box::new(Expression::from_sparql_algebra(expr, graph_name)),
|
|
distinct: *distinct,
|
|
},
|
|
AlAggregateExpression::Avg { expr, distinct } => Self::Avg {
|
|
expr: Box::new(Expression::from_sparql_algebra(expr, graph_name)),
|
|
distinct: *distinct,
|
|
},
|
|
AlAggregateExpression::Min { expr, distinct } => Self::Min {
|
|
expr: Box::new(Expression::from_sparql_algebra(expr, graph_name)),
|
|
distinct: *distinct,
|
|
},
|
|
AlAggregateExpression::Max { expr, distinct } => Self::Max {
|
|
expr: Box::new(Expression::from_sparql_algebra(expr, graph_name)),
|
|
distinct: *distinct,
|
|
},
|
|
AlAggregateExpression::GroupConcat {
|
|
expr,
|
|
distinct,
|
|
separator,
|
|
} => Self::GroupConcat {
|
|
expr: Box::new(Expression::from_sparql_algebra(expr, graph_name)),
|
|
distinct: *distinct,
|
|
separator: separator.clone(),
|
|
},
|
|
AlAggregateExpression::Sample { expr, distinct } => Self::Sample {
|
|
expr: Box::new(Expression::from_sparql_algebra(expr, graph_name)),
|
|
distinct: *distinct,
|
|
},
|
|
AlAggregateExpression::Custom {
|
|
name,
|
|
expr,
|
|
distinct,
|
|
} => Self::Custom {
|
|
name: name.clone(),
|
|
expr: Box::new(Expression::from_sparql_algebra(expr, graph_name)),
|
|
distinct: *distinct,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<&AggregateExpression> for AlAggregateExpression {
|
|
fn from(expression: &AggregateExpression) -> Self {
|
|
match expression {
|
|
AggregateExpression::Count { expr, distinct } => Self::Count {
|
|
expr: expr.as_ref().map(|e| Box::new(e.as_ref().into())),
|
|
distinct: *distinct,
|
|
},
|
|
AggregateExpression::Sum { expr, distinct } => Self::Sum {
|
|
expr: Box::new(expr.as_ref().into()),
|
|
distinct: *distinct,
|
|
},
|
|
AggregateExpression::Avg { expr, distinct } => Self::Avg {
|
|
expr: Box::new(expr.as_ref().into()),
|
|
distinct: *distinct,
|
|
},
|
|
AggregateExpression::Min { expr, distinct } => Self::Min {
|
|
expr: Box::new(expr.as_ref().into()),
|
|
distinct: *distinct,
|
|
},
|
|
AggregateExpression::Max { expr, distinct } => Self::Max {
|
|
expr: Box::new(expr.as_ref().into()),
|
|
distinct: *distinct,
|
|
},
|
|
AggregateExpression::GroupConcat {
|
|
expr,
|
|
distinct,
|
|
separator,
|
|
} => Self::GroupConcat {
|
|
expr: Box::new(expr.as_ref().into()),
|
|
distinct: *distinct,
|
|
separator: separator.clone(),
|
|
},
|
|
AggregateExpression::Sample { expr, distinct } => Self::Sample {
|
|
expr: Box::new(expr.as_ref().into()),
|
|
distinct: *distinct,
|
|
},
|
|
AggregateExpression::Custom {
|
|
name,
|
|
expr,
|
|
distinct,
|
|
} => Self::Custom {
|
|
name: name.clone(),
|
|
expr: Box::new(expr.as_ref().into()),
|
|
distinct: *distinct,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An ordering comparator used by [`GraphPattern::OrderBy`].
|
|
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
|
|
pub enum OrderExpression {
|
|
/// Ascending order
|
|
Asc(Expression),
|
|
/// Descending order
|
|
Desc(Expression),
|
|
}
|
|
|
|
impl OrderExpression {
|
|
fn from_sparql_algebra(
|
|
expression: &AlOrderExpression,
|
|
graph_name: Option<&NamedNodePattern>,
|
|
) -> Self {
|
|
match expression {
|
|
AlOrderExpression::Asc(e) => Self::Asc(Expression::from_sparql_algebra(e, graph_name)),
|
|
AlOrderExpression::Desc(e) => {
|
|
Self::Desc(Expression::from_sparql_algebra(e, graph_name))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<&OrderExpression> for AlOrderExpression {
|
|
fn from(expression: &OrderExpression) -> Self {
|
|
match expression {
|
|
OrderExpression::Asc(e) => Self::Asc(e.into()),
|
|
OrderExpression::Desc(e) => Self::Desc(e.into()),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn new_var() -> Variable {
|
|
Variable::new_unchecked(format!("{:x}", random::<u128>()))
|
|
}
|
|
|