use super::binding::{Binding, Value, Var};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Triple {
pub subject: Pattern,
pub predicate: Pattern,
pub object: Pattern,
}
impl Triple {
pub fn new(subject: Pattern, predicate: Pattern, object: Pattern) -> Self {
Self {
subject,
predicate,
object,
}
}
pub fn vars(&self) -> Vec<Var> {
let mut vars = Vec::new();
if let Pattern::Var(v) = &self.subject {
vars.push(v.clone());
}
if let Pattern::Var(v) = &self.predicate {
vars.push(v.clone());
}
if let Pattern::Var(v) = &self.object {
vars.push(v.clone());
}
vars
}
pub fn is_concrete(&self) -> bool {
!matches!(self.subject, Pattern::Var(_))
&& !matches!(self.predicate, Pattern::Var(_))
&& !matches!(self.object, Pattern::Var(_))
}
}
impl fmt::Display for Triple {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({} {} {})", self.subject, self.predicate, self.object)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Pattern {
Var(Var),
Uri(String),
Literal(String),
TypedLiteral(String, String),
Any,
}
impl Pattern {
pub fn is_var(&self) -> bool {
matches!(self, Pattern::Var(_))
}
pub fn as_var(&self) -> Option<&Var> {
match self {
Pattern::Var(v) => Some(v),
_ => None,
}
}
pub fn to_value(&self) -> Option<Value> {
match self {
Pattern::Var(_) => None,
Pattern::Uri(s) => Some(Value::Uri(s.clone())),
Pattern::Literal(s) => Some(Value::String(s.clone())),
Pattern::TypedLiteral(v, _) => Some(Value::String(v.clone())),
Pattern::Any => None,
}
}
}
impl fmt::Display for Pattern {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Pattern::Var(v) => write!(f, "{}", v),
Pattern::Uri(s) => write!(f, "<{}>", s),
Pattern::Literal(s) => write!(f, "\"{}\"", s),
Pattern::TypedLiteral(v, t) => write!(f, "\"{}\"^^<{}>", v, t),
Pattern::Any => write!(f, "_"),
}
}
}
#[derive(Debug, Clone)]
pub enum Op {
BGP(OpBGP),
Triple(OpTriple),
Join(OpJoin),
LeftJoin(OpLeftJoin),
RightJoin(OpRightJoin),
CrossJoin(OpCrossJoin),
Filter(OpFilter),
Union(OpUnion),
Project(OpProject),
Distinct(OpDistinct),
Reduced(OpReduced),
Slice(OpSlice),
Order(OpOrder),
Group(OpGroup),
Extend(OpExtend),
Minus(OpMinus),
Intersect(OpIntersect),
Table(OpTable),
Sequence(OpSequence),
Disjunction(OpDisjunction),
Null(OpNull),
}
impl Op {
pub fn vars(&self) -> Vec<Var> {
match self {
Op::BGP(op) => op.vars(),
Op::Triple(op) => op.triple.vars(),
Op::Join(op) => {
let mut vars = op.left.vars();
for v in op.right.vars() {
if !vars.contains(&v) {
vars.push(v);
}
}
vars
}
Op::LeftJoin(op) => {
let mut vars = op.left.vars();
for v in op.right.vars() {
if !vars.contains(&v) {
vars.push(v);
}
}
vars
}
Op::RightJoin(op) => {
let mut vars = op.left.vars();
for v in op.right.vars() {
if !vars.contains(&v) {
vars.push(v);
}
}
vars
}
Op::CrossJoin(op) => {
let mut vars = op.left.vars();
for v in op.right.vars() {
if !vars.contains(&v) {
vars.push(v);
}
}
vars
}
Op::Filter(op) => op.sub_op.vars(),
Op::Union(op) => {
let mut vars = op.left.vars();
for v in op.right.vars() {
if !vars.contains(&v) {
vars.push(v);
}
}
vars
}
Op::Project(op) => op.vars.clone(),
Op::Distinct(op) => op.sub_op.vars(),
Op::Reduced(op) => op.sub_op.vars(),
Op::Slice(op) => op.sub_op.vars(),
Op::Order(op) => op.sub_op.vars(),
Op::Group(op) => {
let mut vars = op.group_vars.clone();
for (v, _) in &op.aggregates {
vars.push(v.clone());
}
vars
}
Op::Extend(op) => {
let mut vars = op.sub_op.vars();
if !vars.contains(&op.var) {
vars.push(op.var.clone());
}
vars
}
Op::Minus(op) => op.left.vars(),
Op::Intersect(op) => {
let left_vars = op.left.vars();
let right_vars = op.right.vars();
left_vars
.into_iter()
.filter(|v| right_vars.contains(v))
.collect()
}
Op::Table(op) => op.vars.clone(),
Op::Sequence(op) => {
let mut vars = Vec::new();
for sub in &op.ops {
for v in sub.vars() {
if !vars.contains(&v) {
vars.push(v);
}
}
}
vars
}
Op::Disjunction(op) => {
let mut vars = Vec::new();
for sub in &op.ops {
for v in sub.vars() {
if !vars.contains(&v) {
vars.push(v);
}
}
}
vars
}
Op::Null(_) => Vec::new(),
}
}
pub fn is_null(&self) -> bool {
matches!(self, Op::Null(_))
}
}
#[derive(Debug, Clone)]
pub struct OpBGP {
pub triples: Vec<Triple>,
}
impl OpBGP {
pub fn new() -> Self {
Self {
triples: Vec::new(),
}
}
pub fn from_triples(triples: Vec<Triple>) -> Self {
Self { triples }
}
pub fn add(&mut self, triple: Triple) {
self.triples.push(triple);
}
pub fn vars(&self) -> Vec<Var> {
let mut vars = Vec::new();
for triple in &self.triples {
for v in triple.vars() {
if !vars.contains(&v) {
vars.push(v);
}
}
}
vars
}
pub fn is_empty(&self) -> bool {
self.triples.is_empty()
}
}
impl Default for OpBGP {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct OpTriple {
pub triple: Triple,
}
impl OpTriple {
pub fn new(triple: Triple) -> Self {
Self { triple }
}
}
#[derive(Debug, Clone)]
pub struct OpJoin {
pub left: Box<Op>,
pub right: Box<Op>,
}
impl OpJoin {
pub fn new(left: Op, right: Op) -> Self {
Self {
left: Box::new(left),
right: Box::new(right),
}
}
pub fn join_all(ops: Vec<Op>) -> Op {
if ops.is_empty() {
return Op::Null(OpNull);
}
let mut result = ops.into_iter();
let mut current = result.next().unwrap();
for op in result {
current = Op::Join(OpJoin::new(current, op));
}
current
}
}
#[derive(Debug, Clone)]
pub struct OpLeftJoin {
pub left: Box<Op>,
pub right: Box<Op>,
pub filter: Option<FilterExpr>,
}
impl OpLeftJoin {
pub fn new(left: Op, right: Op) -> Self {
Self {
left: Box::new(left),
right: Box::new(right),
filter: None,
}
}
pub fn with_filter(left: Op, right: Op, filter: FilterExpr) -> Self {
Self {
left: Box::new(left),
right: Box::new(right),
filter: Some(filter),
}
}
}
#[derive(Debug, Clone)]
pub struct OpRightJoin {
pub left: Box<Op>,
pub right: Box<Op>,
pub filter: Option<FilterExpr>,
}
impl OpRightJoin {
pub fn new(left: Op, right: Op) -> Self {
Self {
left: Box::new(left),
right: Box::new(right),
filter: None,
}
}
pub fn with_filter(left: Op, right: Op, filter: FilterExpr) -> Self {
Self {
left: Box::new(left),
right: Box::new(right),
filter: Some(filter),
}
}
}
#[derive(Debug, Clone)]
pub struct OpCrossJoin {
pub left: Box<Op>,
pub right: Box<Op>,
}
impl OpCrossJoin {
pub fn new(left: Op, right: Op) -> Self {
Self {
left: Box::new(left),
right: Box::new(right),
}
}
}
#[derive(Debug, Clone)]
pub enum FilterExpr {
Eq(ExprTerm, ExprTerm),
NotEq(ExprTerm, ExprTerm),
Lt(ExprTerm, ExprTerm),
LtEq(ExprTerm, ExprTerm),
Gt(ExprTerm, ExprTerm),
GtEq(ExprTerm, ExprTerm),
And(Box<FilterExpr>, Box<FilterExpr>),
Or(Box<FilterExpr>, Box<FilterExpr>),
Not(Box<FilterExpr>),
Bound(Var),
Regex(ExprTerm, String, Option<String>),
StartsWith(ExprTerm, String),
EndsWith(ExprTerm, String),
Contains(ExprTerm, String),
IsUri(ExprTerm),
IsLiteral(ExprTerm),
IsBlank(ExprTerm),
In(ExprTerm, Vec<ExprTerm>),
NotIn(ExprTerm, Vec<ExprTerm>),
True,
False,
}
impl FilterExpr {
pub fn and(left: FilterExpr, right: FilterExpr) -> FilterExpr {
FilterExpr::And(Box::new(left), Box::new(right))
}
pub fn or(left: FilterExpr, right: FilterExpr) -> FilterExpr {
FilterExpr::Or(Box::new(left), Box::new(right))
}
pub fn not(expr: FilterExpr) -> FilterExpr {
FilterExpr::Not(Box::new(expr))
}
pub fn evaluate(&self, binding: &Binding) -> bool {
match self {
FilterExpr::Eq(left, right) => {
let l = left.evaluate(binding);
let r = right.evaluate(binding);
l == r
}
FilterExpr::NotEq(left, right) => {
let l = left.evaluate(binding);
let r = right.evaluate(binding);
l != r
}
FilterExpr::Lt(left, right) => compare_terms(left, right, binding, |a, b| a < b),
FilterExpr::LtEq(left, right) => compare_terms(left, right, binding, |a, b| a <= b),
FilterExpr::Gt(left, right) => compare_terms(left, right, binding, |a, b| a > b),
FilterExpr::GtEq(left, right) => compare_terms(left, right, binding, |a, b| a >= b),
FilterExpr::And(left, right) => left.evaluate(binding) && right.evaluate(binding),
FilterExpr::Or(left, right) => left.evaluate(binding) || right.evaluate(binding),
FilterExpr::Not(expr) => !expr.evaluate(binding),
FilterExpr::Bound(var) => binding.contains(var),
FilterExpr::Regex(term, pattern, flags) => {
if let Some(Value::String(s)) = term.evaluate(binding) {
let case_insensitive = flags.as_ref().map(|f| f.contains('i')).unwrap_or(false);
if case_insensitive {
s.to_lowercase().contains(&pattern.to_lowercase())
} else {
s.contains(pattern)
}
} else {
false
}
}
FilterExpr::StartsWith(term, prefix) => {
if let Some(Value::String(s)) = term.evaluate(binding) {
s.starts_with(prefix)
} else {
false
}
}
FilterExpr::EndsWith(term, suffix) => {
if let Some(Value::String(s)) = term.evaluate(binding) {
s.ends_with(suffix)
} else {
false
}
}
FilterExpr::Contains(term, substring) => {
if let Some(Value::String(s)) = term.evaluate(binding) {
s.contains(substring)
} else {
false
}
}
FilterExpr::IsUri(term) => {
matches!(term.evaluate(binding), Some(Value::Uri(_)))
}
FilterExpr::IsLiteral(term) => {
matches!(
term.evaluate(binding),
Some(
Value::String(_) | Value::Integer(_) | Value::Float(_) | Value::Boolean(_)
)
)
}
FilterExpr::IsBlank(term) => {
if let Some(Value::Node(id)) = term.evaluate(binding) {
id.starts_with("_:")
} else {
false
}
}
FilterExpr::In(term, list) => {
if let Some(val) = term.evaluate(binding) {
list.iter()
.any(|t| t.evaluate(binding) == Some(val.clone()))
} else {
false
}
}
FilterExpr::NotIn(term, list) => {
if let Some(val) = term.evaluate(binding) {
!list
.iter()
.any(|t| t.evaluate(binding) == Some(val.clone()))
} else {
true
}
}
FilterExpr::True => true,
FilterExpr::False => false,
}
}
}
fn compare_terms<F>(left: &ExprTerm, right: &ExprTerm, binding: &Binding, cmp: F) -> bool
where
F: Fn(i64, i64) -> bool,
{
match (left.evaluate(binding), right.evaluate(binding)) {
(Some(Value::Integer(a)), Some(Value::Integer(b))) => cmp(a, b),
(Some(Value::Float(a)), Some(Value::Float(b))) => cmp(a as i64, b as i64),
(Some(Value::Integer(a)), Some(Value::Float(b))) => cmp(a, b as i64),
(Some(Value::Float(a)), Some(Value::Integer(b))) => cmp(a as i64, b),
_ => false,
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ExprTerm {
Var(Var),
Const(Value),
Str(Box<ExprTerm>),
LCase(Box<ExprTerm>),
UCase(Box<ExprTerm>),
StrLen(Box<ExprTerm>),
Concat(Vec<ExprTerm>),
}
impl ExprTerm {
pub fn evaluate(&self, binding: &Binding) -> Option<Value> {
match self {
ExprTerm::Var(var) => binding.get(var).cloned(),
ExprTerm::Const(val) => Some(val.clone()),
ExprTerm::Str(inner) => inner
.evaluate(binding)
.map(|v| Value::String(format!("{}", v))),
ExprTerm::LCase(inner) => {
if let Some(Value::String(s)) = inner.evaluate(binding) {
Some(Value::String(s.to_lowercase()))
} else {
None
}
}
ExprTerm::UCase(inner) => {
if let Some(Value::String(s)) = inner.evaluate(binding) {
Some(Value::String(s.to_uppercase()))
} else {
None
}
}
ExprTerm::StrLen(inner) => {
if let Some(Value::String(s)) = inner.evaluate(binding) {
Some(Value::Integer(s.len() as i64))
} else {
None
}
}
ExprTerm::Concat(terms) => {
let mut result = String::new();
for term in terms {
if let Some(Value::String(s)) = term.evaluate(binding) {
result.push_str(&s);
} else if let Some(v) = term.evaluate(binding) {
result.push_str(&format!("{}", v));
}
}
Some(Value::String(result))
}
}
}
}
#[derive(Debug, Clone)]
pub struct OpFilter {
pub filter: FilterExpr,
pub sub_op: Box<Op>,
}
impl OpFilter {
pub fn new(filter: FilterExpr, sub_op: Op) -> Self {
Self {
filter,
sub_op: Box::new(sub_op),
}
}
}
#[derive(Debug, Clone)]
pub struct OpUnion {
pub left: Box<Op>,
pub right: Box<Op>,
}
impl OpUnion {
pub fn new(left: Op, right: Op) -> Self {
Self {
left: Box::new(left),
right: Box::new(right),
}
}
}
#[derive(Debug, Clone)]
pub struct OpProject {
pub vars: Vec<Var>,
pub sub_op: Box<Op>,
}
impl OpProject {
pub fn new(vars: Vec<Var>, sub_op: Op) -> Self {
Self {
vars,
sub_op: Box::new(sub_op),
}
}
}
#[derive(Debug, Clone)]
pub struct OpDistinct {
pub sub_op: Box<Op>,
}
impl OpDistinct {
pub fn new(sub_op: Op) -> Self {
Self {
sub_op: Box::new(sub_op),
}
}
}
#[derive(Debug, Clone)]
pub struct OpReduced {
pub sub_op: Box<Op>,
}
impl OpReduced {
pub fn new(sub_op: Op) -> Self {
Self {
sub_op: Box::new(sub_op),
}
}
}
#[derive(Debug, Clone)]
pub struct OpSlice {
pub sub_op: Box<Op>,
pub offset: u64,
pub limit: Option<u64>,
}
impl OpSlice {
pub fn new(sub_op: Op, offset: u64, limit: Option<u64>) -> Self {
Self {
sub_op: Box::new(sub_op),
offset,
limit,
}
}
pub fn limit(sub_op: Op, limit: u64) -> Self {
Self::new(sub_op, 0, Some(limit))
}
pub fn offset(sub_op: Op, offset: u64) -> Self {
Self::new(sub_op, offset, None)
}
}
#[derive(Debug, Clone)]
pub struct OrderKey {
pub expr: ExprTerm,
pub ascending: bool,
}
impl OrderKey {
pub fn asc(expr: ExprTerm) -> Self {
Self {
expr,
ascending: true,
}
}
pub fn desc(expr: ExprTerm) -> Self {
Self {
expr,
ascending: false,
}
}
}
#[derive(Debug, Clone)]
pub struct OpOrder {
pub sub_op: Box<Op>,
pub keys: Vec<OrderKey>,
}
impl OpOrder {
pub fn new(sub_op: Op, keys: Vec<OrderKey>) -> Self {
Self {
sub_op: Box::new(sub_op),
keys,
}
}
}
#[derive(Debug, Clone)]
pub enum Aggregate {
Count(Option<ExprTerm>),
CountDistinct(ExprTerm),
Sum(ExprTerm),
Avg(ExprTerm),
Min(ExprTerm),
Max(ExprTerm),
Sample(ExprTerm),
GroupConcat(ExprTerm, Option<String>),
}
#[derive(Debug, Clone)]
pub struct OpGroup {
pub sub_op: Box<Op>,
pub group_vars: Vec<Var>,
pub aggregates: Vec<(Var, Aggregate)>,
}
impl OpGroup {
pub fn new(sub_op: Op, group_vars: Vec<Var>) -> Self {
Self {
sub_op: Box::new(sub_op),
group_vars,
aggregates: Vec::new(),
}
}
pub fn with_aggregate(mut self, var: Var, agg: Aggregate) -> Self {
self.aggregates.push((var, agg));
self
}
}
#[derive(Debug, Clone)]
pub struct OpExtend {
pub sub_op: Box<Op>,
pub var: Var,
pub expr: ExprTerm,
}
impl OpExtend {
pub fn new(sub_op: Op, var: Var, expr: ExprTerm) -> Self {
Self {
sub_op: Box::new(sub_op),
var,
expr,
}
}
}
#[derive(Debug, Clone)]
pub struct OpMinus {
pub left: Box<Op>,
pub right: Box<Op>,
}
impl OpMinus {
pub fn new(left: Op, right: Op) -> Self {
Self {
left: Box::new(left),
right: Box::new(right),
}
}
}
#[derive(Debug, Clone)]
pub struct OpIntersect {
pub left: Box<Op>,
pub right: Box<Op>,
}
impl OpIntersect {
pub fn new(left: Op, right: Op) -> Self {
Self {
left: Box::new(left),
right: Box::new(right),
}
}
}
#[derive(Debug, Clone)]
pub struct OpTable {
pub vars: Vec<Var>,
pub rows: Vec<Vec<Option<Value>>>,
}
impl OpTable {
pub fn new(vars: Vec<Var>, rows: Vec<Vec<Option<Value>>>) -> Self {
Self { vars, rows }
}
pub fn empty() -> Self {
Self {
vars: Vec::new(),
rows: Vec::new(),
}
}
pub fn unit() -> Self {
Self {
vars: Vec::new(),
rows: vec![vec![]],
}
}
}
#[derive(Debug, Clone)]
pub struct OpSequence {
pub ops: Vec<Op>,
}
impl OpSequence {
pub fn new(ops: Vec<Op>) -> Self {
Self { ops }
}
}
#[derive(Debug, Clone)]
pub struct OpDisjunction {
pub ops: Vec<Op>,
}
impl OpDisjunction {
pub fn new(ops: Vec<Op>) -> Self {
Self { ops }
}
}
#[derive(Debug, Clone, Copy)]
pub struct OpNull;
impl OpNull {
pub fn new() -> Self {
Self
}
}
impl Default for OpNull {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_triple_pattern() {
let triple = Triple::new(
Pattern::Var(Var::new("s")),
Pattern::Uri("http://example.org/knows".to_string()),
Pattern::Var(Var::new("o")),
);
assert_eq!(triple.vars().len(), 2);
assert!(!triple.is_concrete());
}
#[test]
fn test_bgp() {
let mut bgp = OpBGP::new();
bgp.add(Triple::new(
Pattern::Var(Var::new("s")),
Pattern::Uri("http://example.org/name".to_string()),
Pattern::Var(Var::new("name")),
));
bgp.add(Triple::new(
Pattern::Var(Var::new("s")),
Pattern::Uri("http://example.org/age".to_string()),
Pattern::Var(Var::new("age")),
));
assert_eq!(bgp.triples.len(), 2);
assert_eq!(bgp.vars().len(), 3); }
#[test]
fn test_filter_expr() {
let binding = Binding::one(Var::new("x"), Value::Integer(10));
let expr = FilterExpr::Gt(
ExprTerm::Var(Var::new("x")),
ExprTerm::Const(Value::Integer(5)),
);
assert!(expr.evaluate(&binding));
let expr2 = FilterExpr::Lt(
ExprTerm::Var(Var::new("x")),
ExprTerm::Const(Value::Integer(5)),
);
assert!(!expr2.evaluate(&binding));
}
#[test]
fn test_filter_and_or() {
let binding = Binding::two(
Var::new("x"),
Value::Integer(10),
Var::new("y"),
Value::Integer(20),
);
let expr = FilterExpr::and(
FilterExpr::Gt(
ExprTerm::Var(Var::new("x")),
ExprTerm::Const(Value::Integer(5)),
),
FilterExpr::Lt(
ExprTerm::Var(Var::new("y")),
ExprTerm::Const(Value::Integer(30)),
),
);
assert!(expr.evaluate(&binding));
}
#[test]
fn test_join_all() {
let op1 = Op::BGP(OpBGP::new());
let op2 = Op::BGP(OpBGP::new());
let op3 = Op::BGP(OpBGP::new());
let joined = OpJoin::join_all(vec![op1, op2, op3]);
assert!(matches!(joined, Op::Join(_)));
}
#[test]
fn test_op_vars() {
let mut bgp = OpBGP::new();
bgp.add(Triple::new(
Pattern::Var(Var::new("s")),
Pattern::Uri("pred".to_string()),
Pattern::Var(Var::new("o")),
));
let filter = Op::Filter(OpFilter::new(FilterExpr::True, Op::BGP(bgp)));
let vars = filter.vars();
assert!(vars.contains(&Var::new("s")));
assert!(vars.contains(&Var::new("o")));
}
#[test]
fn test_table_op() {
let table = OpTable::new(
vec![Var::new("x"), Var::new("y")],
vec![
vec![Some(Value::Integer(1)), Some(Value::Integer(2))],
vec![Some(Value::Integer(3)), None],
],
);
assert_eq!(table.vars.len(), 2);
assert_eq!(table.rows.len(), 2);
}
#[test]
fn test_string_functions() {
let binding = Binding::one(Var::new("s"), Value::String("Hello World".to_string()));
let lower = ExprTerm::LCase(Box::new(ExprTerm::Var(Var::new("s"))));
assert_eq!(
lower.evaluate(&binding),
Some(Value::String("hello world".to_string()))
);
let upper = ExprTerm::UCase(Box::new(ExprTerm::Var(Var::new("s"))));
assert_eq!(
upper.evaluate(&binding),
Some(Value::String("HELLO WORLD".to_string()))
);
let len = ExprTerm::StrLen(Box::new(ExprTerm::Var(Var::new("s"))));
assert_eq!(len.evaluate(&binding), Some(Value::Integer(11)));
}
}