From da76633c3892b4defee99e3ab4c1de0b1618f24f Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Wed, 14 Aug 2024 14:41:09 +0200 Subject: [PATCH 01/15] feat(cognitarium)!: rework WhereClause api design adopt a more specific naming for the type of clauses and allow to combine multiple ones, simplifying the addition of new ones --- contracts/axone-cognitarium/src/contract.rs | 411 ++++++++++-------- contracts/axone-cognitarium/src/msg.rs | 38 +- .../axone-cognitarium/src/querier/mod.rs | 1 + .../axone-cognitarium/src/querier/plan.rs | 13 + .../src/querier/plan_builder.rs | 70 ++- 5 files changed, 279 insertions(+), 254 deletions(-) diff --git a/contracts/axone-cognitarium/src/contract.rs b/contracts/axone-cognitarium/src/contract.rs index 1793f660..fdb1a43a 100644 --- a/contracts/axone-cognitarium/src/contract.rs +++ b/contracts/axone-cognitarium/src/contract.rs @@ -53,10 +53,8 @@ pub fn execute( pub mod execute { use super::*; - use crate::msg::{ - DataFormat, Prefix, SimpleWhereCondition, TripleDeleteTemplate, WhereClause, WhereCondition, - }; - use crate::querier::{PlanBuilder, QueryEngine, ResolvedVariables}; + use crate::msg::{DataFormat, Prefix, TripleDeleteTemplate, WhereClause}; + use crate::querier::{PlanBuilder, QueryEngine, QueryPlan, ResolvedVariables}; use crate::rdf::PrefixMap; use crate::state::{HasCachedNamespaces, Triple}; use crate::storer::StoreEngine; @@ -95,21 +93,18 @@ pub mod execute { info: MessageInfo, prefixes: Vec, delete: Vec, - r#where: WhereClause, + r#where: Option, ) -> Result { verify_owner(&deps, &info)?; let delete = if delete.is_empty() { - Left( - r#where + Left(match r#where { + Some(WhereClause::Bgp { ref patterns }) => patterns .iter() - .map(|c| match c { - WhereCondition::Simple(SimpleWhereCondition::TriplePattern(t)) => { - (t.subject.clone(), t.predicate.clone(), t.object.clone()) - } - }) + .map(|p| (p.subject.clone(), p.predicate.clone(), p.object.clone())) .collect(), - ) + _ => Err(StdError::generic_err("Missing triple templates to delete"))?, + }) } else { Right( delete @@ -121,7 +116,10 @@ pub mod execute { let prefix_map = ::from(prefixes).into_inner(); let mut plan_builder = PlanBuilder::new(deps.storage, &prefix_map, None); - let plan = plan_builder.build_plan(&r#where)?; + let plan = match r#where { + Some(ref w) => plan_builder.build_plan(w)?, + None => QueryPlan::empty_plan(), + }; let query_engine = QueryEngine::new(deps.storage); let delete_templates = query_engine.make_triple_templates( @@ -131,7 +129,7 @@ pub mod execute { plan_builder.cached_namespaces(), )?; - let triples = if r#where.is_empty() { + let triples = if r#where.is_none() { let empty_vars = ResolvedVariables::with_capacity(0); delete_templates .into_iter() @@ -176,8 +174,8 @@ pub mod query { use super::*; use crate::msg::{ ConstructQuery, ConstructResponse, DescribeQuery, DescribeResponse, Node, SelectQuery, - SelectResponse, SimpleWhereCondition, StoreResponse, TripleConstructTemplate, - TriplePattern, VarOrNamedNode, VarOrNode, VarOrNodeOrLiteral, WhereCondition, + SelectResponse, StoreResponse, TripleConstructTemplate, TriplePattern, VarOrNamedNode, + VarOrNode, VarOrNodeOrLiteral, WhereClause, }; use crate::querier::{PlanBuilder, QueryEngine}; use crate::rdf::PrefixMap; @@ -227,10 +225,17 @@ pub mod query { object: VarOrNodeOrLiteral::Variable(format!("{var}{o}")), }; - let mut r#where = query.r#where; - r#where.push(WhereCondition::Simple(SimpleWhereCondition::TriplePattern( - select.clone(), - ))); + let r#where = match query.r#where { + Some(c) => WhereClause::LateralJoin { + left: Box::new(c), + right: Box::new(WhereClause::Bgp { + patterns: vec![select.clone()], + }), + }, + None => WhereClause::Bgp { + patterns: vec![select.clone()], + }, + }; (vec![select], r#where) } @@ -243,9 +248,9 @@ pub mod query { ( vec![select.clone()], - vec![WhereCondition::Simple(SimpleWhereCondition::TriplePattern( - select, - ))], + WhereClause::Bgp { + patterns: vec![select.clone()], + }, ) } }; @@ -279,18 +284,17 @@ pub mod query { } = query; let construct = if construct.is_empty() { - r#where - .iter() - .map(|t| match t { - WhereCondition::Simple(SimpleWhereCondition::TriplePattern(t)) => { - TripleConstructTemplate { - subject: t.subject.clone(), - predicate: t.predicate.clone(), - object: t.object.clone(), - } - } - }) - .collect() + match &r#where { + WhereClause::Bgp { patterns } => patterns + .iter() + .map(|p| TripleConstructTemplate { + subject: p.subject.clone(), + predicate: p.predicate.clone(), + object: p.object.clone(), + }) + .collect(), + _ => Err(StdError::generic_err("missing triples to construct"))?, + } } else { construct }; @@ -431,14 +435,14 @@ mod tests { use crate::error::StoreError; use crate::msg::ExecuteMsg::{DeleteData, InsertData}; use crate::msg::Node::{BlankNode, NamedNode}; - use crate::msg::SimpleWhereCondition::TriplePattern; use crate::msg::IRI::{Full, Prefixed}; use crate::msg::{ ConstructQuery, ConstructResponse, DescribeQuery, DescribeResponse, Head, Literal, Prefix, Results, SelectItem, SelectQuery, SelectResponse, StoreLimitsInput, StoreLimitsInputBuilder, StoreResponse, Value, VarOrNamedNode, VarOrNamedNodeOrLiteral, - VarOrNode, VarOrNodeOrLiteral, WhereCondition, + VarOrNode, VarOrNodeOrLiteral, }; + use crate::msg::{TriplePattern, WhereClause}; use crate::state::{ namespaces, triples, Namespace, Node, Object, StoreLimits, StoreStat, Subject, Triple, }; @@ -531,7 +535,7 @@ mod tests { DeleteData { prefixes: vec![], delete: vec![], - r#where: vec![], + r#where: None, }, ]; @@ -910,18 +914,21 @@ mod tests { "https://ontology.axone.space/thesaurus/topic/Test".to_string(), )), }], - r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { - subject: VarOrNode::Node(NamedNode(Full( - "https://ontology.axone.space/dataverse/dataspace/metadata/unknown" - .to_string(), - ))), - predicate: VarOrNamedNode::NamedNode(Full( - "https://ontology.axone.space/core/hasTopic".to_string(), - )), - object: VarOrNodeOrLiteral::Node(NamedNode(Full( - "https://ontology.axone.space/thesaurus/topic/Test".to_string(), - ))), - }))], + r#where: WhereClause::Bgp { + patterns: vec![TriplePattern { + subject: VarOrNode::Node(NamedNode(Full( + "https://ontology.axone.space/dataverse/dataspace/metadata/unknown" + .to_string(), + ))), + predicate: VarOrNamedNode::NamedNode(Full( + "https://ontology.axone.space/core/hasTopic".to_string(), + )), + object: VarOrNodeOrLiteral::Node(NamedNode(Full( + "https://ontology.axone.space/thesaurus/topic/Test".to_string(), + ))), + }], + } + .into(), }, 0, 0, @@ -939,15 +946,18 @@ mod tests { "https://ontology.axone.space/thesaurus/topic/Test".to_string(), )), }], - r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { - subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), - predicate: VarOrNamedNode::NamedNode(Full( - "https://ontology.axone.space/core/hasTopic".to_string(), - )), - object: VarOrNodeOrLiteral::Node(NamedNode(Full( - "https://ontology.axone.space/thesaurus/topic/Test".to_string(), - ))), - }))], + r#where: WhereClause::Bgp { + patterns: vec![TriplePattern { + subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), + predicate: VarOrNamedNode::NamedNode(Full( + "https://ontology.axone.space/core/hasTopic".to_string(), + )), + object: VarOrNodeOrLiteral::Node(NamedNode(Full( + "https://ontology.axone.space/thesaurus/topic/Test".to_string(), + ))), + }], + } + .into(), }, 1, 0, @@ -972,13 +982,18 @@ mod tests { "thesaurus:Test".to_string(), )), }], - r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { - subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), - predicate: VarOrNamedNode::NamedNode(Prefixed("core:hasTopic".to_string())), - object: VarOrNodeOrLiteral::Node(NamedNode(Prefixed( - "thesaurus:Test".to_string(), - ))), - }))], + r#where: WhereClause::Bgp { + patterns: vec![TriplePattern { + subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), + predicate: VarOrNamedNode::NamedNode(Prefixed( + "core:hasTopic".to_string(), + )), + object: VarOrNodeOrLiteral::Node(NamedNode(Prefixed( + "thesaurus:Test".to_string(), + ))), + }], + } + .into(), }, 1, 0, @@ -1001,11 +1016,16 @@ mod tests { predicate: VarOrNamedNode::NamedNode(Prefixed("core:hasTopic".to_string())), object: VarOrNamedNodeOrLiteral::Variable("o".to_string()), }], - r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { - subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), - predicate: VarOrNamedNode::NamedNode(Prefixed("core:hasTopic".to_string())), - object: VarOrNodeOrLiteral::Variable("o".to_string()), - }))], + r#where: WhereClause::Bgp { + patterns: vec![TriplePattern { + subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), + predicate: VarOrNamedNode::NamedNode(Prefixed( + "core:hasTopic".to_string(), + )), + object: VarOrNodeOrLiteral::Variable("o".to_string()), + }], + } + .into(), }, 1, 0, @@ -1019,11 +1039,14 @@ mod tests { predicate: VarOrNamedNode::Variable("p".to_string()), object: VarOrNamedNodeOrLiteral::Variable("o".to_string()), }], - r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { - subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), - predicate: VarOrNamedNode::Variable("p".to_string()), - object: VarOrNodeOrLiteral::Variable("o".to_string()), - }))], + r#where: WhereClause::Bgp { + patterns: vec![TriplePattern { + subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), + predicate: VarOrNamedNode::Variable("p".to_string()), + object: VarOrNodeOrLiteral::Variable("o".to_string()), + }], + } + .into(), }, 11, 2, @@ -1033,11 +1056,14 @@ mod tests { DeleteData { prefixes: vec![], delete: vec![], - r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { - subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), - predicate: VarOrNamedNode::Variable("p".to_string()), - object: VarOrNodeOrLiteral::Variable("o".to_string()), - }))], + r#where: WhereClause::Bgp { + patterns: vec![TriplePattern { + subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), + predicate: VarOrNamedNode::Variable("p".to_string()), + object: VarOrNodeOrLiteral::Variable("o".to_string()), + }], + } + .into(), }, 11, 2, @@ -1047,11 +1073,14 @@ mod tests { DeleteData { prefixes: vec![], delete: vec![], - r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { - subject: VarOrNode::Variable("s".to_string()), - predicate: VarOrNamedNode::Variable("p".to_string()), - object: VarOrNodeOrLiteral::Variable("0".to_string()), - }))], + r#where: WhereClause::Bgp { + patterns: vec![TriplePattern { + subject: VarOrNode::Variable("s".to_string()), + predicate: VarOrNamedNode::Variable("p".to_string()), + object: VarOrNodeOrLiteral::Variable("0".to_string()), + }], + } + .into(), }, 40, 17, @@ -1076,7 +1105,7 @@ mod tests { "thesaurus:Test".to_string(), )), }], - r#where: vec![], + r#where: None, }, 1, 0, @@ -1160,15 +1189,18 @@ mod tests { "https://ontology.axone.space/thesaurus/topic/Test".to_string(), )), }], - r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { - subject: VarOrNode::Node(NamedNode(Prefixed("foo:bar".to_string()))), - predicate: VarOrNamedNode::NamedNode(Full( - "https://ontology.axone.space/core/hasTopic".to_string(), - )), - object: VarOrNodeOrLiteral::Node(NamedNode(Full( - "https://ontology.axone.space/thesaurus/topic/Test".to_string(), - ))), - }))], + r#where: WhereClause::Bgp { + patterns: vec![TriplePattern { + subject: VarOrNode::Node(NamedNode(Prefixed("foo:bar".to_string()))), + predicate: VarOrNamedNode::NamedNode(Full( + "https://ontology.axone.space/core/hasTopic".to_string(), + )), + object: VarOrNodeOrLiteral::Node(NamedNode(Full( + "https://ontology.axone.space/thesaurus/topic/Test".to_string(), + ))), + }], + } + .into(), }, expected: StdError::generic_err("Prefix not found: foo").into(), }, @@ -1182,13 +1214,16 @@ mod tests { predicate: VarOrNamedNode::Variable("z".to_string()), object: VarOrNamedNodeOrLiteral::Variable("o".to_string()), }], - r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { - subject: VarOrNode::Node(NamedNode(Full( - "https://ontology.axone.space/thesaurus/topic/Test".to_string(), - ))), - predicate: VarOrNamedNode::Variable("p".to_string()), - object: VarOrNodeOrLiteral::Variable("o".to_string()), - }))], + r#where: WhereClause::Bgp { + patterns: vec![TriplePattern { + subject: VarOrNode::Node(NamedNode(Full( + "https://ontology.axone.space/thesaurus/topic/Test".to_string(), + ))), + predicate: VarOrNamedNode::Variable("p".to_string()), + object: VarOrNodeOrLiteral::Variable("o".to_string()), + }], + } + .into(), }, expected: StdError::generic_err("Selected variable not found in query").into(), }, @@ -1299,15 +1334,14 @@ mod tests { SelectItem::Variable("a".to_string()), SelectItem::Variable("b".to_string()), ], - r#where: vec![WhereCondition::Simple(TriplePattern( - msg::TriplePattern { + r#where: WhereClause::Bgp{patterns:vec![TriplePattern { subject: VarOrNode::Variable("a".to_string()), predicate: VarOrNamedNode::NamedNode(Full( "https://ontology.axone.space/core/hasDescription".to_string(), )), object: VarOrNodeOrLiteral::Variable("b".to_string()), }, - ))], + ]}, limit: None, }, SelectResponse { @@ -1390,15 +1424,14 @@ mod tests { select: vec![ SelectItem::Variable("a".to_string()), ], - r#where: vec![WhereCondition::Simple(TriplePattern( - msg::TriplePattern { + r#where: WhereClause::Bgp{patterns:vec![TriplePattern { subject: VarOrNode::Variable("a".to_string()), predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasDescription".to_string(), )), object: VarOrNodeOrLiteral::Literal(Literal::LanguageTaggedString { value: "A test Dataset.".to_string(), language: "en".to_string() }), }, - ))], + ]}, limit: None, }, SelectResponse { @@ -1425,13 +1458,12 @@ mod tests { select: vec![ SelectItem::Variable("a".to_string()), ], - r#where: vec![WhereCondition::Simple(TriplePattern( - msg::TriplePattern { + r#where: WhereClause::Bgp{patterns:vec![TriplePattern { subject: VarOrNode::Node(NamedNode(Full("https://ontology.axone.space/dataverse/dataset/metadata/d1615703-4ee1-4e2f-997e-15aecf1eea4e".to_string()))), predicate: VarOrNamedNode::Variable("a".to_string()), object: VarOrNodeOrLiteral::Literal(Literal::LanguageTaggedString { value: "A test Dataset.".to_string(), language: "en".to_string() }), }, - ))], + ]}, limit: None, }, SelectResponse { @@ -1492,25 +1524,22 @@ mod tests { SelectQuery { prefixes: vec![Prefix { prefix: "core".to_string(), namespace: "https://ontology.axone.space/core/".to_string() }], select: vec![SelectItem::Variable("a".to_string()), SelectItem::Variable("b".to_string())], - r#where: vec![ - WhereCondition::Simple(TriplePattern( - msg::TriplePattern { + r#where: WhereClause::Bgp{patterns:vec![ + TriplePattern { subject: VarOrNode::Variable("a".to_string()), predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasTemporalCoverage".to_string(), )), object: VarOrNodeOrLiteral::Node(BlankNode("a".to_string())), }, - )), - WhereCondition::Simple(TriplePattern( - msg::TriplePattern { + TriplePattern { subject: VarOrNode::Node(BlankNode("a".to_string())), predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasStartDate".to_string(), )), object: VarOrNodeOrLiteral::Variable("b".to_string()), }, - ))], + ]}, limit: None, }, SelectResponse { @@ -1541,25 +1570,22 @@ mod tests { SelectQuery { prefixes: vec![Prefix { prefix: "core".to_string(), namespace: "https://ontology.axone.space/core/".to_string() }], select: vec![SelectItem::Variable("a".to_string()), SelectItem::Variable("b".to_string())], - r#where: vec![ - WhereCondition::Simple(TriplePattern( - msg::TriplePattern { + r#where: WhereClause::Bgp{patterns:vec![ + TriplePattern { subject: VarOrNode::Variable("a".to_string()), predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasTemporalCoverage".to_string(), )), object: VarOrNodeOrLiteral::Variable("blank".to_string()), }, - )), - WhereCondition::Simple(TriplePattern( - msg::TriplePattern { + TriplePattern { subject: VarOrNode::Variable("blank".to_string()), predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasStartDate".to_string(), )), object: VarOrNodeOrLiteral::Variable("b".to_string()), - }, - ))], + } + ]}, limit: None, }, SelectResponse { @@ -1590,25 +1616,22 @@ mod tests { SelectQuery { prefixes: vec![Prefix { prefix: "core".to_string(), namespace: "https://ontology.axone.space/core/".to_string() }], select: vec![SelectItem::Variable("a".to_string()), SelectItem::Variable("b".to_string())], - r#where: vec![ - WhereCondition::Simple(TriplePattern( - msg::TriplePattern { + r#where: WhereClause::Bgp{patterns:vec![ + TriplePattern { subject: VarOrNode::Variable("a".to_string()), predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasTemporalCoverage".to_string(), )), object: VarOrNodeOrLiteral::Node(BlankNode("blank1".to_string())), }, - )), - WhereCondition::Simple(TriplePattern( - msg::TriplePattern { + TriplePattern { subject: VarOrNode::Node(BlankNode("blank2".to_string())), predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasInformation".to_string(), )), object: VarOrNodeOrLiteral::Variable("b".to_string()), }, - ))], + ]}, limit: None, }, SelectResponse { @@ -1639,16 +1662,15 @@ mod tests { SelectQuery { prefixes: vec![Prefix { prefix: "core".to_string(), namespace: "https://ontology.axone.space/core/".to_string() }], select: vec![SelectItem::Variable("a".to_string()), SelectItem::Variable("b".to_string())], - r#where: vec![ - WhereCondition::Simple(TriplePattern( - msg::TriplePattern { + r#where: WhereClause::Bgp{patterns:vec![ + TriplePattern { subject: VarOrNode::Variable("a".to_string()), predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasTemporalCoverage".to_string(), )), object: VarOrNodeOrLiteral::Variable("b".to_string()), }, - ))], + ]}, limit: None, }, SelectResponse { @@ -1716,7 +1738,7 @@ mod tests { SelectItem::Variable("a".to_string()), SelectItem::Variable("b".to_string()), ], - r#where: vec![], + r#where: WhereClause::Bgp { patterns: vec![] }, limit: None, }, Err(StdError::generic_err( @@ -1727,7 +1749,7 @@ mod tests { SelectQuery { prefixes: vec![], select: vec![], - r#where: vec![], + r#where: WhereClause::Bgp { patterns: vec![] }, limit: Some(8000), }, Err(StdError::generic_err("Maximum query limit exceeded")), @@ -1739,16 +1761,18 @@ mod tests { namespace: "https://ontology.axone.space/core/".to_string(), }], select: vec![SelectItem::Variable("a".to_string())], - r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { - subject: VarOrNode::Variable("a".to_string()), - predicate: VarOrNamedNode::NamedNode(Prefixed( - "invalid:hasDescription".to_string(), - )), - object: VarOrNodeOrLiteral::Literal(Literal::LanguageTaggedString { - value: "A test Dataset.".to_string(), - language: "en".to_string(), - }), - }))], + r#where: WhereClause::Bgp { + patterns: vec![TriplePattern { + subject: VarOrNode::Variable("a".to_string()), + predicate: VarOrNamedNode::NamedNode(Prefixed( + "invalid:hasDescription".to_string(), + )), + object: VarOrNodeOrLiteral::Literal(Literal::LanguageTaggedString { + value: "A test Dataset.".to_string(), + language: "en".to_string(), + }), + }], + }, limit: None, }, Err(StdError::generic_err("Prefix not found: invalid")), @@ -1757,16 +1781,18 @@ mod tests { SelectQuery { prefixes: vec![], select: vec![SelectItem::Variable("u".to_string())], - r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { - subject: VarOrNode::Variable("a".to_string()), - predicate: VarOrNamedNode::NamedNode(Full( - "https://ontology.axone.space/core/hasDescription".to_string(), - )), - object: VarOrNodeOrLiteral::Literal(Literal::LanguageTaggedString { - value: "A test Dataset.".to_string(), - language: "en".to_string(), - }), - }))], + r#where: WhereClause::Bgp { + patterns: vec![TriplePattern { + subject: VarOrNode::Variable("a".to_string()), + predicate: VarOrNamedNode::NamedNode(Full( + "https://ontology.axone.space/core/hasDescription".to_string(), + )), + object: VarOrNodeOrLiteral::Literal(Literal::LanguageTaggedString { + value: "A test Dataset.".to_string(), + language: "en".to_string(), + }), + }], + }, limit: None, }, Err(StdError::generic_err( @@ -1816,7 +1842,7 @@ mod tests { query: DescribeQuery { prefixes: vec![], resource: VarOrNamedNode::NamedNode(Full("https://ontology.axone.space/dataverse/dataspace/metadata/dcf48417-01c5-4b43-9bc7-49e54c028473".to_string())), - r#where: vec![], + r#where: None, }, format: Some(DataFormat::Turtle), }, @@ -1839,7 +1865,7 @@ mod tests { query: DescribeQuery { prefixes: vec![], resource: VarOrNamedNode::NamedNode(Full("https://ontology.axone.space/dataverse/dataspace/metadata/dcf48417-01c5-4b43-9bc7-49e54c028473".to_string())), - r#where: vec![], + r#where: None, }, format: Some(DataFormat::RDFXml), }, @@ -1868,15 +1894,15 @@ mod tests { query: DescribeQuery { prefixes: vec![], resource: VarOrNamedNode::NamedNode(Full("https://ontology.axone.space/dataverse/dataspace/metadata/dcf48417-01c5-4b43-9bc7-49e54c028473".to_string())), - r#where: vec![WhereCondition::Simple(TriplePattern( - msg::TriplePattern { + r#where: WhereClause::Bgp { patterns: vec![ + TriplePattern { subject: VarOrNode::Variable("a".to_string()), predicate: VarOrNamedNode::NamedNode(Full( "https://ontology.axone.space/core/hasDescription".to_string(), )), object: VarOrNodeOrLiteral::Variable("b".to_string()), }, - ))], + ]}.into(), }, format: Some(DataFormat::NTriples), }, @@ -1903,7 +1929,7 @@ mod tests { query: DescribeQuery { prefixes: vec![], resource: VarOrNamedNode::NamedNode(Full("https://ontology.axone.space/dataverse/dataspace/metadata/dcf48417-01c5-4b43-9bc7-49e54c028473".to_string())), - r#where: vec![], + r#where: None, }, format: Some(DataFormat::NQuads), }, @@ -1977,7 +2003,7 @@ mod tests { }, ], resource: VarOrNamedNode::NamedNode(Prefixed("metadata:dcf48417-01c5-4b43-9bc7-49e54c028473".to_string())), - r#where: vec![], + r#where: None, }, format: Some(DataFormat::Turtle), }, @@ -2042,15 +2068,15 @@ mod tests { query: DescribeQuery { prefixes: vec![Prefix { prefix: "core".to_string(), namespace: "https://ontology.axone.space/core/".to_string() }], resource: VarOrNamedNode::Variable("a".to_string()), - r#where: vec![WhereCondition::Simple(TriplePattern( - msg::TriplePattern { + r#where: WhereClause::Bgp {patterns: vec![ + TriplePattern { subject: VarOrNode::Variable("a".to_string()), predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasDescription".to_string(), )), object: VarOrNodeOrLiteral::Literal(Literal::LanguageTaggedString { value: "A test Dataset.".to_string(), language: "en".to_string() }), }, - ))], + ]}.into(), }, format: Some(DataFormat::Turtle), }, @@ -2100,22 +2126,22 @@ mod tests { } #[test] - fn variable_mutiple_resources_describe() { + fn variable_multiple_resources_describe() { let cases = vec![ ( QueryMsg::Describe { query: DescribeQuery { prefixes: vec![Prefix { prefix: "core".to_string(), namespace: "https://ontology.axone.space/core/".to_string() }], resource: VarOrNamedNode::Variable("a".to_string()), - r#where: vec![WhereCondition::Simple(TriplePattern( - msg::TriplePattern { + r#where: WhereClause::Bgp {patterns: vec![ + TriplePattern { subject: VarOrNode::Variable("a".to_string()), predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasPublisher".to_string(), )), object: VarOrNodeOrLiteral::Literal(Literal::Simple("AXONE".to_string())), }, - ))], + ]}.into(), }, format: Some(DataFormat::Turtle), }, @@ -2175,16 +2201,15 @@ mod tests { Prefix { prefix: "metadata-dataset".to_string(), namespace: "https://ontology.axone.space/dataverse/dataset/metadata/".to_string() }, ], resource: VarOrNamedNode::Variable("x".to_string()), - r#where: vec![WhereCondition::Simple(TriplePattern( - msg::TriplePattern { + r#where: WhereClause::Bgp {patterns: vec![ + TriplePattern { subject: VarOrNode::Node(NamedNode(Prefixed("metadata-dataset:80b1f84e-86dc-4730-b54f-701ad9b1888a".to_string()))), predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasTemporalCoverage".to_string(), )), object: VarOrNodeOrLiteral::Variable("x".to_string()), }, - )), - ], + ]}.into(), }, format: Some(DataFormat::Turtle), }, @@ -2246,13 +2271,13 @@ mod tests { query: ConstructQuery { prefixes: vec![], construct: vec![], - r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { + r#where: WhereClause::Bgp{patterns:vec![TriplePattern { subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), predicate: VarOrNamedNode::NamedNode(Full( "https://ontology.axone.space/core/hasTag".to_string(), )), object: VarOrNodeOrLiteral::Variable("o".to_string()), - }))], + }]}, }, format: None, }, @@ -2282,13 +2307,13 @@ mod tests { object: VarOrNodeOrLiteral::Variable("o".to_string()), } ], - r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { + r#where: WhereClause::Bgp{patterns:vec![TriplePattern { subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), predicate: VarOrNamedNode::NamedNode(Full( "https://ontology.axone.space/core/hasTag".to_string(), )), object: VarOrNodeOrLiteral::Variable("o".to_string()), - }))], + }]}, }, format: Some(DataFormat::NTriples), }, @@ -2335,32 +2360,32 @@ mod tests { object: VarOrNodeOrLiteral::Variable("info_o".to_string()), } ], - r#where: vec![ - WhereCondition::Simple(TriplePattern(msg::TriplePattern { + r#where: WhereClause::Bgp {patterns:vec![ + TriplePattern { subject: VarOrNode::Node(NamedNode(Prefixed("metadata-dataset:80b1f84e-86dc-4730-b54f-701ad9b1888a".to_string()))), predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasTemporalCoverage".to_string(), )), object: VarOrNodeOrLiteral::Variable("tcov".to_string()), - })), - WhereCondition::Simple(TriplePattern(msg::TriplePattern { + }, + TriplePattern { subject: VarOrNode::Node(NamedNode(Prefixed("metadata-dataset:80b1f84e-86dc-4730-b54f-701ad9b1888a".to_string()))), predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasInformations".to_string(), )), object: VarOrNodeOrLiteral::Variable("info".to_string()), - })), - WhereCondition::Simple(TriplePattern(msg::TriplePattern { + }, + TriplePattern { subject: VarOrNode::Variable("tcov".to_string()), predicate: VarOrNamedNode::Variable("tcov_p".to_string()), object: VarOrNodeOrLiteral::Variable("tcov_o".to_string()), - })), - WhereCondition::Simple(TriplePattern(msg::TriplePattern { + }, + TriplePattern { subject: VarOrNode::Variable("info".to_string()), predicate: VarOrNamedNode::Variable("info_p".to_string()), object: VarOrNodeOrLiteral::Variable("info_o".to_string()), - })) - ], + } + ]}, }, format: Some(DataFormat::NTriples), }, diff --git a/contracts/axone-cognitarium/src/msg.rs b/contracts/axone-cognitarium/src/msg.rs index 9885d6f5..6bd049e1 100644 --- a/contracts/axone-cognitarium/src/msg.rs +++ b/contracts/axone-cognitarium/src/msg.rs @@ -68,11 +68,12 @@ pub enum ExecuteMsg { /// The prefixes used in the operation. prefixes: Vec, /// Specifies the specific triple templates to delete. - /// If nothing is provided, the patterns from the `where` clause are used for deletion. + /// If nothing is provided and the `where` clause is a single Bgp, the patterns are used for + /// deletion. delete: Vec, /// Defines the patterns that data (RDF triples) should match in order for it to be - /// considered for deletion. - r#where: WhereClause, + /// considered for deletion, if any. + r#where: Option, }, } @@ -424,7 +425,7 @@ pub struct DescribeQuery { pub resource: VarOrNamedNode, /// The WHERE clause. /// This clause is used to specify the resource identifier to describe using variable bindings. - pub r#where: WhereClause, + pub r#where: Option, } /// # ConstructQuery @@ -435,7 +436,8 @@ pub struct ConstructQuery { /// The prefixes used in the query. pub prefixes: Vec, /// The triples to construct. - /// If nothing is provided, the patterns from the `where` clause are used for construction. + /// If nothing is provided and the `where` clause is a single Bgp, the patterns are used for + /// construction. pub construct: Vec, /// The WHERE clause. /// This clause is used to specify the triples to construct using variable bindings. @@ -463,25 +465,15 @@ pub enum SelectItem { /// # WhereClause /// Represents a WHERE clause in a [SelectQuery], i.e. a set of conditions to filter the results. -pub type WhereClause = Vec; - -/// # WhereCondition -/// Represents a condition in a [WhereClause]. -#[cw_serde] -pub enum WhereCondition { - /// # Simple - /// Represents a simple condition. - Simple(SimpleWhereCondition), -} - -/// # SimpleWhereCondition -/// Represents a simple condition in a [WhereCondition]. #[cw_serde] -pub enum SimpleWhereCondition { - /// # TriplePattern - /// Represents a triple pattern, i.e. a condition on a triple based on its subject, predicate and - /// object. - TriplePattern(TriplePattern), +pub enum WhereClause { + /// # Bgp + /// Represents a basic graph pattern expressed as a set of triple patterns. + Bgp { patterns: Vec }, + + /// # LateralJoin + /// Evaluates right for all result row of left + LateralJoin { left: Box, right: Box }, } /// # TripleDeleteTemplate diff --git a/contracts/axone-cognitarium/src/querier/mod.rs b/contracts/axone-cognitarium/src/querier/mod.rs index fe87d846..b9ecd6ff 100644 --- a/contracts/axone-cognitarium/src/querier/mod.rs +++ b/contracts/axone-cognitarium/src/querier/mod.rs @@ -5,5 +5,6 @@ mod plan_builder; mod variable; pub use engine::*; +pub use plan::*; pub use plan_builder::*; pub use variable::ResolvedVariables; diff --git a/contracts/axone-cognitarium/src/querier/plan.rs b/contracts/axone-cognitarium/src/querier/plan.rs index 00bb2f15..6feafd07 100644 --- a/contracts/axone-cognitarium/src/querier/plan.rs +++ b/contracts/axone-cognitarium/src/querier/plan.rs @@ -20,6 +20,13 @@ pub enum PlanVariable { } impl QueryPlan { + pub fn empty_plan() -> Self { + Self { + entrypoint: QueryNode::noop(), + variables: Vec::new(), + } + } + /// Resolve the index corresponding to the variable name, if not attached to a blank node. pub fn get_var_index(&self, var_name: &str) -> Option { self.variables.iter().enumerate().find_map(|(index, it)| { @@ -74,6 +81,12 @@ pub enum QueryNode { } impl QueryNode { + pub fn noop() -> Self { + QueryNode::Noop { + bound_variables: Vec::new(), + } + } + pub fn bound_variables(&self) -> BTreeSet { let mut vars = BTreeSet::new(); self.lookup_bound_variables(&mut |v| { diff --git a/contracts/axone-cognitarium/src/querier/plan_builder.rs b/contracts/axone-cognitarium/src/querier/plan_builder.rs index 6849f82a..c8febaf1 100644 --- a/contracts/axone-cognitarium/src/querier/plan_builder.rs +++ b/contracts/axone-cognitarium/src/querier/plan_builder.rs @@ -1,11 +1,8 @@ -use crate::msg::{ - Node, SimpleWhereCondition, TriplePattern, VarOrNamedNode, VarOrNode, VarOrNodeOrLiteral, - WhereClause, WhereCondition, -}; +use crate::msg::{Node, TriplePattern, VarOrNamedNode, VarOrNode, VarOrNodeOrLiteral, WhereClause}; use crate::querier::mapper::{iri_as_node, literal_as_object}; use crate::querier::plan::{PatternValue, PlanVariable, QueryNode, QueryPlan}; use crate::state::{HasCachedNamespaces, Namespace, NamespaceResolver, Object, Predicate, Subject}; -use cosmwasm_std::{StdResult, Storage}; +use cosmwasm_std::{StdError, StdResult, Storage}; use std::collections::HashMap; pub struct PlanBuilder<'a> { @@ -45,15 +42,7 @@ impl<'a> PlanBuilder<'a> { } pub fn build_plan(&mut self, where_clause: &WhereClause) -> StdResult { - let bgp: Vec = where_clause - .iter() - .map(|cond| { - let WhereCondition::Simple(SimpleWhereCondition::TriplePattern(pattern)) = cond; - self.build_triple_pattern(pattern) - }) - .collect::>>()?; - - let mut node = Self::build_from_bgp(bgp); + let mut node = self.build_node(where_clause)?; if let Some(skip) = self.skip { node = QueryNode::Skip { @@ -73,28 +62,40 @@ impl<'a> PlanBuilder<'a> { }) } - fn build_from_bgp(bgp: Vec) -> QueryNode { - bgp.into_iter() - .reduce(|left: QueryNode, right: QueryNode| -> QueryNode { - if left + fn build_node(&mut self, where_clause: &WhereClause) -> StdResult { + match where_clause { + WhereClause::Bgp { patterns } => self.build_from_bgp(patterns.iter()), + WhereClause::LateralJoin { .. } => Err(StdError::generic_err("not implemented")), + } + } + + fn build_from_bgp<'b>( + &mut self, + bgp: impl Iterator, + ) -> StdResult { + bgp.map(|pattern| self.build_triple_pattern(pattern)) + .reduce(|acc, item| { + let acc = acc?; + let item = item?; + + if acc .bound_variables() - .intersection(&right.bound_variables()) + .intersection(&item.bound_variables()) .next() .is_some() { - return QueryNode::ForLoopJoin { - left: Box::new(left), - right: Box::new(right), - }; + Ok(QueryNode::ForLoopJoin { + left: Box::new(acc), + right: Box::new(item), + }) + } else { + Ok(QueryNode::CartesianProductJoin { + left: Box::new(acc), + right: Box::new(item), + }) } - QueryNode::CartesianProductJoin { - left: Box::new(left), - right: Box::new(right), - } - }) - .unwrap_or(QueryNode::Noop { - bound_variables: vec![], }) + .unwrap_or(Ok(QueryNode::noop())) } fn build_triple_pattern(&mut self, pattern: &TriplePattern) -> StdResult { @@ -716,14 +717,7 @@ mod test { } assert_eq!( - builder.build_plan( - &case - .2 - .into_iter() - .map(SimpleWhereCondition::TriplePattern) - .map(WhereCondition::Simple) - .collect() - ), + builder.build_plan(&WhereClause::Bgp { patterns: case.2 }), case.3 ) } From ac6854bed6d3f997f1381febb68e83080cf22fa7 Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Wed, 14 Aug 2024 14:46:06 +0200 Subject: [PATCH 02/15] feat(cognitarium): allow to build plan on lateral join based clause --- contracts/axone-cognitarium/src/querier/plan_builder.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contracts/axone-cognitarium/src/querier/plan_builder.rs b/contracts/axone-cognitarium/src/querier/plan_builder.rs index c8febaf1..970508b1 100644 --- a/contracts/axone-cognitarium/src/querier/plan_builder.rs +++ b/contracts/axone-cognitarium/src/querier/plan_builder.rs @@ -65,7 +65,10 @@ impl<'a> PlanBuilder<'a> { fn build_node(&mut self, where_clause: &WhereClause) -> StdResult { match where_clause { WhereClause::Bgp { patterns } => self.build_from_bgp(patterns.iter()), - WhereClause::LateralJoin { .. } => Err(StdError::generic_err("not implemented")), + WhereClause::LateralJoin { left, right } => Ok(QueryNode::ForLoopJoin { + left: Box::new(self.build_node(left)?), + right: Box::new(self.build_node(right)?), + }), } } From b8e9e58f08e8dffaacf3e747d17ebb7a050ce715 Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Wed, 21 Aug 2024 18:39:28 +0200 Subject: [PATCH 03/15] refactor(cognitarium): introduce trait for ns resolution --- contracts/axone-cognitarium/src/contract.rs | 34 +--- .../axone-cognitarium/src/querier/engine.rs | 158 ++++++++---------- .../axone-cognitarium/src/querier/mapper.rs | 30 ++-- .../src/querier/plan_builder.rs | 43 ++--- .../axone-cognitarium/src/querier/variable.rs | 41 +++-- .../axone-cognitarium/src/state/namespaces.rs | 63 +++++-- .../axone-cognitarium/src/state/triples.rs | 8 +- .../axone-cognitarium/src/storer/engine.rs | 17 +- 8 files changed, 196 insertions(+), 198 deletions(-) diff --git a/contracts/axone-cognitarium/src/contract.rs b/contracts/axone-cognitarium/src/contract.rs index fdb1a43a..4b1c2902 100644 --- a/contracts/axone-cognitarium/src/contract.rs +++ b/contracts/axone-cognitarium/src/contract.rs @@ -121,13 +121,8 @@ pub mod execute { None => QueryPlan::empty_plan(), }; - let query_engine = QueryEngine::new(deps.storage); - let delete_templates = query_engine.make_triple_templates( - &plan, - &prefix_map, - delete, - plan_builder.cached_namespaces(), - )?; + let query_engine = QueryEngine::new(deps.storage, plan_builder.cached_namespaces()); + let delete_templates = query_engine.make_triple_templates(&plan, &prefix_map, delete)?; let triples = if r#where.is_none() { let empty_vars = ResolvedVariables::with_capacity(0); @@ -205,7 +200,7 @@ pub mod query { PlanBuilder::new(deps.storage, &prefix_map, None).with_limit(count as usize); let plan = plan_builder.build_plan(&query.r#where)?; - QueryEngine::new(deps.storage) + QueryEngine::new(deps.storage, plan_builder.cached_namespaces()) .select(plan, query.select) .and_then(|res| util::map_select_solutions(deps, res, plan_builder.cached_namespaces())) } @@ -356,7 +351,7 @@ pub mod util { res: SelectResults<'_>, ns_cache: Vec, ) -> StdResult { - let mut ns_resolver: NamespaceResolver = ns_cache.into(); + let mut ns_solver = NamespaceResolver::new(deps.storage, ns_cache); let mut id_issuer = IdentifierIssuer::new("b", 0u128); let mut bindings: Vec> = vec![]; @@ -365,17 +360,7 @@ pub mod util { let resolved = vars .into_iter() .map(|(name, var)| -> StdResult<(String, Value)> { - Ok(( - name, - var.as_value( - &mut |ns_key| { - let res = ns_resolver.resolve_from_key(deps.storage, ns_key); - res.and_then(NamespaceResolver::none_as_error_middleware) - .map(|ns| ns.value) - }, - &mut id_issuer, - )?, - )) + Ok((name, var.as_value(&mut ns_solver, &mut id_issuer)?)) }) .collect::>>()?; bindings.push(resolved); @@ -401,13 +386,8 @@ pub mod util { .with_limit(store.limits.max_query_limit as usize); let plan = plan_builder.build_plan(&r#where)?; - let atoms = QueryEngine::new(storage) - .construct_atoms( - plan, - &prefix_map, - construct, - plan_builder.cached_namespaces(), - )? + let atoms = QueryEngine::new(storage, plan_builder.cached_namespaces()) + .construct_atoms(plan, &prefix_map, construct)? .collect::>>()?; let out: Vec = Vec::default(); diff --git a/contracts/axone-cognitarium/src/querier/engine.rs b/contracts/axone-cognitarium/src/querier/engine.rs index fd98d45a..6b95d18f 100644 --- a/contracts/axone-cognitarium/src/querier/engine.rs +++ b/contracts/axone-cognitarium/src/querier/engine.rs @@ -5,7 +5,9 @@ use crate::querier::mapper::{iri_as_node, literal_as_object}; use crate::querier::plan::{PatternValue, QueryNode, QueryPlan}; use crate::querier::variable::{ResolvedVariable, ResolvedVariables}; use crate::rdf::Atom; -use crate::state::{triples, Namespace, NamespaceResolver, Object, Predicate, Subject, Triple}; +use crate::state::{ + triples, Namespace, NamespaceResolver, NamespaceSolver, Object, Predicate, Subject, Triple, +}; use crate::{rdf, state}; use axone_rdf::normalize::IdentifierIssuer; use cosmwasm_std::{Order, StdError, StdResult, Storage}; @@ -16,6 +18,7 @@ use std::rc::Rc; pub struct QueryEngine<'a> { storage: &'a dyn Storage, + ns_cache: Vec, } pub struct SelectResults<'a> { @@ -24,8 +27,8 @@ pub struct SelectResults<'a> { } impl<'a> QueryEngine<'a> { - pub fn new(storage: &'a dyn Storage) -> Self { - Self { storage } + pub fn new(storage: &'a dyn Storage, ns_cache: Vec) -> Self { + Self { storage, ns_cache } } pub fn select( @@ -59,7 +62,6 @@ impl<'a> QueryEngine<'a> { plan: QueryPlan, prefixes: &HashMap, templates: Vec<(VarOrNode, VarOrNamedNode, VarOrNodeOrLiteral)>, - ns_cache: Vec, ) -> StdResult> { let templates = templates .into_iter() @@ -68,7 +70,7 @@ impl<'a> QueryEngine<'a> { Ok(ResolvedAtomIterator::new( self.storage, - ns_cache.into(), + self.ns_cache.clone().into(), IdentifierIssuer::new("b", 0u128), self.eval_plan(plan), templates, @@ -88,28 +90,17 @@ impl<'a> QueryEngine<'a> { plan: &QueryPlan, prefixes: &HashMap, templates: Either, Vec>, - ns_cache: Vec, ) -> StdResult> { - let mut ns_resolver: NamespaceResolver = ns_cache.into(); + let mut ns_resolver = NamespaceResolver::new(self.storage, self.ns_cache.clone()); match templates { Left(tpl) => tpl .into_iter() - .map(|t| { - TripleTemplate::try_new(self.storage, &mut ns_resolver, plan, prefixes, Left(t)) - }) + .map(|t| TripleTemplate::try_new(&mut ns_resolver, plan, prefixes, Left(t))) .collect::>>(), Right(tpl) => tpl .into_iter() - .map(|t| { - TripleTemplate::try_new( - self.storage, - &mut ns_resolver, - plan, - prefixes, - Right(t), - ) - }) + .map(|t| TripleTemplate::try_new(&mut ns_resolver, plan, prefixes, Right(t))) .collect::>>(), } } @@ -182,6 +173,26 @@ impl<'a> QueryEngine<'a> { type ResolvedVariablesIterator<'a> = Box> + 'a>; +impl<'a> Iterator for FilterIterator<'a> { + type Item = StdResult; + + fn next(&mut self) -> Option { + match self.upstream.next()? { + Ok(vars) => match self.expr.evaluate(&vars, &mut self.ns_resolver) { + Ok(t) => { + if t.as_bool() { + Some(Ok(vars)) + } else { + None + } + } + Err(e) => Some(Err(e)), + }, + Err(e) => Some(Err(e)), + } + } +} + struct ForLoopJoinIterator<'a> { left: ResolvedVariablesIterator<'a>, right: Rc ResolvedVariablesIterator<'a> + 'a>, @@ -592,8 +603,7 @@ pub type TripleTemplateNoBlankNode = (VarOrNamedNode, VarOrNamedNode, VarOrNamed impl TripleTemplate { fn try_new( - storage: &dyn Storage, - ns_resolver: &mut NamespaceResolver, + ns_solver: &mut dyn NamespaceSolver, plan: &QueryPlan, prefixes: &HashMap, template: Either, @@ -604,9 +614,9 @@ impl TripleTemplate { }; Ok(TripleTemplate { - subject: Self::build_subject_template(storage, ns_resolver, plan, prefixes, s_tpl)?, - predicate: Self::build_predicate_template(storage, ns_resolver, plan, prefixes, p_tpl)?, - object: Self::build_object_template(storage, ns_resolver, plan, prefixes, o_tpl)?, + subject: Self::build_subject_template(ns_solver, plan, prefixes, s_tpl)?, + predicate: Self::build_predicate_template(ns_solver, plan, prefixes, p_tpl)?, + object: Self::build_object_template(ns_solver, plan, prefixes, o_tpl)?, }) } @@ -667,8 +677,7 @@ impl TripleTemplate { } fn build_subject_template( - storage: &dyn Storage, - ns_resolver: &mut NamespaceResolver, + ns_solver: &mut dyn NamespaceSolver, plan: &QueryPlan, prefixes: &HashMap, value: Either, @@ -686,19 +695,13 @@ impl TripleTemplate { ))?, ), Left(VarOrNode::Node(Node::NamedNode(iri))) | Right(VarOrNamedNode::NamedNode(iri)) => { - Left(Subject::Named(iri_as_node( - ns_resolver, - storage, - prefixes, - iri, - )?)) + Left(Subject::Named(iri_as_node(ns_solver, prefixes, iri)?)) } }) } fn build_predicate_template( - storage: &dyn Storage, - ns_resolver: &mut NamespaceResolver, + ns_solver: &mut dyn NamespaceSolver, plan: &QueryPlan, prefixes: &HashMap, value: VarOrNamedNode, @@ -707,15 +710,12 @@ impl TripleTemplate { VarOrNamedNode::Variable(v) => Right(plan.get_var_index(v.as_str()).ok_or( StdError::generic_err("Selected variable not found in query"), )?), - VarOrNamedNode::NamedNode(iri) => { - Left(iri_as_node(ns_resolver, storage, prefixes, iri)?) - } + VarOrNamedNode::NamedNode(iri) => Left(iri_as_node(ns_solver, prefixes, iri)?), }) } fn build_object_template( - storage: &dyn Storage, - ns_resolver: &mut NamespaceResolver, + ns_solver: &mut dyn NamespaceSolver, plan: &QueryPlan, prefixes: &HashMap, value: Either, @@ -733,22 +733,18 @@ impl TripleTemplate { ))?, ), Left(VarOrNodeOrLiteral::Node(Node::NamedNode(iri))) - | Right(VarOrNamedNodeOrLiteral::NamedNode(iri)) => Left(Object::Named(iri_as_node( - ns_resolver, - storage, - prefixes, - iri, - )?)), + | Right(VarOrNamedNodeOrLiteral::NamedNode(iri)) => { + Left(Object::Named(iri_as_node(ns_solver, prefixes, iri)?)) + } Left(VarOrNodeOrLiteral::Literal(l)) | Right(VarOrNamedNodeOrLiteral::Literal(l)) => { - Left(literal_as_object(ns_resolver, storage, prefixes, l)?) + Left(literal_as_object(ns_solver, prefixes, l)?) } }) } } pub struct ResolvedAtomIterator<'a> { - storage: &'a dyn Storage, - ns_resolver: NamespaceResolver, + ns_resolver: NamespaceResolver<'a>, id_issuer: IdentifierIssuer, upstream_iter: ResolvedVariablesIterator<'a>, templates: Vec, @@ -758,14 +754,13 @@ pub struct ResolvedAtomIterator<'a> { impl<'a> ResolvedAtomIterator<'a> { pub fn new( storage: &'a dyn Storage, - ns_resolver: NamespaceResolver, + ns_cache: Vec, id_issuer: IdentifierIssuer, upstream_iter: ResolvedVariablesIterator<'a>, templates: Vec, ) -> Self { Self { - storage, - ns_resolver, + ns_resolver: NamespaceResolver::new(storage, ns_cache.into()), id_issuer, upstream_iter, templates, @@ -794,12 +789,7 @@ impl<'a> Iterator for ResolvedAtomIterator<'a> { } Ok(vars) => { for res in self.templates.iter().map(|template| { - template.resolve( - self.storage, - &mut self.ns_resolver, - &mut self.id_issuer, - &vars, - ) + template.resolve(&mut self.ns_resolver, &mut self.id_issuer, &vars) }) { match res { Ok(Some(atom)) => self.buffer.push_back(Ok(atom)), @@ -853,28 +843,21 @@ impl AtomTemplate { pub fn resolve( &self, - storage: &dyn Storage, - ns_resolver: &mut NamespaceResolver, + ns_solver: &mut dyn NamespaceSolver, id_issuer: &mut IdentifierIssuer, vars: &ResolvedVariables, ) -> StdResult> { - let resolve_ns_fn = &mut |ns_key| { - let res = ns_resolver.resolve_from_key(storage, ns_key); - res.and_then(NamespaceResolver::none_as_error_middleware) - .map(|ns| ns.value) - }; - - let subject = match self.resolve_atom_subject(resolve_ns_fn, id_issuer, vars)? { + let subject = match self.resolve_atom_subject(ns_solver, id_issuer, vars)? { Some(s) => s, None => return Ok(None), }; - let property = match self.resolve_atom_property(resolve_ns_fn, vars)? { + let property = match self.resolve_atom_property(ns_solver, vars)? { Some(p) => p, None => return Ok(None), }; - let value = match self.resolve_atom_value(resolve_ns_fn, id_issuer, vars)? { + let value = match self.resolve_atom_value(ns_solver, id_issuer, vars)? { Some(v) => v, None => return Ok(None), }; @@ -886,22 +869,19 @@ impl AtomTemplate { })) } - fn resolve_atom_subject( + fn resolve_atom_subject( &self, - resolve_ns_fn: &mut F, + ns_solver: &mut dyn NamespaceSolver, id_issuer: &mut IdentifierIssuer, vars: &ResolvedVariables, - ) -> StdResult> - where - F: FnMut(u128) -> StdResult, - { + ) -> StdResult> { Self::resolve_atom_term( &self.subject, ResolvedVariable::as_subject, vars, &mut |value| { Ok(match value { - Subject::Named(n) => rdf::Subject::NamedNode(n.as_iri(resolve_ns_fn)?), + Subject::Named(n) => rdf::Subject::NamedNode(n.as_iri(ns_solver)?), Subject::Blank(n) => rdf::Subject::BlankNode( id_issuer.get_str_or_issue(n.to_string()).to_string(), ), @@ -911,39 +891,33 @@ impl AtomTemplate { ) } - fn resolve_atom_property( + fn resolve_atom_property( &self, - resolve_ns_fn: &mut F, + ns_solver: &mut dyn NamespaceSolver, vars: &ResolvedVariables, - ) -> StdResult> - where - F: FnMut(u128) -> StdResult, - { + ) -> StdResult> { Self::resolve_atom_term( &self.property, ResolvedVariable::as_predicate, vars, - &mut |value| value.as_iri(resolve_ns_fn).map(rdf::Property), + &mut |value| value.as_iri(ns_solver).map(rdf::Property), "predicate", ) } - fn resolve_atom_value( + fn resolve_atom_value( &self, - resolve_ns_fn: &mut F, + ns_solver: &mut dyn NamespaceSolver, id_issuer: &mut IdentifierIssuer, vars: &ResolvedVariables, - ) -> StdResult> - where - F: FnMut(u128) -> StdResult, - { + ) -> StdResult> { Self::resolve_atom_term( &self.value, ResolvedVariable::as_object, vars, &mut |value| { Ok(match value { - Object::Named(n) => rdf::Value::NamedNode(n.as_iri(resolve_ns_fn)?), + Object::Named(n) => rdf::Value::NamedNode(n.as_iri(ns_solver)?), Object::Blank(n) => { rdf::Value::BlankNode(id_issuer.get_str_or_issue(n.to_string()).to_string()) } @@ -953,7 +927,7 @@ impl AtomTemplate { rdf::Value::LiteralLang(value, language) } state::Literal::Typed { value, datatype } => { - rdf::Value::LiteralDatatype(value, datatype.as_iri(resolve_ns_fn)?) + rdf::Value::LiteralDatatype(value, datatype.as_iri(ns_solver)?) } }, }) @@ -1226,7 +1200,7 @@ mod test { ]; for case in cases { - let engine = QueryEngine::new(&deps.storage); + let engine = QueryEngine::new(&deps.storage, vec![]); assert_eq!( engine.select(case.plan, case.selection).and_then(|res| Ok(( res.head.clone(), @@ -1369,7 +1343,7 @@ mod test { }, ]; - let engine = QueryEngine::new(&deps.storage); + let engine = QueryEngine::new(&deps.storage, vec![]); for case in cases { assert_eq!(engine.eval_plan(case.plan).count(), case.expects); } diff --git a/contracts/axone-cognitarium/src/querier/mapper.rs b/contracts/axone-cognitarium/src/querier/mapper.rs index ac353dbc..2a05f859 100644 --- a/contracts/axone-cognitarium/src/querier/mapper.rs +++ b/contracts/axone-cognitarium/src/querier/mapper.rs @@ -1,13 +1,12 @@ use crate::msg::{Literal, IRI}; use crate::state; -use crate::state::{NamespaceResolver, Object}; +use crate::state::{NamespaceSolver, Object}; use axone_rdf::uri::{expand_uri, explode_iri}; -use cosmwasm_std::{StdResult, Storage}; +use cosmwasm_std::StdResult; use std::collections::HashMap; pub fn literal_as_object( - ns_resolver: &mut NamespaceResolver, - storage: &dyn Storage, + ns_solver: &mut dyn NamespaceSolver, prefixes: &HashMap, literal: Literal, ) -> StdResult { @@ -18,14 +17,13 @@ pub fn literal_as_object( } Literal::TypedValue { value, datatype } => state::Literal::Typed { value, - datatype: iri_as_node(ns_resolver, storage, prefixes, datatype)?, + datatype: iri_as_node(ns_solver, prefixes, datatype)?, }, })) } pub fn iri_as_node( - ns_resolver: &mut NamespaceResolver, - storage: &dyn Storage, + ns_solver: &mut dyn NamespaceSolver, prefixes: &HashMap, iri: IRI, ) -> StdResult { @@ -35,12 +33,16 @@ pub fn iri_as_node( } .and_then(|iri| explode_iri(&iri)) .and_then(|(ns_key, v)| { - ns_resolver - .resolve_from_val(storage, ns_key) - .and_then(NamespaceResolver::none_as_error_middleware) - .map(|ns| state::Node { - namespace: ns.key, - value: v, - }) + ns_solver.resolve_from_val(ns_key).map(|ns| state::Node { + namespace: ns.key, + value: v, + }) }) } + +pub fn iri_as_string(iri: IRI, prefixes: &HashMap) -> StdResult { + match iri { + IRI::Prefixed(prefixed) => expand_uri(&prefixed, prefixes), + IRI::Full(full) => Ok(full), + } +} diff --git a/contracts/axone-cognitarium/src/querier/plan_builder.rs b/contracts/axone-cognitarium/src/querier/plan_builder.rs index 970508b1..fae1bfb9 100644 --- a/contracts/axone-cognitarium/src/querier/plan_builder.rs +++ b/contracts/axone-cognitarium/src/querier/plan_builder.rs @@ -1,13 +1,14 @@ use crate::msg::{Node, TriplePattern, VarOrNamedNode, VarOrNode, VarOrNodeOrLiteral, WhereClause}; use crate::querier::mapper::{iri_as_node, literal_as_object}; use crate::querier::plan::{PatternValue, PlanVariable, QueryNode, QueryPlan}; -use crate::state::{HasCachedNamespaces, Namespace, NamespaceResolver, Object, Predicate, Subject}; -use cosmwasm_std::{StdError, StdResult, Storage}; +use crate::state::{ + HasCachedNamespaces, Namespace, NamespaceQuerier, NamespaceResolver, Object, Predicate, Subject, +}; +use cosmwasm_std::{StdResult, Storage}; use std::collections::HashMap; pub struct PlanBuilder<'a> { - storage: &'a dyn Storage, - ns_resolver: NamespaceResolver, + ns_resolver: NamespaceResolver<'a>, prefixes: &'a HashMap, variables: Vec, limit: Option, @@ -21,8 +22,7 @@ impl<'a> PlanBuilder<'a> { ns_cache: Option>, ) -> Self { Self { - storage, - ns_resolver: ns_cache.map_or_else(NamespaceResolver::new, Into::into), + ns_resolver: NamespaceResolver::new(storage, ns_cache.unwrap_or(vec![]).into()), prefixes, variables: Vec::new(), skip: None, @@ -133,7 +133,7 @@ impl<'a> PlanBuilder<'a> { value.lookup_bound_variable(&mut |v| bound_variables.push(v)); Some(value) } - Err(err) if NamespaceResolver::is_ns_not_found_error(&err) => None, + Err(err) if NamespaceQuerier::is_ns_not_found_error(&err) => None, _ => Some(pattern_res?), }) } @@ -145,7 +145,7 @@ impl<'a> PlanBuilder<'a> { PatternValue::BlankVariable(self.resolve_blank_variable(b)) } VarOrNode::Node(Node::NamedNode(iri)) => PatternValue::Constant(Subject::Named( - iri_as_node(&mut self.ns_resolver, self.storage, self.prefixes, iri)?, + iri_as_node(&mut self.ns_resolver, self.prefixes, iri)?, )), }) } @@ -156,12 +156,9 @@ impl<'a> PlanBuilder<'a> { ) -> StdResult> { Ok(match value { VarOrNamedNode::Variable(v) => PatternValue::Variable(self.resolve_basic_variable(v)), - VarOrNamedNode::NamedNode(iri) => PatternValue::Constant(iri_as_node( - &mut self.ns_resolver, - self.storage, - self.prefixes, - iri, - )?), + VarOrNamedNode::NamedNode(iri) => { + PatternValue::Constant(iri_as_node(&mut self.ns_resolver, self.prefixes, iri)?) + } }) } @@ -176,20 +173,12 @@ impl<'a> PlanBuilder<'a> { VarOrNodeOrLiteral::Node(Node::BlankNode(b)) => { PatternValue::BlankVariable(self.resolve_blank_variable(b)) } - VarOrNodeOrLiteral::Node(Node::NamedNode(iri)) => { - PatternValue::Constant(Object::Named(iri_as_node( - &mut self.ns_resolver, - self.storage, - self.prefixes, - iri, - )?)) + VarOrNodeOrLiteral::Node(Node::NamedNode(iri)) => PatternValue::Constant( + Object::Named(iri_as_node(&mut self.ns_resolver, self.prefixes, iri)?), + ), + VarOrNodeOrLiteral::Literal(l) => { + PatternValue::Constant(literal_as_object(&mut self.ns_resolver, self.prefixes, l)?) } - VarOrNodeOrLiteral::Literal(l) => PatternValue::Constant(literal_as_object( - &mut self.ns_resolver, - self.storage, - self.prefixes, - l, - )?), }) } diff --git a/contracts/axone-cognitarium/src/querier/variable.rs b/contracts/axone-cognitarium/src/querier/variable.rs index af90d28c..d3458d91 100644 --- a/contracts/axone-cognitarium/src/querier/variable.rs +++ b/contracts/axone-cognitarium/src/querier/variable.rs @@ -1,5 +1,5 @@ use crate::msg::{Value, IRI}; -use crate::state::{Literal, Object, Predicate, Subject}; +use crate::state::{Literal, NamespaceSolver, Object, Predicate, Subject}; use axone_rdf::normalize::IdentifierIssuer; use cosmwasm_std::StdResult; @@ -49,10 +49,11 @@ impl ResolvedVariable { }) } - pub fn as_value(&self, ns_fn: &mut F, id_issuer: &mut IdentifierIssuer) -> StdResult - where - F: FnMut(u128) -> StdResult, - { + pub fn as_value( + &self, + ns_fn: &mut dyn NamespaceSolver, + id_issuer: &mut IdentifierIssuer, + ) -> StdResult { Ok(match self { ResolvedVariable::Subject(subject) => match subject { Subject::Named(named) => named.as_iri(ns_fn).map(|iri| Value::URI { @@ -149,7 +150,7 @@ impl ResolvedVariables { #[cfg(test)] mod tests { use super::*; - use crate::state::{Literal, Node}; + use crate::state::{Literal, Namespace, Node}; use cosmwasm_std::StdError; #[test] @@ -206,11 +207,26 @@ mod tests { } } - fn ns(i: u128) -> StdResult { - match i { - 0 => Ok("foo".to_string()), - 1 => Ok("bar".to_string()), - _ => Err(StdError::generic_err("namespace not found")), + struct TestNamespaceSolver; + impl NamespaceSolver for TestNamespaceSolver { + fn resolve_from_key(&mut self, key: u128) -> StdResult { + match key { + 0 => Ok(Namespace { + key: 0, + value: "foo".to_string(), + counter: 1, + }), + 1 => Ok(Namespace { + key: 1, + value: "bar".to_string(), + counter: 1, + }), + _ => Err(StdError::generic_err("namespace not found")), + } + } + + fn resolve_from_val(&mut self, _value: String) -> StdResult { + Err(StdError::generic_err("namespace not found")) } } @@ -294,8 +310,9 @@ mod tests { ]; let mut id_issuer = IdentifierIssuer::new("b", 0u128); + let mut ns_solver = TestNamespaceSolver; for (var, expected) in cases { - assert_eq!(var.as_value(&mut ns, &mut id_issuer), expected) + assert_eq!(var.as_value(&mut ns_solver, &mut id_issuer), expected) } } diff --git a/contracts/axone-cognitarium/src/state/namespaces.rs b/contracts/axone-cognitarium/src/state/namespaces.rs index 24129e06..5568cdd3 100644 --- a/contracts/axone-cognitarium/src/state/namespaces.rs +++ b/contracts/axone-cognitarium/src/state/namespaces.rs @@ -41,15 +41,15 @@ pub fn namespaces<'a>() -> IndexedMap> { ) } -/// [NamespaceResolver] is a [Namespace] querying service allowing to resolve namespaces either by +/// [NamespaceQuerier] is a [Namespace] querying service allowing to resolve namespaces either by /// namespace's value or namespace's internal state key. It implements a two way indexed in-memory /// cache to mitigate state access. -pub struct NamespaceResolver { +pub struct NamespaceQuerier { by_val: BTreeMap>>, by_key: BTreeMap>>, } -impl NamespaceResolver { +impl NamespaceQuerier { pub fn new() -> Self { Self { by_key: BTreeMap::new(), @@ -138,7 +138,7 @@ impl NamespaceResolver { } } -impl Default for NamespaceResolver { +impl Default for NamespaceQuerier { fn default() -> Self { Self::new() } @@ -154,7 +154,7 @@ pub trait HasCachedNamespaces { fn clear_cache(&mut self); } -impl HasCachedNamespaces for NamespaceResolver { +impl HasCachedNamespaces for NamespaceQuerier { fn cached_namespaces(&self) -> Vec { self.by_key .iter() @@ -168,9 +168,9 @@ impl HasCachedNamespaces for NamespaceResolver { } } -impl From> for NamespaceResolver { +impl From> for NamespaceQuerier { fn from(value: Vec) -> Self { - let mut resolver = NamespaceResolver::new(); + let mut resolver = NamespaceQuerier::new(); for ns in value { resolver.insert(ns); } @@ -179,12 +179,55 @@ impl From> for NamespaceResolver { } } +pub trait NamespaceSolver { + fn resolve_from_key(&mut self, key: u128) -> StdResult; + fn resolve_from_val(&mut self, value: String) -> StdResult; +} + +pub struct NamespaceResolver<'a> { + storage: &'a dyn Storage, + ns_querier: NamespaceQuerier, +} + +impl<'a> NamespaceResolver<'a> { + pub fn new(storage: &'a dyn Storage, ns_cache: Vec) -> Self { + Self { + storage, + ns_querier: ns_cache.into(), + } + } +} + +impl<'a> NamespaceSolver for NamespaceResolver<'a> { + fn resolve_from_key(&mut self, key: u128) -> StdResult { + self.ns_querier + .resolve_from_key(self.storage, key) + .and_then(NamespaceQuerier::none_as_error_middleware) + } + + fn resolve_from_val(&mut self, value: String) -> StdResult { + self.ns_querier + .resolve_from_val(self.storage, value) + .and_then(NamespaceQuerier::none_as_error_middleware) + } +} + +impl<'a> HasCachedNamespaces for NamespaceResolver<'a> { + fn cached_namespaces(&self) -> Vec { + self.ns_querier.cached_namespaces() + } + + fn clear_cache(&mut self) { + self.ns_querier.clear_cache() + } +} + /// Allow to batch write operations on [Namespace] taking care of the [NAMESPACE_KEY_INCREMENT], it -/// manages insertions/deletions as well as counting references. It internally use a [NamespaceResolver] +/// manages insertions/deletions as well as counting references. It internally use a [NamespaceQuerier] /// as a cache of new/removed/modified namespaces, to finally apply writing to the state when /// calling [Self::flush]. pub struct NamespaceBatchService { - ns_resolver: NamespaceResolver, + ns_resolver: NamespaceQuerier, ns_key_inc: u128, ns_count_diff: i128, } @@ -192,7 +235,7 @@ pub struct NamespaceBatchService { impl NamespaceBatchService { pub fn new(storage: &dyn Storage) -> StdResult { Ok(Self { - ns_resolver: NamespaceResolver::new(), + ns_resolver: NamespaceQuerier::new(), ns_key_inc: NAMESPACE_KEY_INCREMENT.load(storage)?, ns_count_diff: 0, }) diff --git a/contracts/axone-cognitarium/src/state/triples.rs b/contracts/axone-cognitarium/src/state/triples.rs index aa62b1fd..f3bb7593 100644 --- a/contracts/axone-cognitarium/src/state/triples.rs +++ b/contracts/axone-cognitarium/src/state/triples.rs @@ -1,3 +1,4 @@ +use crate::state::NamespaceSolver; use blake3::Hash; use cosmwasm_std::StdResult; use cw_storage_plus::{Index, IndexList, IndexedMap, MultiIndex}; @@ -150,11 +151,8 @@ impl Node { key } - pub fn as_iri(&self, ns_fn: &mut F) -> StdResult - where - F: FnMut(u128) -> StdResult, - { - Ok(ns_fn(self.namespace)? + &self.value) + pub fn as_iri(&self, ns_solver: &mut dyn NamespaceSolver) -> StdResult { + Ok(ns_solver.resolve_from_key(self.namespace)?.value + &self.value) } } diff --git a/contracts/axone-cognitarium/src/storer/engine.rs b/contracts/axone-cognitarium/src/storer/engine.rs index 105989b6..fa165416 100644 --- a/contracts/axone-cognitarium/src/storer/engine.rs +++ b/contracts/axone-cognitarium/src/storer/engine.rs @@ -1,7 +1,7 @@ use crate::error::StoreError; use crate::state::{ - triples, Literal, NamespaceBatchService, Node, Object, Store, Subject, Triple, - BLANK_NODE_IDENTIFIER_COUNTER, BLANK_NODE_SIZE, STORE, + triples, Literal, NamespaceBatchService, NamespaceQuerier, Node, Object, Store, Subject, + Triple, BLANK_NODE_IDENTIFIER_COUNTER, BLANK_NODE_SIZE, STORE, }; use crate::ContractError; use axone_rdf::normalize::IdentifierIssuer; @@ -280,15 +280,10 @@ impl<'a> StoreEngine<'a> { } fn node_size(&mut self, node: &Node) -> StdResult { - if let Some(ns) = self - .ns_batch_svc - .resolve_from_key(self.storage, node.namespace)? - { - return Ok(ns.value.len() + node.value.len()); - } - - // Should never happen as in its use the namespace should be already cached. - Err(StdError::not_found("Namespace")) + self.ns_batch_svc + .resolve_from_key(self.storage, node.namespace) + .and_then(NamespaceQuerier::none_as_error_middleware) + .map(|ns| ns.value.len() + node.value.len()) } fn object_size(&mut self, object: &Object) -> StdResult { From 2a40d0644fb8cddfff30edb80b3d0841ad5198c4 Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Thu, 22 Aug 2024 10:39:20 +0200 Subject: [PATCH 04/15] feat(cognitarium): introduce core expression design --- .../src/querier/expression.rs | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 contracts/axone-cognitarium/src/querier/expression.rs diff --git a/contracts/axone-cognitarium/src/querier/expression.rs b/contracts/axone-cognitarium/src/querier/expression.rs new file mode 100644 index 00000000..a53147a2 --- /dev/null +++ b/contracts/axone-cognitarium/src/querier/expression.rs @@ -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), + Or(Vec), + Equal(Box, Box), + Greater(Box, Box), + GreaterOrEqual(Box, Box), + Less(Box, Box), + LessOrEqual(Box, Box), +} + +impl Expression { + pub fn bound_variables(&self) -> BTreeSet { + 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 { + 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) -> StdResult { + Ok(Term::String(iri_as_string(iri, prefixes)?)) + } + + pub fn from_literal( + literal: msg::Literal, + prefixes: &HashMap, + ) -> StdResult { + 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 for Term { + fn partial_cmp(&self, other: &Term) -> Option { + 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, + } + } +} From 29b9cf01ec5985dc9dfb9d25cffffcccd46dfd1d Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Thu, 22 Aug 2024 10:39:58 +0200 Subject: [PATCH 05/15] feat(cognitarium): allow to issue term from variable --- .../axone-cognitarium/src/querier/variable.rs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/contracts/axone-cognitarium/src/querier/variable.rs b/contracts/axone-cognitarium/src/querier/variable.rs index d3458d91..511d13be 100644 --- a/contracts/axone-cognitarium/src/querier/variable.rs +++ b/contracts/axone-cognitarium/src/querier/variable.rs @@ -1,4 +1,5 @@ use crate::msg::{Value, IRI}; +use crate::querier::expression::Term; use crate::state::{Literal, NamespaceSolver, Object, Predicate, Subject}; use axone_rdf::normalize::IdentifierIssuer; use cosmwasm_std::StdResult; @@ -95,6 +96,31 @@ impl ResolvedVariable { }, }) } + + pub fn as_term(&self, ns_solver: &mut dyn NamespaceSolver) -> StdResult { + Ok(match self { + ResolvedVariable::Subject(subject) => match subject { + Subject::Named(named) => named.as_iri(ns_solver).map(Term::String)?, + Subject::Blank(blank) => Term::String(format!("_:{}", blank)), + }, + ResolvedVariable::Predicate(predicate) => { + predicate.as_iri(ns_solver).map(Term::String)? + } + ResolvedVariable::Object(object) => match object { + Object::Named(named) => named.as_iri(ns_solver).map(Term::String)?, + Object::Blank(blank) => Term::String(format!("_:{}", blank)), + Object::Literal(literal) => Term::String(match literal { + Literal::Simple { value } => value.clone(), + Literal::I18NString { value, language } => { + format!("{}{}", value, language).to_string() + } + Literal::Typed { value, datatype } => { + format!("{}{}", value, datatype.as_iri(ns_solver)?).to_string() + } + }), + }, + }) + } } #[derive(Eq, PartialEq, Debug, Clone)] From 945bc3a6c89e4ef90c1ef87294c77219090d5b79 Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Thu, 22 Aug 2024 10:40:27 +0200 Subject: [PATCH 06/15] feat(cognitarium): add expression filtering in where clause --- contracts/axone-cognitarium/src/msg.rs | 38 +++++++++++- .../axone-cognitarium/src/querier/engine.rs | 33 ++++++++++ .../axone-cognitarium/src/querier/mod.rs | 1 + .../axone-cognitarium/src/querier/plan.rs | 8 +++ .../src/querier/plan_builder.rs | 60 ++++++++++++++++++- 5 files changed, 138 insertions(+), 2 deletions(-) diff --git a/contracts/axone-cognitarium/src/msg.rs b/contracts/axone-cognitarium/src/msg.rs index 6bd049e1..19ac2dc1 100644 --- a/contracts/axone-cognitarium/src/msg.rs +++ b/contracts/axone-cognitarium/src/msg.rs @@ -464,7 +464,7 @@ pub enum SelectItem { } /// # WhereClause -/// Represents a WHERE clause in a [SelectQuery], i.e. a set of conditions to filter the results. +/// Represents a WHERE clause, i.e. a set of conditions to filter the results. #[cw_serde] pub enum WhereClause { /// # Bgp @@ -474,6 +474,42 @@ pub enum WhereClause { /// # LateralJoin /// Evaluates right for all result row of left LateralJoin { left: Box, right: Box }, + + /// # Filter + /// Filters the inner clause matching the expression. + /// The solutions coming from the inner clause that do not match the expression are discarded. + /// The variables provided in the inner clause are available in the filter expression. + Filter { expr: Expression, inner: Box }, +} + +/// # Expression +/// Represents a logical combination of operations whose evaluation results in a term. +#[cw_serde] +pub enum Expression { + /// A named node constant. + NamedNode(IRI), + /// A literal constant. + Literal(Literal), + /// A variable that must be bound for evaluation. + Variable(String), + /// Logical conjunction of expressions. + /// All expressions must evaluate to true for the conjunction to be true. + /// If the conjunction is empty, it is considered true. + And(Vec), + /// Logical disjunction of expressions. + /// At least one expression must evaluate to true for the disjunction to be true. + /// If the disjunction is empty, it is considered false. + Or(Vec), + /// Equality comparison. + Equal(Box, Box), + /// Greater than comparison. + Greater(Box, Box), + /// Greater or equal comparison. + GreaterOrEqual(Box, Box), + /// Less than comparison. + Less(Box, Box), + /// Less or equal comparison. + LessOrEqual(Box, Box), } /// # TripleDeleteTemplate diff --git a/contracts/axone-cognitarium/src/querier/engine.rs b/contracts/axone-cognitarium/src/querier/engine.rs index 6b95d18f..f84abbf4 100644 --- a/contracts/axone-cognitarium/src/querier/engine.rs +++ b/contracts/axone-cognitarium/src/querier/engine.rs @@ -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}; @@ -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))) @@ -173,6 +185,27 @@ impl<'a> QueryEngine<'a> { type ResolvedVariablesIterator<'a> = Box> + '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, + ) -> Self { + Self { + upstream, + expr, + ns_resolver: NamespaceResolver::new(storage, ns_cache.into()), + } + } +} + impl<'a> Iterator for FilterIterator<'a> { type Item = StdResult; diff --git a/contracts/axone-cognitarium/src/querier/mod.rs b/contracts/axone-cognitarium/src/querier/mod.rs index b9ecd6ff..84e4d6e7 100644 --- a/contracts/axone-cognitarium/src/querier/mod.rs +++ b/contracts/axone-cognitarium/src/querier/mod.rs @@ -1,4 +1,5 @@ mod engine; +mod expression; mod mapper; mod plan; mod plan_builder; diff --git a/contracts/axone-cognitarium/src/querier/plan.rs b/contracts/axone-cognitarium/src/querier/plan.rs index 6feafd07..6b8c6d09 100644 --- a/contracts/axone-cognitarium/src/querier/plan.rs +++ b/contracts/axone-cognitarium/src/querier/plan.rs @@ -1,3 +1,4 @@ +use crate::querier::expression::Expression; use crate::state::{Object, Predicate, Subject}; use std::collections::BTreeSet; @@ -73,6 +74,9 @@ pub enum QueryNode { /// left node to use them as right node values. ForLoopJoin { left: Box, right: Box }, + /// Filter the results of the inner node by applying the expression. + Filter { expr: Expression, inner: Box }, + /// Skip the specified first elements from the child node. Skip { child: Box, first: usize }, @@ -114,6 +118,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); } diff --git a/contracts/axone-cognitarium/src/querier/plan_builder.rs b/contracts/axone-cognitarium/src/querier/plan_builder.rs index fae1bfb9..6ecca6ef 100644 --- a/contracts/axone-cognitarium/src/querier/plan_builder.rs +++ b/contracts/axone-cognitarium/src/querier/plan_builder.rs @@ -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> { @@ -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 }) + } } } @@ -101,6 +115,50 @@ impl<'a> PlanBuilder<'a> { .unwrap_or(Ok(QueryNode::noop())) } + fn build_expression(&mut self, expr: &msg::Expression) -> StdResult { + 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::>>() + .map(Expression::And), + msg::Expression::Or(exprs) => exprs + .iter() + .map(|e| self.build_expression(e)) + .collect::>>() + .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 { let subject_res = self.build_subject_pattern(pattern.subject.clone()); let predicate_res = self.build_predicate_pattern(pattern.predicate.clone()); From e17998829233fab5f6257ce76ac69c817c76e047 Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Thu, 22 Aug 2024 10:40:44 +0200 Subject: [PATCH 07/15] test(cognitarium): cover variable to term conversion --- .../axone-cognitarium/src/querier/variable.rs | 129 +++++++++++++++++- 1 file changed, 127 insertions(+), 2 deletions(-) diff --git a/contracts/axone-cognitarium/src/querier/variable.rs b/contracts/axone-cognitarium/src/querier/variable.rs index 511d13be..438cae86 100644 --- a/contracts/axone-cognitarium/src/querier/variable.rs +++ b/contracts/axone-cognitarium/src/querier/variable.rs @@ -247,12 +247,12 @@ mod tests { value: "bar".to_string(), counter: 1, }), - _ => Err(StdError::generic_err("namespace not found")), + _ => Err(StdError::not_found("Namespace")), } } fn resolve_from_val(&mut self, _value: String) -> StdResult { - Err(StdError::generic_err("namespace not found")) + Err(StdError::not_found("Namespace")) } } @@ -333,6 +333,37 @@ mod tests { datatype: Some(IRI::Full("foobar".to_string())), }), ), + ( + ResolvedVariable::Subject(Subject::Named(Node { + namespace: 12, + value: "unknown".to_string(), + })), + Err(StdError::not_found("Namespace")), + ), + ( + ResolvedVariable::Predicate(Node { + namespace: 12, + value: "unknown".to_string(), + }), + Err(StdError::not_found("Namespace")), + ), + ( + ResolvedVariable::Object(Object::Named(Node { + namespace: 12, + value: "unknown".to_string(), + })), + Err(StdError::not_found("Namespace")), + ), + ( + ResolvedVariable::Object(Object::Literal(Literal::Typed { + datatype: Node { + namespace: 12, + value: "unknown".to_string(), + }, + value: "unknown".to_string(), + })), + Err(StdError::not_found("Namespace")), + ), ]; let mut id_issuer = IdentifierIssuer::new("b", 0u128); @@ -378,4 +409,98 @@ mod tests { let result2 = vars1.merge_with(&vars3); assert_eq!(result2, None); } + + #[test] + fn terms() { + let cases = vec![ + ( + ResolvedVariable::Subject(Subject::Named(Node { + namespace: 0, + value: "bar".to_string(), + })), + Ok(Term::String("foobar".to_string())), + ), + ( + ResolvedVariable::Subject(Subject::Blank(0u128)), + Ok(Term::String("_:0".to_string())), + ), + ( + ResolvedVariable::Predicate(Node { + namespace: 1, + value: "foo".to_string(), + }), + Ok(Term::String("barfoo".to_string())), + ), + ( + ResolvedVariable::Object(Object::Named(Node { + namespace: 1, + value: "foo".to_string(), + })), + Ok(Term::String("barfoo".to_string())), + ), + ( + ResolvedVariable::Object(Object::Blank(0u128)), + Ok(Term::String("_:0".to_string())), + ), + ( + ResolvedVariable::Object(Object::Literal(Literal::Simple { + value: "foo".to_string(), + })), + Ok(Term::String("foo".to_string())), + ), + ( + ResolvedVariable::Object(Object::Literal(Literal::I18NString { + value: "foo".to_string(), + language: "fr".to_string(), + })), + Ok(Term::String("foofr".to_string())), + ), + ( + ResolvedVariable::Object(Object::Literal(Literal::Typed { + value: "foo".to_string(), + datatype: Node { + namespace: 0, + value: "bar".to_string(), + }, + })), + Ok(Term::String("foofoobar".to_string())), + ), + ( + ResolvedVariable::Subject(Subject::Named(Node { + namespace: 12, + value: "unknown".to_string(), + })), + Err(StdError::not_found("Namespace")), + ), + ( + ResolvedVariable::Predicate(Node { + namespace: 12, + value: "unknown".to_string(), + }), + Err(StdError::not_found("Namespace")), + ), + ( + ResolvedVariable::Object(Object::Named(Node { + namespace: 12, + value: "unknown".to_string(), + })), + Err(StdError::not_found("Namespace")), + ), + ( + ResolvedVariable::Object(Object::Literal(Literal::Typed { + datatype: Node { + namespace: 12, + value: "unknown".to_string(), + }, + value: "unknown".to_string(), + })), + Err(StdError::not_found("Namespace")), + ), + ]; + + let mut ns_solver = TestNamespaceSolver; + for (var, expected) in cases { + assert_eq!(var.as_term(&mut ns_solver), expected) + } + } } From 9f389d488d8172886e722aa49bd43bb69020fbfe Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Thu, 22 Aug 2024 10:44:18 +0200 Subject: [PATCH 08/15] refactor(cognitarium): deduplicate bound var resolution --- .../src/querier/expression.rs | 57 +++++++++---------- .../axone-cognitarium/src/querier/plan.rs | 13 ++--- .../src/querier/plan_builder.rs | 1 + .../axone-cognitarium/src/querier/variable.rs | 13 +++++ 4 files changed, 44 insertions(+), 40 deletions(-) diff --git a/contracts/axone-cognitarium/src/querier/expression.rs b/contracts/axone-cognitarium/src/querier/expression.rs index a53147a2..97ca7243 100644 --- a/contracts/axone-cognitarium/src/querier/expression.rs +++ b/contracts/axone-cognitarium/src/querier/expression.rs @@ -1,10 +1,11 @@ use crate::msg; use crate::querier::mapper::iri_as_string; +use crate::querier::variable::HasBoundVariables; use crate::querier::ResolvedVariables; use crate::state::NamespaceSolver; use cosmwasm_std::{StdError, StdResult}; use std::cmp::Ordering; -use std::collections::{BTreeSet, HashMap}; +use std::collections::HashMap; #[derive(Eq, PartialEq, Debug, Clone)] pub enum Expression { @@ -20,36 +21,6 @@ pub enum Expression { } impl Expression { - pub fn bound_variables(&self) -> BTreeSet { - 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, @@ -97,6 +68,30 @@ impl Expression { } } +impl HasBoundVariables for Expression { + 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); + } + } + } +} + #[derive(Eq, PartialEq, Debug, Clone)] pub enum Term { String(String), diff --git a/contracts/axone-cognitarium/src/querier/plan.rs b/contracts/axone-cognitarium/src/querier/plan.rs index 6b8c6d09..97e66778 100644 --- a/contracts/axone-cognitarium/src/querier/plan.rs +++ b/contracts/axone-cognitarium/src/querier/plan.rs @@ -1,4 +1,5 @@ use crate::querier::expression::Expression; +use crate::querier::variable::HasBoundVariables; use crate::state::{Object, Predicate, Subject}; use std::collections::BTreeSet; @@ -90,16 +91,10 @@ impl QueryNode { bound_variables: Vec::new(), } } +} - pub fn bound_variables(&self) -> BTreeSet { - 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)) { +impl HasBoundVariables for QueryNode { + fn lookup_bound_variables(&self, callback: &mut impl FnMut(usize)) { match self { QueryNode::TriplePattern { subject, diff --git a/contracts/axone-cognitarium/src/querier/plan_builder.rs b/contracts/axone-cognitarium/src/querier/plan_builder.rs index 6ecca6ef..5d807993 100644 --- a/contracts/axone-cognitarium/src/querier/plan_builder.rs +++ b/contracts/axone-cognitarium/src/querier/plan_builder.rs @@ -3,6 +3,7 @@ use crate::msg::{Node, TriplePattern, VarOrNamedNode, VarOrNode, VarOrNodeOrLite 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::querier::variable::HasBoundVariables; use crate::state::{ HasCachedNamespaces, Namespace, NamespaceQuerier, NamespaceResolver, Object, Predicate, Subject, }; diff --git a/contracts/axone-cognitarium/src/querier/variable.rs b/contracts/axone-cognitarium/src/querier/variable.rs index 438cae86..7d2c193e 100644 --- a/contracts/axone-cognitarium/src/querier/variable.rs +++ b/contracts/axone-cognitarium/src/querier/variable.rs @@ -3,6 +3,7 @@ use crate::querier::expression::Term; use crate::state::{Literal, NamespaceSolver, Object, Predicate, Subject}; use axone_rdf::normalize::IdentifierIssuer; use cosmwasm_std::StdResult; +use std::collections::BTreeSet; #[derive(Eq, PartialEq, Debug, Clone)] pub enum ResolvedVariable { @@ -173,6 +174,18 @@ impl ResolvedVariables { } } +pub trait HasBoundVariables { + fn bound_variables(&self) -> BTreeSet { + let mut vars = BTreeSet::new(); + self.lookup_bound_variables(&mut |v| { + vars.insert(v); + }); + vars + } + + fn lookup_bound_variables(&self, callback: &mut impl FnMut(usize)); +} + #[cfg(test)] mod tests { use super::*; From 41a0a78b1e9082d179f3ad7a8731aeb2c49add20 Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Thu, 22 Aug 2024 11:20:19 +0200 Subject: [PATCH 09/15] feat(cognitarium): add the 'not' expression --- contracts/axone-cognitarium/src/msg.rs | 2 + .../src/querier/expression.rs | 199 ++++++++++++++++++ .../src/querier/plan_builder.rs | 4 + 3 files changed, 205 insertions(+) diff --git a/contracts/axone-cognitarium/src/msg.rs b/contracts/axone-cognitarium/src/msg.rs index 19ac2dc1..eb9baefc 100644 --- a/contracts/axone-cognitarium/src/msg.rs +++ b/contracts/axone-cognitarium/src/msg.rs @@ -510,6 +510,8 @@ pub enum Expression { Less(Box, Box), /// Less or equal comparison. LessOrEqual(Box, Box), + /// Negation of an expression. + Not(Box), } /// # TripleDeleteTemplate diff --git a/contracts/axone-cognitarium/src/querier/expression.rs b/contracts/axone-cognitarium/src/querier/expression.rs index 97ca7243..fe671a16 100644 --- a/contracts/axone-cognitarium/src/querier/expression.rs +++ b/contracts/axone-cognitarium/src/querier/expression.rs @@ -18,6 +18,7 @@ pub enum Expression { GreaterOrEqual(Box, Box), Less(Box, Box), LessOrEqual(Box, Box), + Not(Box), } impl Expression { @@ -64,6 +65,7 @@ impl Expression { Expression::LessOrEqual(left, right) => Ok(Term::Boolean( left.evaluate(vars, ns_solver)? <= right.evaluate(vars, ns_solver)?, )), + Expression::Not(expr) => Ok(Term::Boolean(!expr.evaluate(vars, ns_solver)?.as_bool())), } } } @@ -88,6 +90,9 @@ impl HasBoundVariables for Expression { left.lookup_bound_variables(callback); right.lookup_bound_variables(callback); } + Expression::Not(expr) => { + expr.lookup_bound_variables(callback); + } } } } @@ -146,3 +151,197 @@ impl PartialOrd for Term { } } } + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::BTreeSet; + + #[test] + fn expression_bound_variables() { + let cases = vec![ + ( + Expression::Constant(Term::String("foo".to_string())), + vec![], + ), + (Expression::Variable(0), vec![0]), + ( + Expression::And(vec![Expression::Variable(0), Expression::Variable(1)]), + vec![0, 1], + ), + ( + Expression::Or(vec![Expression::Variable(0), Expression::Variable(1)]), + vec![0, 1], + ), + ( + Expression::Equal( + Box::new(Expression::Variable(0)), + Box::new(Expression::Variable(1)), + ), + vec![0, 1], + ), + ( + Expression::Greater( + Box::new(Expression::Variable(0)), + Box::new(Expression::Variable(1)), + ), + vec![0, 1], + ), + ( + Expression::GreaterOrEqual( + Box::new(Expression::Variable(0)), + Box::new(Expression::Variable(1)), + ), + vec![0, 1], + ), + ( + Expression::Less( + Box::new(Expression::Variable(0)), + Box::new(Expression::Variable(1)), + ), + vec![0, 1], + ), + ( + Expression::LessOrEqual( + Box::new(Expression::Variable(0)), + Box::new(Expression::Variable(1)), + ), + vec![0, 1], + ), + (Expression::Not(Box::new(Expression::Variable(0))), vec![0]), + ]; + + for case in cases { + assert_eq!(case.0.bound_variables(), BTreeSet::from_iter(case.1)); + } + } + + #[test] + fn term_from_iri() { + let cases = vec![ + ( + msg::IRI::Prefixed("foo:bar".to_string()), + Ok(Term::String("http://example.com/bar".to_string())), + ), + ( + msg::IRI::Full("foo:bar".to_string()), + Ok(Term::String("foo:bar".to_string())), + ), + ( + msg::IRI::Prefixed("unknown:bar".to_string()), + Err(StdError::generic_err("Prefix not found: unknown")), + ), + ]; + + let mut prefixes = HashMap::new(); + prefixes.insert("foo".to_string(), "http://example.com/".to_string()); + + for case in cases { + assert_eq!(Term::from_iri(case.0, &prefixes), case.1); + } + } + + #[test] + fn term_from_literal() { + let cases = vec![ + ( + msg::Literal::Simple("foo".to_string()), + Ok(Term::String("foo".to_string())), + ), + ( + msg::Literal::LanguageTaggedString { + value: "foo".to_string(), + language: "en".to_string(), + }, + Ok(Term::String("fooen".to_string())), + ), + ( + msg::Literal::TypedValue { + value: "foo".to_string(), + datatype: msg::IRI::Prefixed("foo:bar".to_string()), + }, + Ok(Term::String("foohttp://example.com/bar".to_string())), + ), + ( + msg::Literal::TypedValue { + value: "foo".to_string(), + datatype: msg::IRI::Prefixed("unknown:bar".to_string()), + }, + Err(StdError::generic_err("Prefix not found: unknown")), + ), + ]; + + let mut prefixes = HashMap::new(); + prefixes.insert("foo".to_string(), "http://example.com/".to_string()); + + for case in cases { + assert_eq!(Term::from_literal(case.0, &prefixes), case.1); + } + } + + #[test] + fn term_as_string() { + let cases = vec![ + (Term::String("foo".to_string()), "foo"), + (Term::Boolean(true), "true"), + (Term::Boolean(false), "false"), + ]; + for case in cases { + assert_eq!(case.0.as_string(), case.1); + } + } + + #[test] + fn term_as_bool() { + let cases = vec![ + (Term::String("foo".to_string()), true), + (Term::String("".to_string()), false), + (Term::Boolean(true), true), + (Term::Boolean(false), false), + ]; + for case in cases { + assert_eq!(case.0.as_bool(), case.1); + } + } + + #[test] + fn term_partial_cmp() { + let cases = vec![ + ( + Term::String("a".to_string()), + Term::String("b".to_string()), + Some(Ordering::Less), + ), + ( + Term::String("b".to_string()), + Term::String("a".to_string()), + Some(Ordering::Greater), + ), + ( + Term::String("a".to_string()), + Term::String("a".to_string()), + Some(Ordering::Equal), + ), + ( + Term::Boolean(true), + Term::Boolean(false), + Some(Ordering::Greater), + ), + ( + Term::Boolean(false), + Term::Boolean(true), + Some(Ordering::Less), + ), + ( + Term::Boolean(true), + Term::Boolean(true), + Some(Ordering::Equal), + ), + (Term::String("a".to_string()), Term::Boolean(true), None), + (Term::Boolean(true), Term::String("a".to_string()), None), + ]; + for case in cases { + assert_eq!(case.0.partial_cmp(&case.1), case.2); + } + } +} diff --git a/contracts/axone-cognitarium/src/querier/plan_builder.rs b/contracts/axone-cognitarium/src/querier/plan_builder.rs index 5d807993..7ae4c1cb 100644 --- a/contracts/axone-cognitarium/src/querier/plan_builder.rs +++ b/contracts/axone-cognitarium/src/querier/plan_builder.rs @@ -157,6 +157,10 @@ impl<'a> PlanBuilder<'a> { Box::new(self.build_expression(left)?), Box::new(self.build_expression(right)?), )), + msg::Expression::Not(child) => self + .build_expression(child) + .map(Box::new) + .map(Expression::Not), } } From 73df19c4acde0584510c83040c75c99d1a82402f Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Thu, 22 Aug 2024 11:55:26 +0200 Subject: [PATCH 10/15] test(cognitarium): put expression under testing --- .../src/querier/expression.rs | 167 ++++++++++++++++++ .../axone-cognitarium/src/querier/variable.rs | 29 +-- contracts/axone-cognitarium/src/state/mod.rs | 5 + .../axone-cognitarium/src/state/test_util.rs | 41 +++++ 4 files changed, 216 insertions(+), 26 deletions(-) create mode 100644 contracts/axone-cognitarium/src/state/test_util.rs diff --git a/contracts/axone-cognitarium/src/querier/expression.rs b/contracts/axone-cognitarium/src/querier/expression.rs index fe671a16..79b0e203 100644 --- a/contracts/axone-cognitarium/src/querier/expression.rs +++ b/contracts/axone-cognitarium/src/querier/expression.rs @@ -155,6 +155,8 @@ impl PartialOrd for Term { #[cfg(test)] mod tests { use super::*; + use crate::querier::variable::ResolvedVariable; + use crate::state::{InMemoryNamespaceSolver, Node, Object}; use std::collections::BTreeSet; #[test] @@ -216,6 +218,171 @@ mod tests { } } + #[test] + fn expression_evaluate() { + let cases = vec![ + ( + Expression::Constant(Term::Boolean(true)), + Ok(Term::Boolean(true)), + ), + ( + Expression::Variable(0), + Ok(Term::String("http:://example.com/foo".to_string())), + ), + ( + Expression::Variable(1), + Err(StdError::not_found("Namespace")), + ), + ( + Expression::Variable(12), + Err(StdError::generic_err("Unbound filter variable")), + ), + ( + Expression::And(vec![ + Expression::Constant(Term::Boolean(true)), + Expression::Constant(Term::Boolean(true)), + ]), + Ok(Term::Boolean(true)), + ), + (Expression::And(vec![]), Ok(Term::Boolean(true))), + ( + Expression::And(vec![ + Expression::Constant(Term::Boolean(true)), + Expression::Constant(Term::Boolean(false)), + ]), + Ok(Term::Boolean(false)), + ), + ( + Expression::Or(vec![ + Expression::Constant(Term::Boolean(true)), + Expression::Constant(Term::Boolean(false)), + ]), + Ok(Term::Boolean(true)), + ), + (Expression::Or(vec![]), Ok(Term::Boolean(false))), + ( + Expression::Or(vec![ + Expression::Constant(Term::Boolean(false)), + Expression::Constant(Term::Boolean(false)), + ]), + Ok(Term::Boolean(false)), + ), + ( + Expression::Equal( + Box::new(Expression::Constant(Term::String("foo".to_string()))), + Box::new(Expression::Constant(Term::String("foo".to_string()))), + ), + Ok(Term::Boolean(true)), + ), + ( + Expression::Equal( + Box::new(Expression::Constant(Term::String("foo".to_string()))), + Box::new(Expression::Constant(Term::String("bar".to_string()))), + ), + Ok(Term::Boolean(false)), + ), + ( + Expression::Greater( + Box::new(Expression::Constant(Term::String("1".to_string()))), + Box::new(Expression::Constant(Term::String("1".to_string()))), + ), + Ok(Term::Boolean(false)), + ), + ( + Expression::Greater( + Box::new(Expression::Constant(Term::String("2".to_string()))), + Box::new(Expression::Constant(Term::String("1".to_string()))), + ), + Ok(Term::Boolean(true)), + ), + ( + Expression::GreaterOrEqual( + Box::new(Expression::Constant(Term::String("1".to_string()))), + Box::new(Expression::Constant(Term::String("2".to_string()))), + ), + Ok(Term::Boolean(false)), + ), + ( + Expression::GreaterOrEqual( + Box::new(Expression::Constant(Term::String("1".to_string()))), + Box::new(Expression::Constant(Term::String("1".to_string()))), + ), + Ok(Term::Boolean(true)), + ), + ( + Expression::GreaterOrEqual( + Box::new(Expression::Constant(Term::String("2".to_string()))), + Box::new(Expression::Constant(Term::String("1".to_string()))), + ), + Ok(Term::Boolean(true)), + ), + ( + Expression::Less( + Box::new(Expression::Constant(Term::String("1".to_string()))), + Box::new(Expression::Constant(Term::String("2".to_string()))), + ), + Ok(Term::Boolean(true)), + ), + ( + Expression::Less( + Box::new(Expression::Constant(Term::String("1".to_string()))), + Box::new(Expression::Constant(Term::String("1".to_string()))), + ), + Ok(Term::Boolean(false)), + ), + ( + Expression::LessOrEqual( + Box::new(Expression::Constant(Term::String("1".to_string()))), + Box::new(Expression::Constant(Term::String("2".to_string()))), + ), + Ok(Term::Boolean(true)), + ), + ( + Expression::LessOrEqual( + Box::new(Expression::Constant(Term::String("1".to_string()))), + Box::new(Expression::Constant(Term::String("1".to_string()))), + ), + Ok(Term::Boolean(true)), + ), + ( + Expression::LessOrEqual( + Box::new(Expression::Constant(Term::String("2".to_string()))), + Box::new(Expression::Constant(Term::String("1".to_string()))), + ), + Ok(Term::Boolean(false)), + ), + ( + Expression::Not(Box::new(Expression::Constant(Term::Boolean(true)))), + Ok(Term::Boolean(false)), + ), + ( + Expression::Not(Box::new(Expression::Constant(Term::Boolean(false)))), + Ok(Term::Boolean(true)), + ), + ]; + + let mut vars = ResolvedVariables::with_capacity(2); + vars.merge_index( + 0, + ResolvedVariable::Object(Object::Named(Node { + namespace: 0, + value: "foo".to_string(), + })), + ); + vars.merge_index( + 1, + ResolvedVariable::Object(Object::Named(Node { + namespace: 12, + value: "foo".to_string(), + })), + ); + + let mut ns_solver = InMemoryNamespaceSolver::with(vec![(0, "http:://example.com/")]); + for case in cases { + assert_eq!(case.0.evaluate(&vars, &mut ns_solver), case.1); + } + } + #[test] fn term_from_iri() { let cases = vec![ diff --git a/contracts/axone-cognitarium/src/querier/variable.rs b/contracts/axone-cognitarium/src/querier/variable.rs index 7d2c193e..83977bf2 100644 --- a/contracts/axone-cognitarium/src/querier/variable.rs +++ b/contracts/axone-cognitarium/src/querier/variable.rs @@ -189,7 +189,7 @@ pub trait HasBoundVariables { #[cfg(test)] mod tests { use super::*; - use crate::state::{Literal, Namespace, Node}; + use crate::state::{InMemoryNamespaceSolver, Literal, Node}; use cosmwasm_std::StdError; #[test] @@ -246,29 +246,6 @@ mod tests { } } - struct TestNamespaceSolver; - impl NamespaceSolver for TestNamespaceSolver { - fn resolve_from_key(&mut self, key: u128) -> StdResult { - match key { - 0 => Ok(Namespace { - key: 0, - value: "foo".to_string(), - counter: 1, - }), - 1 => Ok(Namespace { - key: 1, - value: "bar".to_string(), - counter: 1, - }), - _ => Err(StdError::not_found("Namespace")), - } - } - - fn resolve_from_val(&mut self, _value: String) -> StdResult { - Err(StdError::not_found("Namespace")) - } - } - #[test] fn values() { let cases = vec![ @@ -380,7 +357,7 @@ mod tests { ]; let mut id_issuer = IdentifierIssuer::new("b", 0u128); - let mut ns_solver = TestNamespaceSolver; + let mut ns_solver = InMemoryNamespaceSolver::with(vec![(0, "foo"), (1, "bar")]); for (var, expected) in cases { assert_eq!(var.as_value(&mut ns_solver, &mut id_issuer), expected) } @@ -511,7 +488,7 @@ mod tests { ), ]; - let mut ns_solver = TestNamespaceSolver; + let mut ns_solver = InMemoryNamespaceSolver::with(vec![(0, "foo"), (1, "bar")]); for (var, expected) in cases { assert_eq!(var.as_term(&mut ns_solver), expected) } diff --git a/contracts/axone-cognitarium/src/state/mod.rs b/contracts/axone-cognitarium/src/state/mod.rs index e09022e4..3200463d 100644 --- a/contracts/axone-cognitarium/src/state/mod.rs +++ b/contracts/axone-cognitarium/src/state/mod.rs @@ -7,3 +7,8 @@ pub use blank_nodes::*; pub use namespaces::*; pub use store::*; pub use triples::*; + +#[cfg(test)] +mod test_util; +#[cfg(test)] +pub use test_util::*; diff --git a/contracts/axone-cognitarium/src/state/test_util.rs b/contracts/axone-cognitarium/src/state/test_util.rs new file mode 100644 index 00000000..dbfc5818 --- /dev/null +++ b/contracts/axone-cognitarium/src/state/test_util.rs @@ -0,0 +1,41 @@ +use crate::state::{Namespace, NamespaceSolver}; +use cosmwasm_std::{StdError, StdResult}; +use std::collections::BTreeMap; + +pub struct InMemoryNamespaceSolver { + by_val: BTreeMap, + by_key: BTreeMap, +} + +impl InMemoryNamespaceSolver { + pub fn with(namespaces: Vec<(u128, &str)>) -> Self { + let mut by_val = BTreeMap::new(); + let mut by_key = BTreeMap::new(); + for (key, value) in namespaces { + let ns = Namespace { + value: value.to_string(), + key, + counter: 1, + }; + by_val.insert(value.to_string(), ns.clone()); + by_key.insert(key, ns); + } + Self { by_val, by_key } + } +} + +impl NamespaceSolver for InMemoryNamespaceSolver { + fn resolve_from_key(&mut self, key: u128) -> StdResult { + self.by_key + .get(&key) + .ok_or_else(|| StdError::not_found("Namespace")) + .cloned() + } + + fn resolve_from_val(&mut self, _value: String) -> StdResult { + self.by_val + .get(&_value) + .ok_or_else(|| StdError::not_found("Namespace")) + .cloned() + } +} From 0effa13fb485e88672473c73c70e0426854da9e4 Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Thu, 22 Aug 2024 14:34:16 +0200 Subject: [PATCH 11/15] test(cognitarium): cover plan builder filter cases --- .../src/querier/plan_builder.rs | 465 +++++++++++++----- 1 file changed, 330 insertions(+), 135 deletions(-) diff --git a/contracts/axone-cognitarium/src/querier/plan_builder.rs b/contracts/axone-cognitarium/src/querier/plan_builder.rs index 7ae4c1cb..aef190df 100644 --- a/contracts/axone-cognitarium/src/querier/plan_builder.rs +++ b/contracts/axone-cognitarium/src/querier/plan_builder.rs @@ -510,6 +510,7 @@ mod test { ]; let mut deps = mock_dependencies(); + namespaces() .save( deps.as_mut().storage, @@ -530,22 +531,15 @@ mod test { } #[test] - fn build_plan() { + fn build_bgp() { let cases = vec![ ( - None, - None, vec![], - Ok(QueryPlan { - entrypoint: QueryNode::Noop { - bound_variables: vec![], - }, - variables: vec![], + Ok(QueryNode::Noop { + bound_variables: vec![], }), ), ( - None, - None, vec![TriplePattern { subject: VarOrNode::Node(Node::NamedNode(IRI::Full( "notexisting#outch".to_string(), @@ -553,104 +547,297 @@ mod test { predicate: VarOrNamedNode::Variable("predicate".to_string()), object: VarOrNodeOrLiteral::Variable("object".to_string()), }], - Ok(QueryPlan { - entrypoint: QueryNode::Noop { - bound_variables: vec![0usize, 1usize], - }, - variables: vec![ - PlanVariable::Basic("predicate".to_string()), - PlanVariable::Basic("object".to_string()), - ], + Ok(QueryNode::Noop { + bound_variables: vec![0usize, 1usize], }), ), ( - None, - None, vec![TriplePattern { subject: VarOrNode::Variable("subject".to_string()), predicate: VarOrNamedNode::Variable("predicate".to_string()), object: VarOrNodeOrLiteral::Variable("object".to_string()), }], - Ok(QueryPlan { - entrypoint: QueryNode::TriplePattern { - subject: PatternValue::Variable(0usize), - predicate: PatternValue::Variable(1usize), - object: PatternValue::Variable(2usize), - }, - variables: vec![ - PlanVariable::Basic("subject".to_string()), - PlanVariable::Basic("predicate".to_string()), - PlanVariable::Basic("object".to_string()), - ], + Ok(QueryNode::TriplePattern { + subject: PatternValue::Variable(0usize), + predicate: PatternValue::Variable(1usize), + object: PatternValue::Variable(2usize), }), ), ( - Some(20usize), - None, vec![TriplePattern { subject: VarOrNode::Variable("subject".to_string()), - predicate: VarOrNamedNode::Variable("predicate".to_string()), - object: VarOrNodeOrLiteral::Variable("object".to_string()), + predicate: VarOrNamedNode::Variable("n".to_string()), + object: VarOrNodeOrLiteral::Variable("n".to_string()), }], - Ok(QueryPlan { - entrypoint: QueryNode::Skip { - first: 20usize, - child: Box::new(QueryNode::TriplePattern { + Ok(QueryNode::TriplePattern { + subject: PatternValue::Variable(0usize), + predicate: PatternValue::Variable(1usize), + object: PatternValue::Variable(1usize), + }), + ), + ( + vec![ + TriplePattern { + subject: VarOrNode::Variable("var1".to_string()), + predicate: VarOrNamedNode::Variable("var2".to_string()), + object: VarOrNodeOrLiteral::Variable("var3".to_string()), + }, + TriplePattern { + subject: VarOrNode::Variable("var4".to_string()), + predicate: VarOrNamedNode::Variable("var5".to_string()), + object: VarOrNodeOrLiteral::Variable("var6".to_string()), + }, + TriplePattern { + subject: VarOrNode::Variable("var1".to_string()), + predicate: VarOrNamedNode::Variable("var5".to_string()), + object: VarOrNodeOrLiteral::Node(Node::BlankNode("blank".to_string())), + }, + ], + Ok(QueryNode::ForLoopJoin { + left: Box::new(QueryNode::CartesianProductJoin { + left: Box::new(QueryNode::TriplePattern { subject: PatternValue::Variable(0usize), predicate: PatternValue::Variable(1usize), object: PatternValue::Variable(2usize), }), + right: Box::new(QueryNode::TriplePattern { + subject: PatternValue::Variable(3usize), + predicate: PatternValue::Variable(4usize), + object: PatternValue::Variable(5usize), + }), + }), + right: Box::new(QueryNode::TriplePattern { + subject: PatternValue::Variable(0usize), + predicate: PatternValue::Variable(4usize), + object: PatternValue::BlankVariable(6usize), + }), + }), + ), + ( + vec![ + TriplePattern { + subject: VarOrNode::Node(Node::BlankNode("1".to_string())), + predicate: VarOrNamedNode::Variable("1".to_string()), + object: VarOrNodeOrLiteral::Node(Node::BlankNode("2".to_string())), }, - variables: vec![ - PlanVariable::Basic("subject".to_string()), - PlanVariable::Basic("predicate".to_string()), - PlanVariable::Basic("object".to_string()), - ], + TriplePattern { + subject: VarOrNode::Node(Node::BlankNode("1".to_string())), + predicate: VarOrNamedNode::Variable("1".to_string()), + object: VarOrNodeOrLiteral::Variable("2".to_string()), + }, + ], + Ok(QueryNode::ForLoopJoin { + left: Box::new(QueryNode::TriplePattern { + subject: PatternValue::BlankVariable(0usize), + predicate: PatternValue::Variable(1usize), + object: PatternValue::BlankVariable(2usize), + }), + right: Box::new(QueryNode::TriplePattern { + subject: PatternValue::BlankVariable(0usize), + predicate: PatternValue::Variable(1usize), + object: PatternValue::Variable(3usize), + }), + }), + ), + ]; + + let mut deps = mock_dependencies(); + namespaces() + .save( + deps.as_mut().storage, + "http://axone.space/".to_string(), + &Namespace { + value: "http://axone.space/".to_string(), + key: 0u128, + counter: 1u128, + }, + ) + .unwrap(); + + for case in cases { + let prefixes = &PrefixMap::default().into_inner(); + let mut builder = PlanBuilder::new(&deps.storage, prefixes, None); + + assert_eq!(builder.build_from_bgp(case.0.iter()), case.1) + } + } + + #[test] + fn build_expression() { + let cases = vec![ + ( + msg::Expression::NamedNode(IRI::Full("http://axone.space/test".to_string())), + Ok(Expression::Constant(Term::String( + "http://axone.space/test".to_string(), + ))), + ), + ( + msg::Expression::NamedNode(IRI::Prefixed("oups:test".to_string())), + Err(StdError::generic_err("Prefix not found: oups")), + ), + ( + msg::Expression::Literal(Literal::Simple("simple".to_string())), + Ok(Expression::Constant(Term::String("simple".to_string()))), + ), + ( + msg::Expression::Literal(Literal::TypedValue { + value: "typed".to_string(), + datatype: IRI::Prefixed("oups:type".to_string()), }), + Err(StdError::generic_err("Prefix not found: oups")), + ), + ( + msg::Expression::Variable("variable".to_string()), + Ok(Expression::Variable(0usize)), + ), + ( + msg::Expression::And(vec![msg::Expression::Variable("variable".to_string())]), + Ok(Expression::And(vec![Expression::Variable(0usize)])), ), + ( + msg::Expression::Or(vec![msg::Expression::Variable("variable".to_string())]), + Ok(Expression::Or(vec![Expression::Variable(0usize)])), + ), + ( + msg::Expression::Equal( + Box::new(msg::Expression::Variable("v1".to_string())), + Box::new(msg::Expression::Variable("v2".to_string())), + ), + Ok(Expression::Equal( + Box::new(Expression::Variable(0usize)), + Box::new(Expression::Variable(1usize)), + )), + ), + ( + msg::Expression::Greater( + Box::new(msg::Expression::Variable("v1".to_string())), + Box::new(msg::Expression::Variable("v2".to_string())), + ), + Ok(Expression::Greater( + Box::new(Expression::Variable(0usize)), + Box::new(Expression::Variable(1usize)), + )), + ), + ( + msg::Expression::GreaterOrEqual( + Box::new(msg::Expression::Variable("v1".to_string())), + Box::new(msg::Expression::Variable("v2".to_string())), + ), + Ok(Expression::GreaterOrEqual( + Box::new(Expression::Variable(0usize)), + Box::new(Expression::Variable(1usize)), + )), + ), + ( + msg::Expression::Less( + Box::new(msg::Expression::Variable("v1".to_string())), + Box::new(msg::Expression::Variable("v2".to_string())), + ), + Ok(Expression::Less( + Box::new(Expression::Variable(0usize)), + Box::new(Expression::Variable(1usize)), + )), + ), + ( + msg::Expression::LessOrEqual( + Box::new(msg::Expression::Variable("v1".to_string())), + Box::new(msg::Expression::Variable("v2".to_string())), + ), + Ok(Expression::LessOrEqual( + Box::new(Expression::Variable(0usize)), + Box::new(Expression::Variable(1usize)), + )), + ), + ( + msg::Expression::Not(Box::new(msg::Expression::Variable("v1".to_string()))), + Ok(Expression::Not(Box::new(Expression::Variable(0usize)))), + ), + ]; + + let deps = mock_dependencies(); + + for case in cases { + let prefixes = &PrefixMap::default().into_inner(); + let mut builder = PlanBuilder::new(&deps.storage, prefixes, None); + + assert_eq!(builder.build_expression(&case.0), case.1) + } + } + + #[test] + fn build_plan() { + let cases = vec![ ( None, - Some(20usize), - vec![TriplePattern { - subject: VarOrNode::Variable("subject".to_string()), - predicate: VarOrNamedNode::Variable("predicate".to_string()), - object: VarOrNodeOrLiteral::Variable("object".to_string()), - }], + None, + WhereClause::Bgp { patterns: vec![] }, + Ok(QueryPlan { + entrypoint: QueryNode::Noop { + bound_variables: vec![], + }, + variables: vec![], + }), + ), + ( + Some(10usize), + None, + WhereClause::Bgp { patterns: vec![] }, + Ok(QueryPlan { + entrypoint: QueryNode::Skip { + child: Box::new(QueryNode::Noop { + bound_variables: vec![], + }), + first: 10usize, + }, + variables: vec![], + }), + ), + ( + None, + Some(10usize), + WhereClause::Bgp { patterns: vec![] }, Ok(QueryPlan { entrypoint: QueryNode::Limit { - first: 20usize, - child: Box::new(QueryNode::TriplePattern { - subject: PatternValue::Variable(0usize), - predicate: PatternValue::Variable(1usize), - object: PatternValue::Variable(2usize), + child: Box::new(QueryNode::Noop { + bound_variables: vec![], }), + first: 10usize, }, - variables: vec![ - PlanVariable::Basic("subject".to_string()), - PlanVariable::Basic("predicate".to_string()), - PlanVariable::Basic("object".to_string()), - ], + variables: vec![], }), ), ( + Some(10usize), Some(20usize), - Some(50usize), - vec![TriplePattern { - subject: VarOrNode::Variable("subject".to_string()), - predicate: VarOrNamedNode::Variable("predicate".to_string()), - object: VarOrNodeOrLiteral::Variable("object".to_string()), - }], + WhereClause::Bgp { patterns: vec![] }, Ok(QueryPlan { entrypoint: QueryNode::Limit { - first: 50usize, child: Box::new(QueryNode::Skip { - first: 20usize, - child: Box::new(QueryNode::TriplePattern { - subject: PatternValue::Variable(0usize), - predicate: PatternValue::Variable(1usize), - object: PatternValue::Variable(2usize), + child: Box::new(QueryNode::Noop { + bound_variables: vec![], }), + first: 10usize, }), + first: 20usize, + }, + variables: vec![], + }), + ), + ( + None, + None, + WhereClause::Bgp { + patterns: vec![TriplePattern { + subject: VarOrNode::Variable("subject".to_string()), + predicate: VarOrNamedNode::Variable("predicate".to_string()), + object: VarOrNodeOrLiteral::Variable("object".to_string()), + }], + }, + Ok(QueryPlan { + entrypoint: QueryNode::TriplePattern { + subject: PatternValue::Variable(0usize), + predicate: PatternValue::Variable(1usize), + object: PatternValue::Variable(2usize), }, variables: vec![ PlanVariable::Basic("subject".to_string()), @@ -662,69 +849,38 @@ mod test { ( None, None, - vec![ - TriplePattern { - subject: VarOrNode::Variable("var1".to_string()), - predicate: VarOrNamedNode::Variable("var2".to_string()), - object: VarOrNodeOrLiteral::Variable("var3".to_string()), - }, - TriplePattern { - subject: VarOrNode::Variable("var4".to_string()), - predicate: VarOrNamedNode::Variable("var5".to_string()), - object: VarOrNodeOrLiteral::Variable("var6".to_string()), - }, - TriplePattern { - subject: VarOrNode::Variable("var1".to_string()), - predicate: VarOrNamedNode::Variable("var5".to_string()), - object: VarOrNodeOrLiteral::Node(Node::BlankNode("blank".to_string())), - }, - ], + WhereClause::Bgp { + patterns: vec![TriplePattern { + subject: VarOrNode::Variable("subject".to_string()), + predicate: VarOrNamedNode::Variable("n".to_string()), + object: VarOrNodeOrLiteral::Variable("n".to_string()), + }], + }, Ok(QueryPlan { - entrypoint: QueryNode::ForLoopJoin { - left: Box::new(QueryNode::CartesianProductJoin { - left: Box::new(QueryNode::TriplePattern { - subject: PatternValue::Variable(0usize), - predicate: PatternValue::Variable(1usize), - object: PatternValue::Variable(2usize), - }), - right: Box::new(QueryNode::TriplePattern { - subject: PatternValue::Variable(3usize), - predicate: PatternValue::Variable(4usize), - object: PatternValue::Variable(5usize), - }), - }), - right: Box::new(QueryNode::TriplePattern { - subject: PatternValue::Variable(0usize), - predicate: PatternValue::Variable(4usize), - object: PatternValue::BlankVariable(6usize), - }), + entrypoint: QueryNode::TriplePattern { + subject: PatternValue::Variable(0usize), + predicate: PatternValue::Variable(1usize), + object: PatternValue::Variable(1usize), }, variables: vec![ - PlanVariable::Basic("var1".to_string()), - PlanVariable::Basic("var2".to_string()), - PlanVariable::Basic("var3".to_string()), - PlanVariable::Basic("var4".to_string()), - PlanVariable::Basic("var5".to_string()), - PlanVariable::Basic("var6".to_string()), - PlanVariable::BlankNode("blank".to_string()), + PlanVariable::Basic("subject".to_string()), + PlanVariable::Basic("n".to_string()), ], }), ), ( None, None, - vec![ - TriplePattern { - subject: VarOrNode::Node(Node::BlankNode("1".to_string())), - predicate: VarOrNamedNode::Variable("1".to_string()), - object: VarOrNodeOrLiteral::Node(Node::BlankNode("2".to_string())), - }, - TriplePattern { - subject: VarOrNode::Node(Node::BlankNode("1".to_string())), - predicate: VarOrNamedNode::Variable("1".to_string()), - object: VarOrNodeOrLiteral::Variable("2".to_string()), - }, - ], + WhereClause::LateralJoin { + left: Box::new(WhereClause::Bgp { + patterns: vec![TriplePattern { + subject: VarOrNode::Node(Node::BlankNode("1".to_string())), + predicate: VarOrNamedNode::Variable("n".to_string()), + object: VarOrNodeOrLiteral::Node(Node::BlankNode("2".to_string())), + }], + }), + right: Box::new(WhereClause::Bgp { patterns: vec![] }), + }, Ok(QueryPlan { entrypoint: QueryNode::ForLoopJoin { left: Box::new(QueryNode::TriplePattern { @@ -732,20 +888,62 @@ mod test { predicate: PatternValue::Variable(1usize), object: PatternValue::BlankVariable(2usize), }), - right: Box::new(QueryNode::TriplePattern { - subject: PatternValue::BlankVariable(0usize), - predicate: PatternValue::Variable(1usize), - object: PatternValue::Variable(3usize), + right: Box::new(QueryNode::Noop { + bound_variables: vec![], }), }, variables: vec![ PlanVariable::BlankNode("1".to_string()), - PlanVariable::Basic("1".to_string()), + PlanVariable::Basic("n".to_string()), PlanVariable::BlankNode("2".to_string()), + ], + }), + ), + ( + None, + None, + WhereClause::Filter { + inner: Box::new(WhereClause::Bgp { + patterns: vec![TriplePattern { + subject: VarOrNode::Variable("1".to_string()), + predicate: VarOrNamedNode::Variable("2".to_string()), + object: VarOrNodeOrLiteral::Variable("2".to_string()), + }], + }), + expr: msg::Expression::Variable("1".to_string()), + }, + Ok(QueryPlan { + entrypoint: QueryNode::Filter { + inner: Box::new(QueryNode::TriplePattern { + subject: PatternValue::Variable(0usize), + predicate: PatternValue::Variable(1usize), + object: PatternValue::Variable(1usize), + }), + expr: Expression::Variable(0usize), + }, + variables: vec![ + PlanVariable::Basic("1".to_string()), PlanVariable::Basic("2".to_string()), ], }), ), + ( + None, + None, + WhereClause::Filter { + inner: Box::new(WhereClause::Bgp { + patterns: vec![TriplePattern { + subject: VarOrNode::Variable("1".to_string()), + predicate: VarOrNamedNode::Variable("2".to_string()), + object: VarOrNodeOrLiteral::Variable("2".to_string()), + }], + }), + expr: msg::Expression::Variable("oups".to_string()), + }, + Err(StdError::generic_err( + "Unbound variable in filter expression", + )), + ), ]; let mut deps = mock_dependencies(); @@ -771,10 +969,7 @@ mod test { builder = builder.with_limit(limit); } - assert_eq!( - builder.build_plan(&WhereClause::Bgp { patterns: case.2 }), - case.3 - ) + assert_eq!(builder.build_plan(&case.2), case.3) } } } From 0ccc86deb437c68f37bfd62c1dcbf44445f3d967 Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Thu, 22 Aug 2024 15:15:40 +0200 Subject: [PATCH 12/15] test(cognitarium): cover filter engine iter --- .../axone-cognitarium/src/querier/engine.rs | 103 ++++++++++++++++-- 1 file changed, 92 insertions(+), 11 deletions(-) diff --git a/contracts/axone-cognitarium/src/querier/engine.rs b/contracts/axone-cognitarium/src/querier/engine.rs index f84abbf4..f80f576a 100644 --- a/contracts/axone-cognitarium/src/querier/engine.rs +++ b/contracts/axone-cognitarium/src/querier/engine.rs @@ -210,18 +210,18 @@ impl<'a> Iterator for FilterIterator<'a> { type Item = StdResult; fn next(&mut self) -> Option { - match self.upstream.next()? { - Ok(vars) => match self.expr.evaluate(&vars, &mut self.ns_resolver) { - Ok(t) => { - if t.as_bool() { - Some(Ok(vars)) - } else { - None + loop { + match self.upstream.next()? { + Ok(vars) => match self.expr.evaluate(&vars, &mut self.ns_resolver) { + Ok(t) => { + if t.as_bool() { + return Some(Ok(vars)); + } } - } - Err(e) => Some(Err(e)), - }, - Err(e) => Some(Err(e)), + Err(e) => return Some(Err(e)), + }, + Err(e) => return Some(Err(e)), + } } } } @@ -1001,6 +1001,7 @@ impl AtomTemplate { mod test { use super::*; use crate::msg::StoreLimitsInput; + use crate::querier::expression::Term; use crate::querier::plan::PlanVariable; use crate::state; use crate::state::Object::{Literal, Named}; @@ -1382,6 +1383,86 @@ mod test { } } + #[test] + fn filter_iter() { + let cases = vec![ + ( + Expression::Equal( + Box::new(Expression::Variable(0usize)), + Box::new(Expression::Constant(Term::String("1".to_string()))), + ), + Ok(1usize), + ), + ( + Expression::Not(Box::new(Expression::Equal( + Box::new(Expression::Variable(0usize)), + Box::new(Expression::Constant(Term::String("1".to_string()))), + ))), + Ok(3usize), + ), + ( + Expression::Greater( + Box::new(Expression::Variable(0usize)), + Box::new(Expression::Constant(Term::String("1".to_string()))), + ), + Ok(2usize), + ), + ( + Expression::Equal( + Box::new(Expression::Variable(1usize)), + Box::new(Expression::Constant(Term::String("1".to_string()))), + ), + Err(StdError::generic_err("Unbound filter variable")), + ), + ( + Expression::Equal( + Box::new(Expression::Variable(3usize)), + Box::new(Expression::Constant(Term::String("1".to_string()))), + ), + Err(StdError::generic_err("Unbound filter variable")), + ), + ( + Expression::Equal( + Box::new(Expression::Variable(2usize)), + Box::new(Expression::Constant(Term::String("1".to_string()))), + ), + Err(StdError::not_found("Namespace")), + ), + ]; + + let mut upstream = Vec::with_capacity(4); + for i in 0..4 { + let mut vars = ResolvedVariables::with_capacity(3); + vars.merge_index( + 0, + ResolvedVariable::Object(Object::Literal(state::Literal::Simple { + value: format!("{i}"), + })), + ); + vars.merge_index( + 2, + ResolvedVariable::Predicate(Node { + namespace: 0, + value: "foo".to_string(), + }), + ); + upstream.push(vars); + } + + let deps = mock_dependencies(); + for (expr, expects) in cases { + let result = FilterIterator::new( + &deps.storage, + Box::new(upstream.iter().map(|v| Ok(v.clone()))), + expr, + vec![], + ) + .collect::>>(); + + assert_eq!(result.map(|s| s.len()), expects); + } + } + #[test] fn for_loop_join_iter() { struct TestCase { From ec13afab736d2d074de2ff3b6ed227fe6867b642 Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Thu, 22 Aug 2024 15:26:14 +0200 Subject: [PATCH 13/15] feat(dataverse): align with new cognitarium api --- contracts/axone-dataverse/src/contract.rs | 13 ++++++------- contracts/axone-dataverse/src/registrar/registry.rs | 12 ++++++------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/contracts/axone-dataverse/src/contract.rs b/contracts/axone-dataverse/src/contract.rs index 278b507b..1b0b067f 100644 --- a/contracts/axone-dataverse/src/contract.rs +++ b/contracts/axone-dataverse/src/contract.rs @@ -142,9 +142,8 @@ mod tests { }; use crate::testutil::testutil::read_test_data; use axone_cognitarium::msg::{ - DataFormat, Head, Node, Results, SelectItem, SelectQuery, SelectResponse, - SimpleWhereCondition, TriplePattern, Value, VarOrNamedNode, VarOrNode, VarOrNodeOrLiteral, - WhereCondition, IRI, + DataFormat, Head, Node, Results, SelectItem, SelectQuery, SelectResponse, TriplePattern, + Value, VarOrNamedNode, VarOrNode, VarOrNodeOrLiteral, WhereClause, IRI, }; use cosmwasm_std::testing::{message_info, mock_dependencies, mock_env}; use cosmwasm_std::{ @@ -304,15 +303,15 @@ mod tests { prefixes: vec![], limit: Some(1u32), select: vec![SelectItem::Variable("p".to_string())], - r#where: vec![WhereCondition::Simple( - SimpleWhereCondition::TriplePattern(TriplePattern { + r#where: WhereClause::Bgp { + patterns: vec![TriplePattern { subject: VarOrNode::Node(Node::NamedNode(IRI::Full( "http://example.edu/credentials/3732".to_string(), ))), predicate: VarOrNamedNode::Variable("p".to_string()), object: VarOrNodeOrLiteral::Variable("o".to_string()), - }) - )], + }] + }, } }) ); diff --git a/contracts/axone-dataverse/src/registrar/registry.rs b/contracts/axone-dataverse/src/registrar/registry.rs index ad500bd4..0057000f 100644 --- a/contracts/axone-dataverse/src/registrar/registry.rs +++ b/contracts/axone-dataverse/src/registrar/registry.rs @@ -2,8 +2,8 @@ use crate::registrar::credential::DataverseCredential; use crate::state::DATAVERSE; use crate::ContractError; use axone_cognitarium::msg::{ - DataFormat, Node, SelectItem, SelectQuery, SimpleWhereCondition, TriplePattern, VarOrNamedNode, - VarOrNode, VarOrNodeOrLiteral, WhereCondition, IRI, + DataFormat, Node, SelectItem, SelectQuery, TriplePattern, VarOrNamedNode, VarOrNode, + VarOrNodeOrLiteral, WhereClause, IRI, }; use axone_cognitarium_client::CognitariumClient; use cosmwasm_std::{DepsMut, StdResult, Storage, WasmMsg}; @@ -36,15 +36,15 @@ impl ClaimRegistrar { prefixes: vec![], limit: Some(1u32), select: vec![SelectItem::Variable("p".to_string())], - r#where: vec![WhereCondition::Simple(SimpleWhereCondition::TriplePattern( - TriplePattern { + r#where: WhereClause::Bgp { + patterns: vec![TriplePattern { subject: VarOrNode::Node(Node::NamedNode(IRI::Full( credential.id.to_string(), ))), predicate: VarOrNamedNode::Variable("p".to_string()), object: VarOrNodeOrLiteral::Variable("o".to_string()), - }, - ))], + }], + }, }, )?; From c906cdccdaada1a3a327a4176a7459dbd34f9909 Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Thu, 22 Aug 2024 15:31:27 +0200 Subject: [PATCH 14/15] style: little coup de polish --- contracts/axone-cognitarium/src/contract.rs | 2 +- contracts/axone-cognitarium/src/querier/engine.rs | 6 +++--- .../axone-cognitarium/src/querier/expression.rs | 12 ++++++------ contracts/axone-cognitarium/src/querier/plan.rs | 2 +- .../axone-cognitarium/src/querier/plan_builder.rs | 2 +- contracts/axone-cognitarium/src/querier/variable.rs | 4 ++-- contracts/axone-cognitarium/src/state/namespaces.rs | 2 +- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/contracts/axone-cognitarium/src/contract.rs b/contracts/axone-cognitarium/src/contract.rs index 4b1c2902..6f36ecbf 100644 --- a/contracts/axone-cognitarium/src/contract.rs +++ b/contracts/axone-cognitarium/src/contract.rs @@ -244,7 +244,7 @@ pub mod query { ( vec![select.clone()], WhereClause::Bgp { - patterns: vec![select.clone()], + patterns: vec![select], }, ) } diff --git a/contracts/axone-cognitarium/src/querier/engine.rs b/contracts/axone-cognitarium/src/querier/engine.rs index f80f576a..37ccb3ca 100644 --- a/contracts/axone-cognitarium/src/querier/engine.rs +++ b/contracts/axone-cognitarium/src/querier/engine.rs @@ -71,7 +71,7 @@ impl<'a> QueryEngine<'a> { Ok(ResolvedAtomIterator::new( self.storage, - self.ns_cache.clone().into(), + self.ns_cache.clone(), IdentifierIssuer::new("b", 0u128), self.eval_plan(plan), templates, @@ -201,7 +201,7 @@ impl<'a> FilterIterator<'a> { Self { upstream, expr, - ns_resolver: NamespaceResolver::new(storage, ns_cache.into()), + ns_resolver: NamespaceResolver::new(storage, ns_cache), } } } @@ -793,7 +793,7 @@ impl<'a> ResolvedAtomIterator<'a> { templates: Vec, ) -> Self { Self { - ns_resolver: NamespaceResolver::new(storage, ns_cache.into()), + ns_resolver: NamespaceResolver::new(storage, ns_cache), id_issuer, upstream_iter, templates, diff --git a/contracts/axone-cognitarium/src/querier/expression.rs b/contracts/axone-cognitarium/src/querier/expression.rs index 79b0e203..583ad5d6 100644 --- a/contracts/axone-cognitarium/src/querier/expression.rs +++ b/contracts/axone-cognitarium/src/querier/expression.rs @@ -22,9 +22,9 @@ pub enum Expression { } impl Expression { - pub fn evaluate<'a>( + pub fn evaluate( &self, - vars: &'a ResolvedVariables, + vars: &ResolvedVariables, ns_solver: &mut dyn NamespaceSolver, ) -> StdResult { match self { @@ -40,7 +40,7 @@ impl Expression { return Ok(Term::Boolean(false)); } } - return Ok(Term::Boolean(true)); + Ok(Term::Boolean(true)) } Expression::Or(exprs) => { for expr in exprs { @@ -48,7 +48,7 @@ impl Expression { return Ok(Term::Boolean(true)); } } - return Ok(Term::Boolean(false)); + Ok(Term::Boolean(false)) } Expression::Equal(left, right) => Ok(Term::Boolean( left.evaluate(vars, ns_solver)? == right.evaluate(vars, ns_solver)?, @@ -115,10 +115,10 @@ impl Term { Ok(Term::String(match literal { msg::Literal::Simple(value) => value, msg::Literal::LanguageTaggedString { value, language } => { - format!("{}{}", value, language).to_string() + format!("{}{}", value, language) } msg::Literal::TypedValue { value, datatype } => { - format!("{}{}", value, iri_as_string(datatype, prefixes)?).to_string() + format!("{}{}", value, iri_as_string(datatype, prefixes)?) } })) } diff --git a/contracts/axone-cognitarium/src/querier/plan.rs b/contracts/axone-cognitarium/src/querier/plan.rs index 97e66778..af4eb681 100644 --- a/contracts/axone-cognitarium/src/querier/plan.rs +++ b/contracts/axone-cognitarium/src/querier/plan.rs @@ -1,7 +1,6 @@ use crate::querier::expression::Expression; use crate::querier::variable::HasBoundVariables; use crate::state::{Object, Predicate, Subject}; -use std::collections::BTreeSet; /// Represents a querying plan. #[derive(Eq, PartialEq, Debug, Clone)] @@ -143,6 +142,7 @@ impl PatternValue { #[cfg(test)] mod tests { use super::*; + use std::collections::BTreeSet; #[test] fn bound_variables() { diff --git a/contracts/axone-cognitarium/src/querier/plan_builder.rs b/contracts/axone-cognitarium/src/querier/plan_builder.rs index aef190df..2596dcaa 100644 --- a/contracts/axone-cognitarium/src/querier/plan_builder.rs +++ b/contracts/axone-cognitarium/src/querier/plan_builder.rs @@ -25,7 +25,7 @@ impl<'a> PlanBuilder<'a> { ns_cache: Option>, ) -> Self { Self { - ns_resolver: NamespaceResolver::new(storage, ns_cache.unwrap_or(vec![]).into()), + ns_resolver: NamespaceResolver::new(storage, ns_cache.unwrap_or_default()), prefixes, variables: Vec::new(), skip: None, diff --git a/contracts/axone-cognitarium/src/querier/variable.rs b/contracts/axone-cognitarium/src/querier/variable.rs index 83977bf2..62da1e19 100644 --- a/contracts/axone-cognitarium/src/querier/variable.rs +++ b/contracts/axone-cognitarium/src/querier/variable.rs @@ -113,10 +113,10 @@ impl ResolvedVariable { Object::Literal(literal) => Term::String(match literal { Literal::Simple { value } => value.clone(), Literal::I18NString { value, language } => { - format!("{}{}", value, language).to_string() + format!("{}{}", value, language) } Literal::Typed { value, datatype } => { - format!("{}{}", value, datatype.as_iri(ns_solver)?).to_string() + format!("{}{}", value, datatype.as_iri(ns_solver)?) } }), }, diff --git a/contracts/axone-cognitarium/src/state/namespaces.rs b/contracts/axone-cognitarium/src/state/namespaces.rs index 5568cdd3..3d92bd8b 100644 --- a/contracts/axone-cognitarium/src/state/namespaces.rs +++ b/contracts/axone-cognitarium/src/state/namespaces.rs @@ -218,7 +218,7 @@ impl<'a> HasCachedNamespaces for NamespaceResolver<'a> { } fn clear_cache(&mut self) { - self.ns_querier.clear_cache() + self.ns_querier.clear_cache(); } } From dcc7155cd3ba880b31b9e4c2637a53f28dbb1803 Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Thu, 22 Aug 2024 15:33:10 +0200 Subject: [PATCH 15/15] docs(cognitarium): regenerate docs --- docs/axone-cognitarium.md | 111 +++++++++++++++++++++++++++----------- 1 file changed, 80 insertions(+), 31 deletions(-) diff --git a/docs/axone-cognitarium.md b/docs/axone-cognitarium.md index 491e15de..10f62813 100644 --- a/docs/axone-cognitarium.md +++ b/docs/axone-cognitarium.md @@ -415,12 +415,12 @@ Example: `json { "prefixes": [ { "prefix": "foaf", "namespace": "http://xmlns.co Only the smart contract owner (i.e. the address who instantiated it) is authorized to perform this action. -| parameter | description | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `delete_data` | _(Required.) _ **object**. | -| `delete_data.delete` | _(Required.) _ **Array<[TripleDeleteTemplate](#tripledeletetemplate)>**. Specifies the specific triple templates to delete. If nothing is provided, the patterns from the `where` clause are used for deletion. | -| `delete_data.prefixes` | _(Required.) _ **Array<[Prefix](#prefix)>**. The prefixes used in the operation. | -| `delete_data.where` | _(Required.) _ **Array<[WhereCondition](#wherecondition)>**. Defines the patterns that data (RDF triples) should match in order for it to be considered for deletion. | +| parameter | description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `delete_data` | _(Required.) _ **object**. | +| `delete_data.delete` | _(Required.) _ **Array<[TripleDeleteTemplate](#tripledeletetemplate)>**. Specifies the specific triple templates to delete. If nothing is provided and the `where` clause is a single Bgp, the patterns are used for deletion. | +| `delete_data.prefixes` | _(Required.) _ **Array<[Prefix](#prefix)>**. The prefixes used in the operation. | +| `delete_data.where` | **[WhereClause](#whereclause)\|null**. Defines the patterns that data (RDF triples) should match in order for it to be considered for deletion, if any. | ## QueryMsg @@ -516,6 +516,15 @@ Contains information related to triple store. ## Definitions +### Bgp + +Represents a basic graph pattern expressed as a set of triple patterns. + +| property | description | +| -------------- | ---------------------------------------------------------------- | +| `bgp` | _(Required.) _ **object**. | +| `bgp.patterns` | _(Required.) _ **Array<[TriplePattern](#triplepattern)>**. | + ### Binary A string containing Base64-encoded data. @@ -536,11 +545,11 @@ An RDF [blank node](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node). Represents a CONSTRUCT query over the triple store, allowing to retrieve a set of triples serialized in a specific format. -| property | description | -| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `construct` | _(Required.) _ **Array<[TripleConstructTemplate](#tripleconstructtemplate)>**. The triples to construct. If nothing is provided, the patterns from the `where` clause are used for construction. | -| `prefixes` | _(Required.) _ **Array<[Prefix](#prefix)>**. The prefixes used in the query. | -| `where` | _(Required.) _ **Array<[WhereCondition](#wherecondition)>**. The WHERE clause. This clause is used to specify the triples to construct using variable bindings. | +| property | description | +| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `construct` | _(Required.) _ **Array<[TripleConstructTemplate](#tripleconstructtemplate)>**. The triples to construct. If nothing is provided and the `where` clause is a single Bgp, the patterns are used for construction. | +| `prefixes` | _(Required.) _ **Array<[Prefix](#prefix)>**. The prefixes used in the query. | +| `where` | _(Required.) _ **[WhereClause](#whereclause)**. The WHERE clause. This clause is used to specify the triples to construct using variable bindings. | ### DataFormat @@ -557,11 +566,39 @@ Represents the format in which the data are serialized, for example when returne Represents a DESCRIBE query over the triple store, allowing to retrieve a description of a resource as a set of triples serialized in a specific format. -| property | description | -| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `prefixes` | _(Required.) _ **Array<[Prefix](#prefix)>**. The prefixes used in the query. | -| `resource` | _(Required.) _ **[VarOrNamedNode](#varornamednode)**. The resource to describe given as a variable or a node. | -| `where` | _(Required.) _ **Array<[WhereCondition](#wherecondition)>**. The WHERE clause. This clause is used to specify the resource identifier to describe using variable bindings. | +| property | description | +| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| `prefixes` | _(Required.) _ **Array<[Prefix](#prefix)>**. The prefixes used in the query. | +| `resource` | _(Required.) _ **[VarOrNamedNode](#varornamednode)**. The resource to describe given as a variable or a node. | +| `where` | **[WhereClause](#whereclause)\|null**. The WHERE clause. This clause is used to specify the resource identifier to describe using variable bindings. | + +### Expression + +Represents a logical combination of operations whose evaluation results in a term. + +| variant | description | +| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| undefined | **object**. A named node constant. | +| undefined | **object**. A literal constant. | +| undefined | **object**. A variable that must be bound for evaluation. | +| undefined | **object**. Logical conjunction of expressions. All expressions must evaluate to true for the conjunction to be true. If the conjunction is empty, it is considered true. | +| undefined | **object**. Logical disjunction of expressions. At least one expression must evaluate to true for the disjunction to be true. If the disjunction is empty, it is considered false. | +| undefined | **object**. Equality comparison. | +| undefined | **object**. Greater than comparison. | +| undefined | **object**. Greater or equal comparison. | +| undefined | **object**. Less than comparison. | +| undefined | **object**. Less or equal comparison. | +| undefined | **object**. Negation of an expression. | + +### Filter + +Filters the inner clause matching the expression. The solutions coming from the inner clause that do not match the expression are discarded. The variables provided in the inner clause are available in the filter expression. + +| property | description | +| -------------- | ---------------------------------------------------------------------------------------------------------- | +| `filter` | _(Required.) _ **object**. | +| `filter.expr` | _(Required.) _ **object\|object\|object\|object\|object\|object\|object\|object\|object\|object\|object**. | +| `filter.inner` | _(Required.) _ **[Bgp](#bgp)\|[LateralJoin](#lateraljoin)\|[Filter](#filter)**. | ### Full @@ -598,6 +635,16 @@ A [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-ta | `language_tagged_string.language` | _(Required.) _ **string**. The [language tag](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tag). | | `language_tagged_string.value` | _(Required.) _ **string**. The [lexical form](https://www.w3.org/TR/rdf11-concepts/#dfn-lexical-form). | +### LateralJoin + +Evaluates right for all result row of left + +| property | description | +| -------------------- | ------------------------------------------------------------------------------- | +| `lateral_join` | _(Required.) _ **object**. | +| `lateral_join.left` | _(Required.) _ **[Bgp](#bgp)\|[LateralJoin](#lateraljoin)\|[Filter](#filter)**. | +| `lateral_join.right` | _(Required.) _ **[Bgp](#bgp)\|[LateralJoin](#lateraljoin)\|[Filter](#filter)**. | + ### Literal An RDF [literal](https://www.w3.org/TR/rdf11-concepts/#dfn-literal). @@ -691,7 +738,7 @@ Represents a SELECT query over the triple store, allowing to select variables to | `limit` | **integer\|null**. The maximum number of results to return. If `None`, there is no limit. Note: the value of the limit cannot exceed the maximum query limit defined in the store limitations. | | `prefixes` | _(Required.) _ **Array<[Prefix](#prefix)>**. The prefixes used in the query. | | `select` | _(Required.) _ **Array<[SelectItem](#selectitem)>**. The items to select. Note: the number of items to select cannot exceed the maximum query variable count defined in the store limitations. | -| `where` | _(Required.) _ **Array<[WhereCondition](#wherecondition)>**. The WHERE clause. If `None`, there is no WHERE clause, i.e. all triples are returned without filtering. | +| `where` | _(Required.) _ **[WhereClause](#whereclause)**. The WHERE clause. If `None`, there is no WHERE clause, i.e. all triples are returned without filtering. | ### Simple @@ -701,14 +748,6 @@ A [simple literal](https://www.w3.org/TR/rdf11-concepts/#dfn-simple-literal) wit | -------- | -------------------------- | | `simple` | _(Required.) _ **string**. | -### SimpleWhereCondition - -Represents a simple condition in a [WhereCondition]. - -| variant | description | -| ------------------------------- | ----------------------------------------------------------------------------------------------------------------- | -| [TriplePattern](#triplepattern) | **object**. Represents a triple pattern, i.e. a condition on a triple based on its subject, predicate and object. | - ### StoreLimits Contains limitations regarding store usages. @@ -866,14 +905,24 @@ A variable. | ---------- | -------------------------- | | `variable` | _(Required.) _ **string**. | -### WhereCondition +### WhereClause + +Represents a WHERE clause, i.e. a set of conditions to filter the results. -Represents a condition in a [WhereClause]. +| variant | description | +| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Bgp](#bgp) | **object**. Represents a basic graph pattern expressed as a set of triple patterns. | +| [LateralJoin](#lateraljoin) | **object**. Evaluates right for all result row of left | +| [Filter](#filter) | **object**. Filters the inner clause matching the expression. The solutions coming from the inner clause that do not match the expression are discarded. The variables provided in the inner clause are available in the filter expression. | -| variant | description | -| ----------------- | ------------------------------------------ | -| [Simple](#simple) | **object**. Represents a simple condition. | +### undefined + +A named node constant. + +| property | description | +| ------------ | -------------------------------------------------------- | +| `named_node` | _(Required.) _ **[Prefixed](#prefixed)\|[Full](#full)**. | --- -_Rendered by [Fadroma](https://fadroma.tech) ([@fadroma/schema 1.1.0](https://www.npmjs.com/package/@fadroma/schema)) from `axone-cognitarium.json` (`8f8a0452855d9314`)_ +_Rendered by [Fadroma](https://fadroma.tech) ([@fadroma/schema 1.1.0](https://www.npmjs.com/package/@fadroma/schema)) from `axone-cognitarium.json` (`a6344c92b24801fb`)_