Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
amimart committed Aug 21, 2024
1 parent b8e9e58 commit 87b53fa
Show file tree
Hide file tree
Showing 7 changed files with 319 additions and 6 deletions.
18 changes: 18 additions & 0 deletions contracts/axone-cognitarium/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,24 @@ pub enum WhereClause {
/// # LateralJoin
/// Evaluates right for all result row of left
LateralJoin { left: Box<Self>, right: Box<Self> },

/// # Filter
/// Filters the inner clause matching the expression.
Filter { expr: Expression, inner: Box<Self> },
}

#[cw_serde]
pub enum Expression {
NamedNode(IRI),
Literal(Literal),
Variable(String),
And(Vec<Self>),
Or(Vec<Self>),
Equal(Box<Self>, Box<Self>),
Greater(Box<Self>, Box<Self>),
GreaterOrEqual(Box<Self>, Box<Self>),
Less(Box<Self>, Box<Self>),
LessOrEqual(Box<Self>, Box<Self>),
}

/// # TripleDeleteTemplate
Expand Down
33 changes: 33 additions & 0 deletions contracts/axone-cognitarium/src/querier/engine.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::msg::{
Node, SelectItem, VarOrNamedNode, VarOrNamedNodeOrLiteral, VarOrNode, VarOrNodeOrLiteral,
};
use crate::querier::expression::Expression;
use crate::querier::mapper::{iri_as_node, literal_as_object};
use crate::querier::plan::{PatternValue, QueryNode, QueryPlan};
use crate::querier::variable::{ResolvedVariable, ResolvedVariables};
Expand Down Expand Up @@ -159,6 +160,17 @@ impl<'a> QueryEngine<'a> {
Box::new(ForLoopJoinIterator::new(left(vars), right))
})
}
QueryNode::Filter { expr, inner } => {
let inner = self.eval_node(*inner);
Rc::new(move |vars| {
Box::new(FilterIterator::new(
self.storage,
inner(vars),
expr.clone(),
self.ns_cache.clone(),
))
})
}
QueryNode::Skip { child, first } => {
let upstream = self.eval_node(*child);
Rc::new(move |vars| Box::new(upstream(vars).skip(first)))
Expand All @@ -173,6 +185,27 @@ impl<'a> QueryEngine<'a> {

type ResolvedVariablesIterator<'a> = Box<dyn Iterator<Item = StdResult<ResolvedVariables>> + 'a>;

struct FilterIterator<'a> {
upstream: ResolvedVariablesIterator<'a>,
expr: Expression,
ns_resolver: NamespaceResolver<'a>,
}

impl<'a> FilterIterator<'a> {
fn new(
storage: &'a dyn Storage,
upstream: ResolvedVariablesIterator<'a>,
expr: Expression,
ns_cache: Vec<Namespace>,
) -> Self {
Self {
upstream,
expr,
ns_resolver: NamespaceResolver::new(storage, ns_cache.into()),
}
}
}

impl<'a> Iterator for FilterIterator<'a> {
type Item = StdResult<ResolvedVariables>;

Expand Down
153 changes: 153 additions & 0 deletions contracts/axone-cognitarium/src/querier/expression.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
use crate::msg;
use crate::querier::mapper::iri_as_string;
use crate::querier::ResolvedVariables;
use crate::state::NamespaceSolver;
use cosmwasm_std::{StdError, StdResult};
use std::cmp::Ordering;
use std::collections::{BTreeSet, HashMap};

#[derive(Eq, PartialEq, Debug, Clone)]
pub enum Expression {
Constant(Term),
Variable(usize),
And(Vec<Self>),
Or(Vec<Self>),
Equal(Box<Self>, Box<Self>),
Greater(Box<Self>, Box<Self>),
GreaterOrEqual(Box<Self>, Box<Self>),
Less(Box<Self>, Box<Self>),
LessOrEqual(Box<Self>, Box<Self>),
}

impl Expression {
pub fn bound_variables(&self) -> BTreeSet<usize> {
let mut vars = BTreeSet::new();
self.lookup_bound_variables(&mut |v| {
vars.insert(v);
});
vars
}

pub fn lookup_bound_variables(&self, callback: &mut impl FnMut(usize)) {
match self {
Expression::Constant(_) => {}
Expression::Variable(v) => {
callback(*v);
}
Expression::And(exprs) | Expression::Or(exprs) => {
exprs
.iter()
.for_each(|e| e.lookup_bound_variables(callback));
}
Expression::Equal(left, right)
| Expression::Greater(left, right)
| Expression::GreaterOrEqual(left, right)
| Expression::Less(left, right)
| Expression::LessOrEqual(left, right) => {
left.lookup_bound_variables(callback);
right.lookup_bound_variables(callback);
}
}
}

pub fn evaluate<'a>(
&self,
vars: &'a ResolvedVariables,
ns_solver: &mut dyn NamespaceSolver,
) -> StdResult<Term> {
match self {
Expression::Constant(term) => Ok(term.clone()),
Expression::Variable(v) => vars
.get(*v)
.clone()
.ok_or(StdError::generic_err("Unbound filter variable"))
.and_then(|v| v.as_term(ns_solver)),
Expression::And(exprs) => {
for expr in exprs {
if !expr.evaluate(vars, ns_solver)?.as_bool() {
return Ok(Term::Boolean(false));
}
}
return Ok(Term::Boolean(true));
}
Expression::Or(exprs) => {
for expr in exprs {
if expr.evaluate(vars, ns_solver)?.as_bool() {
return Ok(Term::Boolean(true));
}
}
return Ok(Term::Boolean(false));
}
Expression::Equal(left, right) => Ok(Term::Boolean(
left.evaluate(vars, ns_solver)? == right.evaluate(vars, ns_solver)?,
)),
Expression::Greater(left, right) => Ok(Term::Boolean(
left.evaluate(vars, ns_solver)? > right.evaluate(vars, ns_solver)?,
)),
Expression::GreaterOrEqual(left, right) => Ok(Term::Boolean(
left.evaluate(vars, ns_solver)? >= right.evaluate(vars, ns_solver)?,
)),
Expression::Less(left, right) => Ok(Term::Boolean(
left.evaluate(vars, ns_solver)? < right.evaluate(vars, ns_solver)?,
)),
Expression::LessOrEqual(left, right) => Ok(Term::Boolean(
left.evaluate(vars, ns_solver)? <= right.evaluate(vars, ns_solver)?,
)),
}
}
}

#[derive(Eq, PartialEq, Debug, Clone)]
pub enum Term {
String(String),
Boolean(bool),
}

impl Term {
pub fn from_iri(iri: msg::IRI, prefixes: &HashMap<String, String>) -> StdResult<Self> {
Ok(Term::String(iri_as_string(iri, prefixes)?))
}

pub fn from_literal(
literal: msg::Literal,
prefixes: &HashMap<String, String>,
) -> StdResult<Self> {
Ok(Term::String(match literal {
msg::Literal::Simple(value) => value,
msg::Literal::LanguageTaggedString { value, language } => {
format!("{}{}", value, language).to_string()
}
msg::Literal::TypedValue { value, datatype } => {
format!("{}{}", value, iri_as_string(datatype, prefixes)?).to_string()
}
}))
}

pub fn as_string(&self) -> String {
match self {
Term::String(t) => t.clone(),
Term::Boolean(b) => b.to_string(),
}
}

pub fn as_bool(&self) -> bool {
match self {
Term::String(s) => !s.is_empty(),
Term::Boolean(b) => *b,
}
}
}

impl PartialOrd<Term> for Term {
fn partial_cmp(&self, other: &Term) -> Option<Ordering> {
if self == other {
return Some(Ordering::Equal);
}

match (self, other) {
(Term::String(left), Term::String(right)) => Some(left.cmp(right)),
(Term::Boolean(left), Term::Boolean(right)) => Some(left.cmp(right)),
_ => None,
}
}
}
1 change: 1 addition & 0 deletions contracts/axone-cognitarium/src/querier/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod engine;
mod expression;
mod mapper;
mod plan;
mod plan_builder;
Expand Down
34 changes: 29 additions & 5 deletions contracts/axone-cognitarium/src/querier/plan.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::querier::expression::Expression;
use crate::state::{Object, Predicate, Subject};
use std::collections::BTreeSet;

Expand Down Expand Up @@ -58,26 +59,45 @@ pub enum QueryNode {
/// Results in no solutions, this special node is used when we know before plan execution that a node
/// will end up with no possible solutions. For example, using a triple pattern filtering with a constant
/// named node containing a non-existing namespace.
Noop { bound_variables: Vec<usize> },
Noop {
bound_variables: Vec<usize>,
},

/// Join two nodes by applying the cartesian product of the nodes variables.
///
/// This should be used when the nodes don't have variables in common, and can be seen as a
/// full join of disjoint datasets.
CartesianProductJoin { left: Box<Self>, right: Box<Self> },
CartesianProductJoin {
left: Box<Self>,
right: Box<Self>,
},

/// Join two nodes by using the variables values from the left node as replacement in the right
/// node.
///
/// This results to an inner join, but the underlying processing stream the variables from the
/// left node to use them as right node values.
ForLoopJoin { left: Box<Self>, right: Box<Self> },
ForLoopJoin {
left: Box<Self>,
right: Box<Self>,
},

Filter {
expr: Expression,
inner: Box<Self>,
},

/// Skip the specified first elements from the child node.
Skip { child: Box<Self>, first: usize },
Skip {
child: Box<Self>,
first: usize,
},

/// Limit to the specified first elements from the child node.
Limit { child: Box<Self>, first: usize },
Limit {
child: Box<Self>,
first: usize,
},
}

impl QueryNode {
Expand Down Expand Up @@ -114,6 +134,10 @@ impl QueryNode {
left.lookup_bound_variables(callback);
right.lookup_bound_variables(callback);
}
QueryNode::Filter { expr, inner } => {
expr.lookup_bound_variables(callback);
inner.lookup_bound_variables(callback);
}
QueryNode::Skip { child, .. } | QueryNode::Limit { child, .. } => {
child.lookup_bound_variables(callback);
}
Expand Down
60 changes: 59 additions & 1 deletion contracts/axone-cognitarium/src/querier/plan_builder.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use crate::msg;
use crate::msg::{Node, TriplePattern, VarOrNamedNode, VarOrNode, VarOrNodeOrLiteral, WhereClause};
use crate::querier::expression::{Expression, Term};
use crate::querier::mapper::{iri_as_node, literal_as_object};
use crate::querier::plan::{PatternValue, PlanVariable, QueryNode, QueryPlan};
use crate::state::{
HasCachedNamespaces, Namespace, NamespaceQuerier, NamespaceResolver, Object, Predicate, Subject,
};
use cosmwasm_std::{StdResult, Storage};
use cosmwasm_std::{StdError, StdResult, Storage};
use std::collections::HashMap;

pub struct PlanBuilder<'a> {
Expand Down Expand Up @@ -69,6 +71,18 @@ impl<'a> PlanBuilder<'a> {
left: Box::new(self.build_node(left)?),
right: Box::new(self.build_node(right)?),
}),
WhereClause::Filter { expr, inner } => {
let inner = Box::new(self.build_node(inner)?);
let expr = self.build_expression(expr)?;

if !expr.bound_variables().is_subset(&inner.bound_variables()) {
return Err(StdError::generic_err(
"Unbound variable in filter expression",
));
}

Ok(QueryNode::Filter { expr, inner })
}
}
}

Expand Down Expand Up @@ -101,6 +115,50 @@ impl<'a> PlanBuilder<'a> {
.unwrap_or(Ok(QueryNode::noop()))
}

fn build_expression(&mut self, expr: &msg::Expression) -> StdResult<Expression> {
match expr {
msg::Expression::NamedNode(iri) => {
Term::from_iri(iri.clone(), self.prefixes).map(Expression::Constant)
}
msg::Expression::Literal(literal) => {
Term::from_literal(literal.clone(), self.prefixes).map(Expression::Constant)
}
msg::Expression::Variable(v) => Ok(Expression::Variable(
self.resolve_basic_variable(v.to_string()),
)),
msg::Expression::And(exprs) => exprs
.iter()
.map(|e| self.build_expression(e))
.collect::<StdResult<Vec<Expression>>>()
.map(Expression::And),
msg::Expression::Or(exprs) => exprs
.iter()
.map(|e| self.build_expression(e))
.collect::<StdResult<Vec<Expression>>>()
.map(Expression::Or),
msg::Expression::Equal(left, right) => Ok(Expression::Equal(
Box::new(self.build_expression(left)?),
Box::new(self.build_expression(right)?),
)),
msg::Expression::Greater(left, right) => Ok(Expression::Greater(
Box::new(self.build_expression(left)?),
Box::new(self.build_expression(right)?),
)),
msg::Expression::GreaterOrEqual(left, right) => Ok(Expression::GreaterOrEqual(
Box::new(self.build_expression(left)?),
Box::new(self.build_expression(right)?),
)),
msg::Expression::Less(left, right) => Ok(Expression::Less(
Box::new(self.build_expression(left)?),
Box::new(self.build_expression(right)?),
)),
msg::Expression::LessOrEqual(left, right) => Ok(Expression::LessOrEqual(
Box::new(self.build_expression(left)?),
Box::new(self.build_expression(right)?),
)),
}
}

fn build_triple_pattern(&mut self, pattern: &TriplePattern) -> StdResult<QueryNode> {
let subject_res = self.build_subject_pattern(pattern.subject.clone());
let predicate_res = self.build_predicate_pattern(pattern.predicate.clone());
Expand Down
Loading

0 comments on commit 87b53fa

Please sign in to comment.