diff --git a/contracts/okp4-cognitarium/src/contract.rs b/contracts/okp4-cognitarium/src/contract.rs index c0df6740..c422cf3a 100644 --- a/contracts/okp4-cognitarium/src/contract.rs +++ b/contracts/okp4-cognitarium/src/contract.rs @@ -7,7 +7,7 @@ use cw2::set_contract_version; use crate::error::ContractError; use crate::msg::{DataFormat, ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state::{Store, NAMESPACE_KEY_INCREMENT, STORE}; +use crate::state::{Store, BLANK_NODE_IDENTIFIER_COUNTER, NAMESPACE_KEY_INCREMENT, STORE}; // version info for migration info const CONTRACT_NAME: &str = concat!("crates.io:", env!("CARGO_PKG_NAME")); @@ -24,6 +24,7 @@ pub fn instantiate( STORE.save(deps.storage, &Store::new(info.sender, msg.limits.into()))?; NAMESPACE_KEY_INCREMENT.save(deps.storage, &0u128)?; + BLANK_NODE_IDENTIFIER_COUNTER.save(deps.storage, &0u128)?; Ok(Response::default()) } @@ -49,11 +50,14 @@ pub fn execute( pub mod execute { use super::*; - use crate::msg::{DataFormat, Prefix, TriplePattern, WhereClause}; - use crate::querier::{PlanBuilder, QueryEngine}; + use crate::msg::{ + DataFormat, Prefix, SimpleWhereCondition, TripleDeleteTemplate, WhereClause, WhereCondition, + }; + use crate::querier::{PlanBuilder, QueryEngine, ResolvedVariables}; use crate::rdf::PrefixMap; - use crate::state::HasCachedNamespaces; + use crate::state::{HasCachedNamespaces, Triple}; use crate::storer::StoreEngine; + use either::{Left, Right}; use okp4_rdf::serde::TripleReader; use std::io::BufReader; @@ -87,30 +91,58 @@ pub mod execute { deps: DepsMut<'_>, info: MessageInfo, prefixes: Vec, - delete: Vec, + delete: Vec, r#where: WhereClause, ) -> Result { verify_owner(&deps, &info)?; - let patterns: Vec = if delete.is_empty() { - util::as_triple_patterns(&r#where)? + let delete = if delete.is_empty() { + Left( + r#where + .iter() + .map(|c| match c { + WhereCondition::Simple(SimpleWhereCondition::TriplePattern(t)) => { + (t.subject.clone(), t.predicate.clone(), t.object.clone()) + } + }) + .collect(), + ) } else { - delete + Right( + delete + .into_iter() + .map(|t| (t.subject, t.predicate, t.object)) + .collect(), + ) }; 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 triples = QueryEngine::new(deps.storage) - .select(plan, util::as_select_variables(&patterns))? - .solutions - .resolve_triples( - deps.storage, - &prefix_map, - patterns, - plan_builder.cached_namespaces(), - )?; + let query_engine = QueryEngine::new(deps.storage); + let delete_templates = query_engine.make_triple_templates( + &plan, + &prefix_map, + delete, + plan_builder.cached_namespaces(), + )?; + + let triples = if r#where.is_empty() { + let empty_vars = ResolvedVariables::with_capacity(0); + delete_templates + .into_iter() + .filter_map(|tpl| match tpl.resolve(&empty_vars) { + Ok(Some(v)) => Some(Ok(v)), + Ok(None) => None, + Err(e) => Some(Err(e)), + }) + .collect::>>()? + } else { + query_engine + .construct_triples(plan, delete_templates) + .collect::>>()? + }; let mut store = StoreEngine::new(deps.storage)?; let count = store.delete_all(&triples)?; @@ -141,13 +173,13 @@ pub mod query { use super::*; use crate::msg::{ ConstructQuery, ConstructResponse, DescribeQuery, DescribeResponse, Node, SelectQuery, - SelectResponse, SimpleWhereCondition, StoreResponse, TriplePattern, VarOrNamedNode, - VarOrNode, VarOrNodeOrLiteral, WhereCondition, + SelectResponse, SimpleWhereCondition, StoreResponse, TripleConstructTemplate, + TriplePattern, VarOrNamedNode, VarOrNode, VarOrNodeOrLiteral, WhereCondition, }; use crate::querier::{PlanBuilder, QueryEngine}; use crate::rdf::PrefixMap; use crate::state::HasCachedNamespaces; - use okp4_rdf::serde::TripleWriter; + use okp4_rdf::normalize::IdentifierIssuer; pub fn store(deps: Deps<'_>) -> StdResult { STORE.load(deps.storage).map(Into::into) @@ -184,13 +216,11 @@ pub mod query { ) -> StdResult { let (p, o) = ("_2p".to_owned(), "_3o".to_owned()); - let store = STORE.load(deps.storage)?; - - let (select, r#where) = match &query.resource { + let (construct, r#where) = match &query.resource { VarOrNamedNode::Variable(var) => { let select = TriplePattern { subject: VarOrNode::Variable(var.clone()), - predicate: VarOrNode::Variable(format!("{var}{p}")), + predicate: VarOrNamedNode::Variable(format!("{var}{p}")), object: VarOrNodeOrLiteral::Variable(format!("{var}{o}")), }; @@ -204,7 +234,7 @@ pub mod query { VarOrNamedNode::NamedNode(iri) => { let select = TriplePattern { subject: VarOrNode::Node(Node::NamedNode(iri.clone())), - predicate: VarOrNode::Variable(p), + predicate: VarOrNamedNode::Variable(p), object: VarOrNodeOrLiteral::Variable(o), }; @@ -216,38 +246,17 @@ pub mod query { ) } }; - let prefix_map = ::from(query.prefixes).into_inner(); - let mut plan_builder = PlanBuilder::new(deps.storage, &prefix_map, None) - .with_limit(store.limits.max_query_limit as usize); - let plan = plan_builder.build_plan(&r#where)?; - - let atoms = QueryEngine::new(deps.storage) - .select(plan, util::as_select_variables(&select)) - .and_then(|res| { - res.solutions.resolve_atoms( - deps.storage, - &prefix_map, - select, - plan_builder.cached_namespaces(), - ) - })?; - let out: Vec = Vec::default(); - let mut writer = TripleWriter::new(&(&format).into(), out); - - for atom in &atoms { - let triple = atom.into(); - - writer.write(&triple).map_err(|e| { - StdError::serialize_err( - "triple", - format!("Error writing triple {}: {}", &triple, e), - ) - })?; - } - let out = writer - .finish() - .map_err(|e| StdError::serialize_err("triple", format!("Error writing triple: {e}")))?; + let out = util::construct_atoms( + deps.storage, + &format, + query.prefixes, + construct + .into_iter() + .map(|t| (t.subject, t.predicate, t.object)) + .collect(), + r#where, + )?; Ok(DescribeResponse { format, @@ -265,43 +274,54 @@ pub mod query { prefixes, r#where, } = query; - let patterns: Vec = if construct.is_empty() { - util::as_triple_patterns(&r#where)? + + 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() } else { construct }; - 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 atoms = QueryEngine::new(deps.storage) - .select(plan, util::as_select_variables(&patterns)) - .and_then(|res| { - res.solutions.resolve_atoms( - deps.storage, - &prefix_map, - patterns, - plan_builder.cached_namespaces(), - ) - })?; - - let out: Vec = Vec::default(); - let mut writer = TripleWriter::new(&(&format).into(), out); - - for atom in &atoms { - let triple = atom.into(); - writer.write(&triple).map_err(|e| { - StdError::serialize_err( - "triple", - format!("Error writing triple {}: {}", &triple, e), - ) - })?; - } + let mut id_issuer = IdentifierIssuer::new("a", 0u128); + let construct: Vec<_> = construct + .into_iter() + .map(|t| TripleConstructTemplate { + subject: match t.subject { + VarOrNode::Node(Node::BlankNode(n)) => { + VarOrNode::Node(Node::BlankNode(id_issuer.get_str_or_issue(n))) + } + _ => t.subject, + }, + predicate: t.predicate, + object: match t.object { + VarOrNodeOrLiteral::Node(Node::BlankNode(n)) => { + VarOrNodeOrLiteral::Node(Node::BlankNode(id_issuer.get_str_or_issue(n))) + } + _ => t.object, + }, + }) + .collect(); - let out = writer - .finish() - .map_err(|e| StdError::serialize_err("triple", format!("Error writing triple: {e}")))?; + let out = util::construct_atoms( + deps.storage, + &format, + prefixes, + construct + .into_iter() + .map(|t| (t.subject, t.predicate, t.object)) + .collect(), + r#where, + )?; Ok(ConstructResponse { format, @@ -313,32 +333,16 @@ pub mod query { pub mod util { use super::*; use crate::msg::{ - Head, Results, SelectItem, SelectResponse, SimpleWhereCondition, TriplePattern, Value, - WhereCondition, + Head, Prefix, Results, SelectResponse, Value, VarOrNamedNode, VarOrNode, + VarOrNodeOrLiteral, WhereClause, }; - use crate::querier::SelectResults; - use crate::state::{Namespace, NamespaceResolver}; - use std::collections::{BTreeMap, HashSet}; - - pub fn as_select_variables(patterns: &[TriplePattern]) -> Vec { - let variables = patterns - .iter() - .flat_map(TriplePattern::variables) - .collect::>() - .into_iter() - .map(SelectItem::Variable) - .collect(); - variables - } - - pub fn as_triple_patterns(r#where: &[WhereCondition]) -> StdResult> { - r#where - .iter() - .map(|c| match c { - WhereCondition::Simple(SimpleWhereCondition::TriplePattern(tp)) => Ok(tp.clone()), - }) - .collect::>() - } + use crate::querier::{PlanBuilder, QueryEngine, SelectResults}; + use crate::rdf::{Atom, PrefixMap}; + use crate::state::{HasCachedNamespaces, Namespace, NamespaceResolver}; + use cosmwasm_std::Storage; + use okp4_rdf::normalize::IdentifierIssuer; + use okp4_rdf::serde::TripleWriter; + use std::collections::BTreeMap; pub fn map_select_solutions( deps: Deps<'_>, @@ -346,6 +350,7 @@ pub mod util { ns_cache: Vec, ) -> StdResult { let mut ns_resolver: NamespaceResolver = ns_cache.into(); + let mut id_issuer = IdentifierIssuer::new("b", 0u128); let mut bindings: Vec> = vec![]; for solution in res.solutions { @@ -355,11 +360,14 @@ pub mod util { .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) - })?, + 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, + )?, )) }) .collect::>>()?; @@ -371,6 +379,47 @@ pub mod util { results: Results { bindings }, }) } + + pub fn construct_atoms( + storage: &dyn Storage, + format: &DataFormat, + prefixes: Vec, + construct: Vec<(VarOrNode, VarOrNamedNode, VarOrNodeOrLiteral)>, + r#where: WhereClause, + ) -> StdResult> { + let store = STORE.load(storage)?; + + let prefix_map = ::from(prefixes).into_inner(); + let mut plan_builder = PlanBuilder::new(storage, &prefix_map, None) + .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(), + )? + .collect::>>()?; + + let out: Vec = Vec::default(); + let mut writer = TripleWriter::new(&format.into(), out); + + for atom in &atoms { + let triple = atom.into(); + + writer.write(&triple).map_err(|e| { + StdError::serialize_err( + "triple", + format!("Error writing triple {}: {}", &triple, e), + ) + })?; + } + writer + .finish() + .map_err(|e| StdError::serialize_err("triple", format!("Error writing triple: {e}"))) + } } #[cfg(test)] @@ -384,8 +433,8 @@ mod tests { use crate::msg::{ ConstructQuery, ConstructResponse, DescribeQuery, DescribeResponse, Head, Literal, Prefix, Results, SelectItem, SelectQuery, SelectResponse, StoreLimitsInput, - StoreLimitsInputBuilder, StoreResponse, Value, VarOrNamedNode, VarOrNode, - VarOrNodeOrLiteral, WhereCondition, + StoreLimitsInputBuilder, StoreResponse, Value, VarOrNamedNode, VarOrNamedNodeOrLiteral, + VarOrNode, VarOrNodeOrLiteral, WhereCondition, }; use crate::state::{ namespaces, triples, Namespace, Node, Object, StoreLimits, StoreStat, Subject, Triple, @@ -443,6 +492,10 @@ mod tests { ); assert_eq!(NAMESPACE_KEY_INCREMENT.load(&deps.storage).unwrap(), 0u128); + assert_eq!( + BLANK_NODE_IDENTIFIER_COUNTER.load(&deps.storage).unwrap(), + 0u128 + ); } #[test] @@ -564,6 +617,40 @@ mod tests { } } + #[test] + fn proper_insert_blank_nodes() { + let mut deps = mock_dependencies(); + + let info = mock_info("owner", &[]); + instantiate( + deps.as_mut(), + mock_env(), + info.clone(), + InstantiateMsg::default(), + ) + .unwrap(); + + let insert_msg = InsertData { + format: None, + data: read_test_data("blank-nodes.ttl"), + }; + + let res = execute(deps.as_mut(), mock_env(), info.clone(), insert_msg.clone()); + assert!(res.is_ok()); + assert_eq!( + BLANK_NODE_IDENTIFIER_COUNTER.load(&deps.storage).unwrap(), + 2u128 + ); + + // we insert the same data again to check the creation of new blank nodes + let res = execute(deps.as_mut(), mock_env(), info.clone(), insert_msg); + assert!(res.is_ok()); + assert_eq!( + BLANK_NODE_IDENTIFIER_COUNTER.load(&deps.storage).unwrap(), + 4u128 + ); + } + #[test] fn insert_existing_triples() { let mut deps = mock_dependencies(); @@ -764,26 +851,26 @@ mod tests { ( DeleteData { prefixes: vec![], - delete: vec![msg::TriplePattern { - subject: VarOrNode::Node(NamedNode(Full( + delete: vec![msg::TripleDeleteTemplate { + subject: VarOrNamedNode::NamedNode(Full( "https://ontology.okp4.space/dataverse/dataspace/metadata/unknown" .to_string(), - ))), - predicate: VarOrNode::Node(NamedNode(Full( + )), + predicate: VarOrNamedNode::NamedNode(Full( "https://ontology.okp4.space/core/hasTopic".to_string(), - ))), - object: VarOrNodeOrLiteral::Node(NamedNode(Full( + )), + object: VarOrNamedNodeOrLiteral::NamedNode(Full( "https://ontology.okp4.space/thesaurus/topic/Test".to_string(), - ))), + )), }], r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { subject: VarOrNode::Node(NamedNode(Full( "https://ontology.okp4.space/dataverse/dataspace/metadata/unknown" .to_string(), ))), - predicate: VarOrNode::Node(NamedNode(Full( + predicate: VarOrNamedNode::NamedNode(Full( "https://ontology.okp4.space/core/hasTopic".to_string(), - ))), + )), object: VarOrNodeOrLiteral::Node(NamedNode(Full( "https://ontology.okp4.space/thesaurus/topic/Test".to_string(), ))), @@ -796,20 +883,20 @@ mod tests { ( DeleteData { prefixes: vec![], - delete: vec![msg::TriplePattern { - subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), - predicate: VarOrNode::Node(NamedNode(Full( + delete: vec![msg::TripleDeleteTemplate { + subject: VarOrNamedNode::NamedNode(Full(id.to_string())), + predicate: VarOrNamedNode::NamedNode(Full( "https://ontology.okp4.space/core/hasTopic".to_string(), - ))), - object: VarOrNodeOrLiteral::Node(NamedNode(Full( + )), + object: VarOrNamedNodeOrLiteral::NamedNode(Full( "https://ontology.okp4.space/thesaurus/topic/Test".to_string(), - ))), + )), }], r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), - predicate: VarOrNode::Node(NamedNode(Full( + predicate: VarOrNamedNode::NamedNode(Full( "https://ontology.okp4.space/core/hasTopic".to_string(), - ))), + )), object: VarOrNodeOrLiteral::Node(NamedNode(Full( "https://ontology.okp4.space/thesaurus/topic/Test".to_string(), ))), @@ -831,20 +918,16 @@ mod tests { namespace: "https://ontology.okp4.space/thesaurus/topic/".to_string(), }, ], - delete: vec![msg::TriplePattern { - subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), - predicate: VarOrNode::Node(NamedNode(Prefixed( - "core:hasTopic".to_string(), - ))), - object: VarOrNodeOrLiteral::Node(NamedNode(Prefixed( + delete: vec![msg::TripleDeleteTemplate { + subject: VarOrNamedNode::NamedNode(Full(id.to_string())), + predicate: VarOrNamedNode::NamedNode(Prefixed("core:hasTopic".to_string())), + object: VarOrNamedNodeOrLiteral::NamedNode(Prefixed( "thesaurus:Test".to_string(), - ))), + )), }], r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), - predicate: VarOrNode::Node(NamedNode(Prefixed( - "core:hasTopic".to_string(), - ))), + predicate: VarOrNamedNode::NamedNode(Prefixed("core:hasTopic".to_string())), object: VarOrNodeOrLiteral::Node(NamedNode(Prefixed( "thesaurus:Test".to_string(), ))), @@ -866,18 +949,14 @@ mod tests { namespace: "https://ontology.okp4.space/thesaurus/topic/".to_string(), }, ], - delete: vec![msg::TriplePattern { - subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), - predicate: VarOrNode::Node(NamedNode(Prefixed( - "core:hasTopic".to_string(), - ))), - object: VarOrNodeOrLiteral::Variable("o".to_string()), + delete: vec![msg::TripleDeleteTemplate { + subject: VarOrNamedNode::NamedNode(Full(id.to_string())), + 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: VarOrNode::Node(NamedNode(Prefixed( - "core:hasTopic".to_string(), - ))), + predicate: VarOrNamedNode::NamedNode(Prefixed("core:hasTopic".to_string())), object: VarOrNodeOrLiteral::Variable("o".to_string()), }))], }, @@ -888,14 +967,14 @@ mod tests { ( DeleteData { prefixes: vec![], - delete: vec![msg::TriplePattern { - subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), - predicate: VarOrNode::Variable("p".to_string()), - object: VarOrNodeOrLiteral::Variable("o".to_string()), + delete: vec![msg::TripleDeleteTemplate { + subject: VarOrNamedNode::NamedNode(Full(id.to_string())), + 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: VarOrNode::Variable("p".to_string()), + predicate: VarOrNamedNode::Variable("p".to_string()), object: VarOrNodeOrLiteral::Variable("o".to_string()), }))], }, @@ -909,7 +988,7 @@ mod tests { delete: vec![], r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), - predicate: VarOrNode::Variable("p".to_string()), + predicate: VarOrNamedNode::Variable("p".to_string()), object: VarOrNodeOrLiteral::Variable("o".to_string()), }))], }, @@ -923,7 +1002,7 @@ mod tests { delete: vec![], r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { subject: VarOrNode::Variable("s".to_string()), - predicate: VarOrNode::Variable("p".to_string()), + predicate: VarOrNamedNode::Variable("p".to_string()), object: VarOrNodeOrLiteral::Variable("0".to_string()), }))], }, @@ -931,6 +1010,31 @@ mod tests { 17, Uint128::from(0u128), ), + ( + DeleteData { + prefixes: vec![ + Prefix { + prefix: "core".to_string(), + namespace: "https://ontology.okp4.space/core/".to_string(), + }, + Prefix { + prefix: "thesaurus".to_string(), + namespace: "https://ontology.okp4.space/thesaurus/topic/".to_string(), + }, + ], + delete: vec![msg::TripleDeleteTemplate { + subject: VarOrNamedNode::NamedNode(Full(id.to_string())), + predicate: VarOrNamedNode::NamedNode(Prefixed("core:hasTopic".to_string())), + object: VarOrNamedNodeOrLiteral::NamedNode(Prefixed( + "thesaurus:Test".to_string(), + )), + }], + r#where: vec![], + }, + 1, + 0, + Uint128::from(6921u128), + ), ]; for case in cases { @@ -1000,20 +1104,20 @@ mod tests { TC { command: DeleteData { prefixes: vec![], - delete: vec![msg::TriplePattern { - subject: VarOrNode::Node(NamedNode(Prefixed("foo:bar".to_string()))), - predicate: VarOrNode::Node(NamedNode(Full( + delete: vec![msg::TripleDeleteTemplate { + subject: VarOrNamedNode::NamedNode(Prefixed("foo:bar".to_string())), + predicate: VarOrNamedNode::NamedNode(Full( "https://ontology.okp4.space/core/hasTopic".to_string(), - ))), - object: VarOrNodeOrLiteral::Node(NamedNode(Full( + )), + object: VarOrNamedNodeOrLiteral::NamedNode(Full( "https://ontology.okp4.space/thesaurus/topic/Test".to_string(), - ))), + )), }], r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { subject: VarOrNode::Node(NamedNode(Prefixed("foo:bar".to_string()))), - predicate: VarOrNode::Node(NamedNode(Full( + predicate: VarOrNamedNode::NamedNode(Full( "https://ontology.okp4.space/core/hasTopic".to_string(), - ))), + )), object: VarOrNodeOrLiteral::Node(NamedNode(Full( "https://ontology.okp4.space/thesaurus/topic/Test".to_string(), ))), @@ -1024,41 +1128,23 @@ mod tests { TC { command: DeleteData { prefixes: vec![], - delete: vec![msg::TriplePattern { - subject: VarOrNode::Node(NamedNode(Full( + delete: vec![msg::TripleDeleteTemplate { + subject: VarOrNamedNode::NamedNode(Full( "https://ontology.okp4.space/thesaurus/topic/Test".to_string(), - ))), - predicate: VarOrNode::Variable("z".to_string()), - object: VarOrNodeOrLiteral::Variable("o".to_string()), + )), + 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.okp4.space/thesaurus/topic/Test".to_string(), ))), - predicate: VarOrNode::Variable("p".to_string()), + predicate: VarOrNamedNode::Variable("p".to_string()), object: VarOrNodeOrLiteral::Variable("o".to_string()), }))], }, expected: StdError::generic_err("Selected variable not found in query").into(), }, - TC { - command: DeleteData { - prefixes: vec![], - delete: vec![msg::TriplePattern { - subject: VarOrNode::Node(NamedNode(Full( - "https://ontology.okp4.space/thesaurus/topic/Test".to_string(), - ))), - predicate: VarOrNode::Node(NamedNode(Full( - "https://ontology.okp4.space/core/hasTopic".to_string(), - ))), - object: VarOrNodeOrLiteral::Node(NamedNode(Full( - "https://ontology.okp4.space/thesaurus/topic/Test".to_string(), - ))), - }], - r#where: vec![], - }, - expected: StdError::generic_err("Empty basic graph pattern").into(), - }, ]; for case in cases { @@ -1169,9 +1255,9 @@ mod tests { r#where: vec![WhereCondition::Simple(TriplePattern( msg::TriplePattern { subject: VarOrNode::Variable("a".to_string()), - predicate: VarOrNode::Node(NamedNode(Full( + predicate: VarOrNamedNode::NamedNode(Full( "https://ontology.okp4.space/core/hasDescription".to_string(), - ))), + )), object: VarOrNodeOrLiteral::Variable("b".to_string()), }, ))], @@ -1260,9 +1346,9 @@ mod tests { r#where: vec![WhereCondition::Simple(TriplePattern( msg::TriplePattern { subject: VarOrNode::Variable("a".to_string()), - predicate: VarOrNode::Node(NamedNode(Prefixed( + predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasDescription".to_string(), - ))), + )), object: VarOrNodeOrLiteral::Literal(Literal::LanguageTaggedString { value: "A test Dataset.".to_string(), language: "en".to_string() }), }, ))], @@ -1295,7 +1381,7 @@ mod tests { r#where: vec![WhereCondition::Simple(TriplePattern( msg::TriplePattern { subject: VarOrNode::Node(NamedNode(Full("https://ontology.okp4.space/dataverse/dataset/metadata/d1615703-4ee1-4e2f-997e-15aecf1eea4e".to_string()))), - predicate: VarOrNode::Variable("a".to_string()), + predicate: VarOrNamedNode::Variable("a".to_string()), object: VarOrNodeOrLiteral::Literal(Literal::LanguageTaggedString { value: "A test Dataset.".to_string(), language: "en".to_string() }), }, ))], @@ -1363,18 +1449,18 @@ mod tests { WhereCondition::Simple(TriplePattern( msg::TriplePattern { subject: VarOrNode::Variable("a".to_string()), - predicate: VarOrNode::Node(NamedNode(Prefixed( + predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasTemporalCoverage".to_string(), - ))), + )), object: VarOrNodeOrLiteral::Node(BlankNode("a".to_string())), }, )), WhereCondition::Simple(TriplePattern( msg::TriplePattern { subject: VarOrNode::Node(BlankNode("a".to_string())), - predicate: VarOrNode::Node(NamedNode(Prefixed( + predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasStartDate".to_string(), - ))), + )), object: VarOrNodeOrLiteral::Variable("b".to_string()), }, ))], @@ -1412,18 +1498,18 @@ mod tests { WhereCondition::Simple(TriplePattern( msg::TriplePattern { subject: VarOrNode::Variable("a".to_string()), - predicate: VarOrNode::Node(NamedNode(Prefixed( + predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasTemporalCoverage".to_string(), - ))), + )), object: VarOrNodeOrLiteral::Variable("blank".to_string()), }, )), WhereCondition::Simple(TriplePattern( msg::TriplePattern { subject: VarOrNode::Variable("blank".to_string()), - predicate: VarOrNode::Node(NamedNode(Prefixed( + predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasStartDate".to_string(), - ))), + )), object: VarOrNodeOrLiteral::Variable("b".to_string()), }, ))], @@ -1461,18 +1547,18 @@ mod tests { WhereCondition::Simple(TriplePattern( msg::TriplePattern { subject: VarOrNode::Variable("a".to_string()), - predicate: VarOrNode::Node(NamedNode(Prefixed( + predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasTemporalCoverage".to_string(), - ))), + )), object: VarOrNodeOrLiteral::Node(BlankNode("blank1".to_string())), }, )), WhereCondition::Simple(TriplePattern( msg::TriplePattern { subject: VarOrNode::Node(BlankNode("blank2".to_string())), - predicate: VarOrNode::Node(NamedNode(Prefixed( + predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasInformation".to_string(), - ))), + )), object: VarOrNodeOrLiteral::Variable("b".to_string()), }, ))], @@ -1510,9 +1596,9 @@ mod tests { WhereCondition::Simple(TriplePattern( msg::TriplePattern { subject: VarOrNode::Variable("a".to_string()), - predicate: VarOrNode::Node(NamedNode(Prefixed( + predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasTemporalCoverage".to_string(), - ))), + )), object: VarOrNodeOrLiteral::Variable("b".to_string()), }, ))], @@ -1532,7 +1618,7 @@ mod tests { ( "b".to_string(), Value::BlankNode { - value: "riog00000001".to_string(), + value: "b0".to_string(), } ) ]) @@ -1608,9 +1694,9 @@ mod tests { select: vec![SelectItem::Variable("a".to_string())], r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { subject: VarOrNode::Variable("a".to_string()), - predicate: VarOrNode::Node(NamedNode(Prefixed( + predicate: VarOrNamedNode::NamedNode(Prefixed( "invalid:hasDescription".to_string(), - ))), + )), object: VarOrNodeOrLiteral::Literal(Literal::LanguageTaggedString { value: "A test Dataset.".to_string(), language: "en".to_string(), @@ -1626,9 +1712,9 @@ mod tests { select: vec![SelectItem::Variable("u".to_string())], r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { subject: VarOrNode::Variable("a".to_string()), - predicate: VarOrNode::Node(NamedNode(Full( + predicate: VarOrNamedNode::NamedNode(Full( "https://ontology.okp4.space/core/hasDescription".to_string(), - ))), + )), object: VarOrNodeOrLiteral::Literal(Literal::LanguageTaggedString { value: "A test Dataset.".to_string(), language: "en".to_string(), @@ -1738,9 +1824,9 @@ mod tests { r#where: vec![WhereCondition::Simple(TriplePattern( msg::TriplePattern { subject: VarOrNode::Variable("a".to_string()), - predicate: VarOrNode::Node(NamedNode(Full( + predicate: VarOrNamedNode::NamedNode(Full( "https://ontology.okp4.space/core/hasDescription".to_string(), - ))), + )), object: VarOrNodeOrLiteral::Variable("b".to_string()), }, ))], @@ -1912,9 +1998,9 @@ mod tests { r#where: vec![WhereCondition::Simple(TriplePattern( msg::TriplePattern { subject: VarOrNode::Variable("a".to_string()), - predicate: VarOrNode::Node(NamedNode(Prefixed( + predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasDescription".to_string(), - ))), + )), object: VarOrNodeOrLiteral::Literal(Literal::LanguageTaggedString { value: "A test Dataset.".to_string(), language: "en".to_string() }), }, ))], @@ -1977,9 +2063,9 @@ mod tests { r#where: vec![WhereCondition::Simple(TriplePattern( msg::TriplePattern { subject: VarOrNode::Variable("a".to_string()), - predicate: VarOrNode::Node(NamedNode(Prefixed( + predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasPublisher".to_string(), - ))), + )), object: VarOrNodeOrLiteral::Literal(Literal::Simple("OKP4".to_string())), }, ))], @@ -2045,9 +2131,9 @@ mod tests { r#where: vec![WhereCondition::Simple(TriplePattern( msg::TriplePattern { subject: VarOrNode::Node(NamedNode(Prefixed("metadata-dataset:80b1f84e-86dc-4730-b54f-701ad9b1888a".to_string()))), - predicate: VarOrNode::Node(NamedNode(Prefixed( + predicate: VarOrNamedNode::NamedNode(Prefixed( "core:hasTemporalCoverage".to_string(), - ))), + )), object: VarOrNodeOrLiteral::Variable("x".to_string()), }, )), @@ -2058,7 +2144,7 @@ mod tests { DescribeResponse { format: DataFormat::Turtle, data: Binary::from( - " , ;\n\t \"2022-01-01T00:00:00+00:00\"^^ .\n".to_string().as_bytes().to_vec()), + " , ;\n\t \"2022-01-01T00:00:00+00:00\"^^ .\n".to_string().as_bytes().to_vec()), } ), ]; @@ -2103,61 +2189,143 @@ mod tests { #[test] fn proper_construct() { let id = "https://ontology.okp4.space/dataverse/dataspace/metadata/dcf48417-01c5-4b43-9bc7-49e54c028473"; - let cases = vec![( - QueryMsg::Construct { - query: ConstructQuery { - prefixes: vec![], - construct: vec![], - r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { - subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), - predicate: VarOrNode::Node(NamedNode(Full( - "https://ontology.okp4.space/core/hasTag".to_string(), - ))), - object: VarOrNodeOrLiteral::Variable("o".to_string()), - }))], - }, - format: None, - }, - ConstructResponse { - format: DataFormat::Turtle, - data: Binary::from( - " \"Test\" , \"OKP4\" .\n".to_string().as_bytes().to_vec()), - }, - ), - ( - QueryMsg::Construct { - query: ConstructQuery { - prefixes: vec![ - Prefix { prefix: "my-ns".to_string(), namespace: "https://my-ns.org/".to_string() }, - Prefix { prefix: "metadata-dataset".to_string(), namespace: "https://ontology.okp4.space/dataverse/dataset/metadata/".to_string() }, - ], - construct: vec![ - msg::TriplePattern { - subject: VarOrNode::Node(NamedNode(Prefixed("my-ns:instance-1".to_string()))), - predicate: VarOrNode::Node(NamedNode(Full( - "https://my-ns/predicate/tag".to_string(), - ))), - object: VarOrNodeOrLiteral::Variable("o".to_string()), - } - ], - r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { - subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), - predicate: VarOrNode::Node(NamedNode(Full( - "https://ontology.okp4.space/core/hasTag".to_string(), - ))), - object: VarOrNodeOrLiteral::Variable("o".to_string()), - }))], - }, - format: Some(DataFormat::NTriples), - }, - ConstructResponse { - format: DataFormat::NTriples, - data: Binary::from( - " \"Test\" .\n \"OKP4\" .\n".to_string().as_bytes().to_vec()), - }, - )]; + let cases = vec![ + ( + InsertData { + format: Some(DataFormat::RDFXml), + data: read_test_data("sample.rdf.xml"), + }, + QueryMsg::Construct { + query: ConstructQuery { + prefixes: vec![], + construct: vec![], + r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { + subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), + predicate: VarOrNamedNode::NamedNode(Full( + "https://ontology.okp4.space/core/hasTag".to_string(), + )), + object: VarOrNodeOrLiteral::Variable("o".to_string()), + }))], + }, + format: None, + }, + ConstructResponse { + format: DataFormat::Turtle, + data: Binary::from( + " \"Test\" , \"OKP4\" .\n".to_string().as_bytes().to_vec()), + }, + ), + ( + InsertData { + format: Some(DataFormat::RDFXml), + data: read_test_data("sample.rdf.xml"), + }, + QueryMsg::Construct { + query: ConstructQuery { + prefixes: vec![ + Prefix { prefix: "my-ns".to_string(), namespace: "https://my-ns.org/".to_string() }, + Prefix { prefix: "metadata-dataset".to_string(), namespace: "https://ontology.okp4.space/dataverse/dataset/metadata/".to_string() }, + ], + construct: vec![ + msg::TripleConstructTemplate { + subject: VarOrNode::Node(NamedNode(Prefixed("my-ns:instance-1".to_string()))), + predicate: VarOrNamedNode::NamedNode(Full( + "https://my-ns/predicate/tag".to_string(), + )), + object: VarOrNodeOrLiteral::Variable("o".to_string()), + } + ], + r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { + subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), + predicate: VarOrNamedNode::NamedNode(Full( + "https://ontology.okp4.space/core/hasTag".to_string(), + )), + object: VarOrNodeOrLiteral::Variable("o".to_string()), + }))], + }, + format: Some(DataFormat::NTriples), + }, + ConstructResponse { + format: DataFormat::NTriples, + data: Binary::from( + " \"Test\" .\n \"OKP4\" .\n".to_string().as_bytes().to_vec()), + }, + ), + ( + InsertData { + format: Some(DataFormat::Turtle), + data: read_test_data("blank-nodes.ttl"), + }, + QueryMsg::Construct { + query: ConstructQuery { + prefixes: vec![ + Prefix { prefix: "core".to_string(), namespace: "https://ontology.okp4.space/core/".to_string() }, + Prefix { prefix: "metadata-dataset".to_string(), namespace: "https://ontology.okp4.space/dataverse/dataset/metadata/".to_string() }, + ], + construct: vec![ + msg::TripleConstructTemplate { + subject: VarOrNode::Node(BlankNode("my-metadata".to_string())), + predicate: VarOrNamedNode::NamedNode(Full( + "https://my-ns/predicate/tcov".to_string(), + )), + object: VarOrNodeOrLiteral::Variable("tcov".to_string()), + }, + msg::TripleConstructTemplate { + subject: VarOrNode::Node(BlankNode("my-metadata".to_string())), + predicate: VarOrNamedNode::NamedNode(Full( + "https://my-ns/predicate/info".to_string(), + )), + object: VarOrNodeOrLiteral::Variable("info".to_string()), + }, + msg::TripleConstructTemplate { + subject: VarOrNode::Variable("tcov".to_string()), + predicate: VarOrNamedNode::Variable("tcov_p".to_string()), + object: VarOrNodeOrLiteral::Variable("tcov_o".to_string()), + }, + msg::TripleConstructTemplate { + subject: VarOrNode::Variable("info".to_string()), + predicate: VarOrNamedNode::Variable("info_p".to_string()), + object: VarOrNodeOrLiteral::Variable("info_o".to_string()), + } + ], + r#where: vec![ + WhereCondition::Simple(TriplePattern(msg::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 { + 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 { + 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 { + subject: VarOrNode::Variable("info".to_string()), + predicate: VarOrNamedNode::Variable("info_p".to_string()), + object: VarOrNodeOrLiteral::Variable("info_o".to_string()), + })) + ], + }, + format: Some(DataFormat::NTriples), + }, + ConstructResponse { + format: DataFormat::NTriples, + data: Binary::from( + " .\n .\n .\n \"this is a dataset\" .\n .\n .\n .\n \"this is a dataset\" .\n .\n .\n \"2022-01-01T00:00:00+00:00\"^^ .\n \"this is a dataset\" .\n".to_string().as_bytes().to_vec()), + }, + ), + ]; - for (q, expected) in cases { + for (data, q, expected) in cases { let mut deps = mock_dependencies(); let info = mock_info("owner", &[]); @@ -2171,16 +2339,7 @@ mod tests { ) .unwrap(); - execute( - deps.as_mut(), - mock_env(), - info.clone(), - InsertData { - format: Some(DataFormat::RDFXml), - data: read_test_data("sample.rdf.xml"), - }, - ) - .unwrap(); + execute(deps.as_mut(), mock_env(), info.clone(), data).unwrap(); let res = query(deps.as_ref(), mock_env(), q); diff --git a/contracts/okp4-cognitarium/src/msg.rs b/contracts/okp4-cognitarium/src/msg.rs index 5fc2c077..5ad0158b 100644 --- a/contracts/okp4-cognitarium/src/msg.rs +++ b/contracts/okp4-cognitarium/src/msg.rs @@ -51,7 +51,7 @@ pub enum ExecuteMsg { /// "where": [ /// { "simple": { "triplePattern": { /// "subject": { "variable": "s" }, - /// "predicate": { "node": { "namedNode": {"prefixed": "foaf:givenName"} } }, + /// "predicate": { "namedNode": {"prefixed": "foaf:givenName"} }, /// "object": { "literal": { "simple": "Myrddin" } } /// } } }, /// { "simple": { "triplePattern": { @@ -67,9 +67,9 @@ pub enum ExecuteMsg { DeleteData { /// The prefixes used in the operation. prefixes: Vec, - /// Specifies the specific triple patterns to delete. + /// Specifies the specific triple templates to delete. /// If nothing is provided, the patterns from the `where` clause are used for deletion. - delete: Vec, + delete: Vec, /// Defines the patterns that data (RDF triples) should match in order for it to be /// considered for deletion. r#where: WhereClause, @@ -436,7 +436,7 @@ pub struct ConstructQuery { pub prefixes: Vec, /// The triples to construct. /// If nothing is provided, the patterns from the `where` clause are used for construction. - pub construct: Vec, + pub construct: Vec, /// The WHERE clause. /// This clause is used to specify the triples to construct using variable bindings. pub r#where: WhereClause, @@ -484,6 +484,30 @@ pub enum SimpleWhereCondition { TriplePattern(TriplePattern), } +/// # TripleDeleteTemplate +/// Represents a triple template to be deleted. +#[cw_serde] +pub struct TripleDeleteTemplate { + /// The subject of the triple pattern. + pub subject: VarOrNamedNode, + /// The predicate of the triple pattern. + pub predicate: VarOrNamedNode, + /// The object of the triple pattern. + pub object: VarOrNamedNodeOrLiteral, +} + +/// # TripleConstructTemplate +/// Represents a triple template to be forged for a construct query. +#[cw_serde] +pub struct TripleConstructTemplate { + /// The subject of the triple pattern. + pub subject: VarOrNode, + /// The predicate of the triple pattern. + pub predicate: VarOrNamedNode, + /// The object of the triple pattern. + pub object: VarOrNodeOrLiteral, +} + /// # TriplePattern /// Represents a triple pattern in a [SimpleWhereCondition]. #[cw_serde] @@ -491,32 +515,11 @@ pub struct TriplePattern { /// The subject of the triple pattern. pub subject: VarOrNode, /// The predicate of the triple pattern. - pub predicate: VarOrNode, + pub predicate: VarOrNamedNode, /// The object of the triple pattern. pub object: VarOrNodeOrLiteral, } -impl TriplePattern { - /// Returns the variables used in the triple pattern. - pub fn variables(&self) -> Vec { - let mut variables: Vec = vec![]; - - if let VarOrNode::Variable(var) = &self.subject { - variables.push(var.clone()); - } - - if let VarOrNode::Variable(var) = &self.predicate { - variables.push(var.clone()); - } - - if let VarOrNodeOrLiteral::Variable(var) = &self.object { - variables.push(var.clone()); - } - - variables - } -} - /// # VarOrNode /// Represents either a variable or a node. #[cw_serde] @@ -557,6 +560,22 @@ pub enum VarOrNodeOrLiteral { Literal(Literal), } +/// # VarOrNamedNodeOrLiteral +/// Represents either a variable, a named node or a literal. +#[cw_serde] +pub enum VarOrNamedNodeOrLiteral { + /// # Variable + /// A variable. + Variable(String), + /// # NamedNode + /// An RDF [IRI](https://www.w3.org/TR/rdf11-concepts/#dfn-iri). + NamedNode(IRI), + /// # Literal + /// An RDF [literal](https://www.w3.org/TR/rdf11-concepts/#dfn-literal), i.e. a simple literal, + /// a language-tagged string or a typed value. + Literal(Literal), +} + /// # Literal /// An RDF [literal](https://www.w3.org/TR/rdf11-concepts/#dfn-literal). #[cw_serde] @@ -596,12 +615,7 @@ pub enum Node { #[cfg(test)] mod tests { - use crate::msg::Literal::Simple; - use crate::msg::Node::{BlankNode, NamedNode}; - use crate::msg::IRI::{Full, Prefixed}; - use crate::msg::{ - InstantiateMsg, StoreLimitsInput, TriplePattern, VarOrNode, VarOrNodeOrLiteral, - }; + use crate::msg::{InstantiateMsg, StoreLimitsInput}; use cosmwasm_std::Uint128; use schemars::_serde_json; @@ -636,77 +650,4 @@ mod tests { assert_eq!(msg.limits.max_insert_data_byte_size, Uint128::MAX); assert_eq!(msg.limits.max_insert_data_triple_count, Uint128::MAX); } - - #[test] - fn variables_from_triple_pattern() { - let (s, p, o) = ("s".to_string(), "p".to_string(), "o".to_string()); - let (node, prefixed, literal) = ( - "node".to_string(), - "a:node".to_string(), - "literal".to_string(), - ); - - let cases = vec![ - ( - TriplePattern { - subject: VarOrNode::Variable(s.clone()), - predicate: VarOrNode::Variable(p.clone()), - object: VarOrNodeOrLiteral::Variable(o.clone()), - }, - vec![s.clone(), p.clone(), o.clone()], - ), - ( - TriplePattern { - subject: VarOrNode::Node(NamedNode(Full(node.clone()))), - predicate: VarOrNode::Variable(p.clone()), - object: VarOrNodeOrLiteral::Variable(o.clone()), - }, - vec![p.clone(), o.clone()], - ), - ( - TriplePattern { - subject: VarOrNode::Node(NamedNode(Prefixed(prefixed.clone()))), - predicate: VarOrNode::Variable(p.clone()), - object: VarOrNodeOrLiteral::Variable(o.clone()), - }, - vec![p.clone(), o.clone()], - ), - ( - TriplePattern { - subject: VarOrNode::Node(BlankNode(node.clone())), - predicate: VarOrNode::Variable(p.clone()), - object: VarOrNodeOrLiteral::Variable(o.clone()), - }, - vec![p.clone(), o.clone()], - ), - ( - TriplePattern { - subject: VarOrNode::Variable(s.clone()), - predicate: VarOrNode::Node(NamedNode(Full(node.clone()))), - object: VarOrNodeOrLiteral::Variable(o.clone()), - }, - vec![s.clone(), o], - ), - ( - TriplePattern { - subject: VarOrNode::Variable(s.clone()), - predicate: VarOrNode::Variable(p.clone()), - object: VarOrNodeOrLiteral::Literal(Simple(literal.clone())), - }, - vec![s, p], - ), - ( - TriplePattern { - subject: VarOrNode::Node(BlankNode(node)), - predicate: VarOrNode::Node(NamedNode(Prefixed(prefixed))), - object: VarOrNodeOrLiteral::Literal(Simple(literal)), - }, - vec![], - ), - ]; - - for (triple_pattern, expected) in cases { - assert_eq!(triple_pattern.variables(), expected); - } - } } diff --git a/contracts/okp4-cognitarium/src/querier/engine.rs b/contracts/okp4-cognitarium/src/querier/engine.rs index dc4617da..c48d8d80 100644 --- a/contracts/okp4-cognitarium/src/querier/engine.rs +++ b/contracts/okp4-cognitarium/src/querier/engine.rs @@ -1,7 +1,7 @@ -use crate::msg::{SelectItem, TriplePattern, VarOrNode, VarOrNodeOrLiteral}; -use crate::querier::mapper::{ - literal_as_object, node_as_object, node_as_predicate, node_as_subject, +use crate::msg::{ + Node, SelectItem, VarOrNamedNode, VarOrNamedNodeOrLiteral, VarOrNode, VarOrNodeOrLiteral, }; +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; @@ -9,6 +9,7 @@ use crate::state::{triples, Namespace, NamespaceResolver, Object, Predicate, Sub use crate::{rdf, state}; use cosmwasm_std::{Order, StdError, StdResult, Storage}; use either::{Either, Left, Right}; +use okp4_rdf::normalize::IdentifierIssuer; use std::collections::{BTreeMap, HashMap, VecDeque}; use std::iter; use std::rc::Rc; @@ -53,6 +54,66 @@ impl<'a> QueryEngine<'a> { }) } + pub fn construct_atoms( + &'a self, + plan: QueryPlan, + prefixes: &HashMap, + templates: Vec<(VarOrNode, VarOrNamedNode, VarOrNodeOrLiteral)>, + ns_cache: Vec, + ) -> StdResult> { + let templates = templates + .into_iter() + .map(|t| AtomTemplate::try_new(&plan, prefixes, t)) + .collect::>>()?; + + Ok(ResolvedAtomIterator::new( + self.storage, + ns_cache.into(), + IdentifierIssuer::new("b", 0u128), + self.eval_plan(plan), + templates, + )) + } + + pub fn construct_triples( + &'a self, + plan: QueryPlan, + templates: Vec, + ) -> ResolvedTripleIterator<'_> { + ResolvedTripleIterator::new(self.eval_plan(plan), templates) + } + + pub fn make_triple_templates( + &'a self, + plan: &QueryPlan, + prefixes: &HashMap, + templates: Either, Vec>, + ns_cache: Vec, + ) -> StdResult> { + let mut ns_resolver: NamespaceResolver = ns_cache.into(); + + match templates { + Left(tpl) => tpl + .into_iter() + .map(|t| { + TripleTemplate::try_new(self.storage, &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), + ) + }) + .collect::>>(), + } + } + pub fn eval_plan(&'a self, plan: QueryPlan) -> ResolvedVariablesIterator<'_> { return self.eval_node(plan.entrypoint)(ResolvedVariables::with_capacity( plan.variables.len(), @@ -436,34 +497,6 @@ impl<'a> SolutionsIterator<'a> { fn new(iter: ResolvedVariablesIterator<'a>, bindings: BTreeMap) -> Self { Self { iter, bindings } } - - pub fn resolve_triples( - self, - storage: &dyn Storage, - prefixes: &HashMap, - patterns: Vec, - ns_cache: Vec, - ) -> StdResult> { - let mut ns_resolver = ns_cache.into(); - - let triples_iter = - ResolvedTripleIterator::try_new(&mut ns_resolver, storage, self, prefixes, patterns)?; - triples_iter.collect() - } - - pub fn resolve_atoms( - self, - storage: &dyn Storage, - prefixes: &HashMap, - patterns: Vec, - ns_cache: Vec, - ) -> StdResult> { - let mut ns_resolver = ns_cache.into(); - - let atoms_iter = - ResolvedAtomIterator::try_new(&mut ns_resolver, storage, self, prefixes, patterns)?; - atoms_iter.collect() - } } impl<'a> Iterator for SolutionsIterator<'a> { @@ -494,27 +527,21 @@ impl<'a> Iterator for SolutionsIterator<'a> { } pub struct ResolvedTripleIterator<'a> { - iter: SolutionsIterator<'a>, + upstream_iter: ResolvedVariablesIterator<'a>, templates: Vec, buffer: VecDeque>, } impl<'a> ResolvedTripleIterator<'a> { - pub fn try_new( - ns_resolver: &mut NamespaceResolver, - storage: &dyn Storage, - solutions: SolutionsIterator<'a>, - prefixes: &HashMap, - patterns: Vec, - ) -> StdResult { - Ok(Self { - iter: solutions, - templates: patterns - .iter() - .map(|p| TripleTemplate::try_new(ns_resolver, storage, prefixes, p)) - .collect::>>()?, + pub fn new( + upstream_iter: ResolvedVariablesIterator<'a>, + templates: Vec, + ) -> Self { + Self { + upstream_iter, + templates, buffer: VecDeque::new(), - }) + } } } @@ -527,7 +554,7 @@ impl<'a> Iterator for ResolvedTripleIterator<'a> { return Some(val); } - let upstream_res = match self.iter.next() { + let upstream_res = match self.upstream_iter.next() { None => None?, Some(res) => res, }; @@ -554,42 +581,36 @@ impl<'a> Iterator for ResolvedTripleIterator<'a> { } } -struct TripleTemplate { - subject: Either, - predicate: Either, - object: Either, +pub struct TripleTemplate { + subject: Either, + predicate: Either, + object: Either, } +pub type TripleTemplateWithBlankNode = (VarOrNode, VarOrNamedNode, VarOrNodeOrLiteral); +pub type TripleTemplateNoBlankNode = (VarOrNamedNode, VarOrNamedNode, VarOrNamedNodeOrLiteral); + impl TripleTemplate { fn try_new( - ns_resolver: &mut NamespaceResolver, storage: &dyn Storage, + ns_resolver: &mut NamespaceResolver, + plan: &QueryPlan, prefixes: &HashMap, - pattern: &TriplePattern, + template: Either, ) -> StdResult { + let (s_tpl, p_tpl, o_tpl) = match template { + Right((s, p, o)) => (Right(s), p, Right(o)), + Left((s, p, o)) => (Left(s), p, Left(o)), + }; + Ok(TripleTemplate { - subject: Self::build_subject_pattern( - ns_resolver, - storage, - prefixes, - pattern.subject.clone(), - )?, - predicate: Self::build_predicate_pattern( - ns_resolver, - storage, - prefixes, - pattern.predicate.clone(), - )?, - object: Self::build_object_pattern( - ns_resolver, - storage, - prefixes, - pattern.object.clone(), - )?, + 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)?, }) } - pub fn resolve(&self, vars: &BTreeMap) -> StdResult> { + pub fn resolve(&self, vars: &ResolvedVariables) -> StdResult> { let subject = match Self::resolve_triple_term( &self.subject, ResolvedVariable::as_subject, @@ -628,9 +649,9 @@ impl TripleTemplate { } fn resolve_triple_term( - term: &Either, + term: &Either, from_var: F, - vars: &BTreeMap, + vars: &ResolvedVariables, term_name: &str, ) -> StdResult> where @@ -638,47 +659,87 @@ impl TripleTemplate { F: Fn(&ResolvedVariable) -> Option, { match term { - Left(p) => StdResult::Ok(Some(p.clone())), - Right(key) => vars.get(key).map(from_var).ok_or_else(|| { + Left(p) => Ok(Some(p.clone())), + Right(key) => vars.get(*key).as_ref().map(from_var).ok_or_else(|| { StdError::generic_err(format!("Unbound {:?} variable: {:?}", term_name, key)) }), } } - fn build_subject_pattern( - ns_resolver: &mut NamespaceResolver, + fn build_subject_template( storage: &dyn Storage, + ns_resolver: &mut NamespaceResolver, + plan: &QueryPlan, prefixes: &HashMap, - value: VarOrNode, - ) -> StdResult> { + value: Either, + ) -> StdResult> { Ok(match value { - VarOrNode::Variable(v) => Right(v), - VarOrNode::Node(n) => Left(node_as_subject(ns_resolver, storage, prefixes, n)?), + Left(VarOrNode::Variable(v)) | Right(VarOrNamedNode::Variable(v)) => { + Right(plan.get_var_index(v.as_str()).ok_or(StdError::generic_err( + "Selected variable not found in query", + ))?) + } + Left(VarOrNode::Node(Node::BlankNode(n))) => Right( + plan.get_bnode_index(n.as_str()) + .ok_or(StdError::generic_err( + "Selected blank node not found in query", + ))?, + ), + Left(VarOrNode::Node(Node::NamedNode(iri))) | Right(VarOrNamedNode::NamedNode(iri)) => { + Left(Subject::Named(iri_as_node( + ns_resolver, + storage, + prefixes, + iri, + )?)) + } }) } - fn build_predicate_pattern( - ns_resolver: &mut NamespaceResolver, + fn build_predicate_template( storage: &dyn Storage, + ns_resolver: &mut NamespaceResolver, + plan: &QueryPlan, prefixes: &HashMap, - value: VarOrNode, - ) -> StdResult> { + value: VarOrNamedNode, + ) -> StdResult> { Ok(match value { - VarOrNode::Variable(v) => Right(v), - VarOrNode::Node(n) => Left(node_as_predicate(ns_resolver, storage, prefixes, n)?), + 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)?) + } }) } - fn build_object_pattern( - ns_resolver: &mut NamespaceResolver, + fn build_object_template( storage: &dyn Storage, + ns_resolver: &mut NamespaceResolver, + plan: &QueryPlan, prefixes: &HashMap, - value: VarOrNodeOrLiteral, - ) -> StdResult> { + value: Either, + ) -> StdResult> { Ok(match value { - VarOrNodeOrLiteral::Variable(v) => Right(v), - VarOrNodeOrLiteral::Node(n) => Left(node_as_object(ns_resolver, storage, prefixes, n)?), - VarOrNodeOrLiteral::Literal(l) => { + Left(VarOrNodeOrLiteral::Variable(v)) | Right(VarOrNamedNodeOrLiteral::Variable(v)) => { + Right(plan.get_var_index(v.as_str()).ok_or(StdError::generic_err( + "Selected variable not found in query", + ))?) + } + Left(VarOrNodeOrLiteral::Node(Node::BlankNode(n))) => Right( + plan.get_bnode_index(n.as_str()) + .ok_or(StdError::generic_err( + "Selected blank node not found in query", + ))?, + ), + Left(VarOrNodeOrLiteral::Node(Node::NamedNode(iri))) + | Right(VarOrNamedNodeOrLiteral::NamedNode(iri)) => Left(Object::Named(iri_as_node( + ns_resolver, + storage, + prefixes, + iri, + )?)), + Left(VarOrNodeOrLiteral::Literal(l)) | Right(VarOrNamedNodeOrLiteral::Literal(l)) => { Left(literal_as_object(ns_resolver, storage, prefixes, l)?) } }) @@ -686,31 +747,30 @@ impl TripleTemplate { } pub struct ResolvedAtomIterator<'a> { - ns_resolver: &'a mut NamespaceResolver, storage: &'a dyn Storage, - iter: SolutionsIterator<'a>, + ns_resolver: NamespaceResolver, + id_issuer: IdentifierIssuer, + upstream_iter: ResolvedVariablesIterator<'a>, templates: Vec, buffer: VecDeque>, } impl<'a> ResolvedAtomIterator<'a> { - pub fn try_new( - ns_resolver: &'a mut NamespaceResolver, + pub fn new( storage: &'a dyn Storage, - solutions: SolutionsIterator<'a>, - prefixes: &HashMap, - patterns: Vec, - ) -> StdResult { - Ok(Self { - ns_resolver, + ns_resolver: NamespaceResolver, + id_issuer: IdentifierIssuer, + upstream_iter: ResolvedVariablesIterator<'a>, + templates: Vec, + ) -> Self { + Self { storage, - iter: solutions, - templates: patterns - .iter() - .map(|p| AtomTemplate::try_new(prefixes, p)) - .collect::>>()?, + ns_resolver, + id_issuer, + upstream_iter, + templates, buffer: VecDeque::new(), - }) + } } } @@ -723,7 +783,7 @@ impl<'a> Iterator for ResolvedAtomIterator<'a> { return Some(val); } - let upstream_res = match self.iter.next() { + let upstream_res = match self.upstream_iter.next() { None => None?, Some(res) => res, }; @@ -733,11 +793,14 @@ impl<'a> Iterator for ResolvedAtomIterator<'a> { self.buffer.push_back(Err(err)); } Ok(vars) => { - for res in self - .templates - .iter() - .map(|template| template.resolve(self.ns_resolver, self.storage, &vars)) - { + for res in self.templates.iter().map(|template| { + template.resolve( + self.storage, + &mut self.ns_resolver, + &mut self.id_issuer, + &vars, + ) + }) { match res { Ok(Some(atom)) => self.buffer.push_back(Ok(atom)), Err(err) => self.buffer.push_back(Err(err)), @@ -750,39 +813,50 @@ impl<'a> Iterator for ResolvedAtomIterator<'a> { } } -struct AtomTemplate { - subject: Either, - property: Either, - value: Either, +pub struct AtomTemplate { + subject: Either, + property: Either, + value: Either, } impl AtomTemplate { pub fn try_new( + plan: &QueryPlan, prefixes: &HashMap, - pattern: &TriplePattern, + (s_tpl, p_tpl, o_tpl): (VarOrNode, VarOrNamedNode, VarOrNodeOrLiteral), ) -> StdResult { Ok(Self { - subject: match &pattern.subject { - VarOrNode::Variable(key) => Right(key.clone()), - VarOrNode::Node(n) => Left((n.clone(), prefixes).try_into()?), + subject: match s_tpl { + VarOrNode::Variable(key) => Right(plan.get_var_index(key.as_str()).ok_or( + StdError::generic_err("Selected variable not found in query"), + )?), + VarOrNode::Node(n) => Left((n, prefixes).try_into()?), }, - property: match &pattern.predicate { - VarOrNode::Variable(key) => Right(key.clone()), - VarOrNode::Node(n) => Left((n.clone(), prefixes).try_into()?), + property: match p_tpl { + VarOrNamedNode::Variable(key) => Right(plan.get_var_index(key.as_str()).ok_or( + StdError::generic_err("Selected variable not found in query"), + )?), + VarOrNamedNode::NamedNode(iri) => Left((iri, prefixes).try_into()?), }, - value: match &pattern.object { - VarOrNodeOrLiteral::Variable(key) => Right(key.clone()), - VarOrNodeOrLiteral::Node(n) => Left((n.clone(), prefixes).try_into()?), - VarOrNodeOrLiteral::Literal(l) => Left((l.clone(), prefixes).try_into()?), + value: match o_tpl { + VarOrNodeOrLiteral::Variable(key) => Right( + plan.get_var_index(key.as_str()) + .ok_or(StdError::generic_err( + "Selected variable not found in query", + ))?, + ), + VarOrNodeOrLiteral::Node(n) => Left((n, prefixes).try_into()?), + VarOrNodeOrLiteral::Literal(l) => Left((l, prefixes).try_into()?), }, }) } pub fn resolve( &self, - ns_resolver: &mut NamespaceResolver, storage: &dyn Storage, - vars: &BTreeMap, + ns_resolver: &mut NamespaceResolver, + id_issuer: &mut IdentifierIssuer, + vars: &ResolvedVariables, ) -> StdResult> { let resolve_ns_fn = &mut |ns_key| { let res = ns_resolver.resolve_from_key(storage, ns_key); @@ -790,7 +864,7 @@ impl AtomTemplate { .map(|ns| ns.value) }; - let subject = match self.resolve_atom_subject(resolve_ns_fn, vars)? { + let subject = match self.resolve_atom_subject(resolve_ns_fn, id_issuer, vars)? { Some(s) => s, None => return Ok(None), }; @@ -800,7 +874,7 @@ impl AtomTemplate { None => return Ok(None), }; - let value = match self.resolve_atom_value(resolve_ns_fn, vars)? { + let value = match self.resolve_atom_value(resolve_ns_fn, id_issuer, vars)? { Some(v) => v, None => return Ok(None), }; @@ -815,7 +889,8 @@ impl AtomTemplate { fn resolve_atom_subject( &self, resolve_ns_fn: &mut F, - vars: &BTreeMap, + id_issuer: &mut IdentifierIssuer, + vars: &ResolvedVariables, ) -> StdResult> where F: FnMut(u128) -> StdResult, @@ -827,7 +902,9 @@ impl AtomTemplate { &mut |value| { Ok(match value { Subject::Named(n) => rdf::Subject::NamedNode(n.as_iri(resolve_ns_fn)?), - Subject::Blank(n) => rdf::Subject::BlankNode(n), + Subject::Blank(n) => { + rdf::Subject::BlankNode(id_issuer.get_str_or_issue(n.to_string())) + } }) }, "subject", @@ -837,7 +914,7 @@ impl AtomTemplate { fn resolve_atom_property( &self, resolve_ns_fn: &mut F, - vars: &BTreeMap, + vars: &ResolvedVariables, ) -> StdResult> where F: FnMut(u128) -> StdResult, @@ -854,7 +931,8 @@ impl AtomTemplate { fn resolve_atom_value( &self, resolve_ns_fn: &mut F, - vars: &BTreeMap, + id_issuer: &mut IdentifierIssuer, + vars: &ResolvedVariables, ) -> StdResult> where F: FnMut(u128) -> StdResult, @@ -866,7 +944,9 @@ impl AtomTemplate { &mut |value| { Ok(match value { Object::Named(n) => rdf::Value::NamedNode(n.as_iri(resolve_ns_fn)?), - Object::Blank(n) => rdf::Value::BlankNode(n), + Object::Blank(n) => { + rdf::Value::BlankNode(id_issuer.get_str_or_issue(n.to_string())) + } Object::Literal(l) => match l { state::Literal::Simple { value } => rdf::Value::LiteralSimple(value), state::Literal::I18NString { value, language } => { @@ -883,9 +963,9 @@ impl AtomTemplate { } fn resolve_atom_term( - term: &Either, + term: &Either, from_var: F, - vars: &BTreeMap, + vars: &ResolvedVariables, mapping_fn: &mut M, term_name: &str, ) -> StdResult> @@ -897,7 +977,7 @@ impl AtomTemplate { match term { Left(v) => Ok(Some(v.clone())), Right(key) => { - let var = vars.get(key).ok_or_else(|| { + let var = vars.get(*key).as_ref().ok_or_else(|| { StdError::generic_err(format!("Unbound {:?} variable: {:?}", term_name, key)) })?; @@ -917,7 +997,9 @@ mod test { use crate::querier::plan::PlanVariable; use crate::state; use crate::state::Object::{Literal, Named}; - use crate::state::{Node, Store, StoreStat, NAMESPACE_KEY_INCREMENT, STORE}; + use crate::state::{ + Node, Store, StoreStat, BLANK_NODE_IDENTIFIER_COUNTER, NAMESPACE_KEY_INCREMENT, STORE, + }; use crate::storer::StoreEngine; use cosmwasm_std::testing::mock_dependencies; use cosmwasm_std::{Addr, Uint128}; @@ -954,6 +1036,7 @@ mod test { ) .unwrap(); NAMESPACE_KEY_INCREMENT.save(storage, &0u128).unwrap(); + BLANK_NODE_IDENTIFIER_COUNTER.save(storage, &0u128).unwrap(); let data = read_test_data("sample.rdf.xml"); let buf = BufReader::new(data.as_slice()); let mut reader = TripleReader::new(&okp4_rdf::serde::DataFormat::RDFXml, buf); @@ -1295,33 +1378,30 @@ mod test { #[test] fn for_loop_join_iter() { struct TestCase { - left: Vec, - right: Vec, - expects: Vec<(String, String)>, + left: Vec, + right: Vec, + expects: Vec<(u128, u128)>, } let cases = vec![ TestCase { left: vec![], - right: vec!["1".to_string(), "2".to_string()], + right: vec![0u128, 1u128], expects: vec![], }, TestCase { - left: vec!["A".to_string()], - right: vec!["1".to_string(), "2".to_string()], - expects: vec![ - ("A".to_string(), "1".to_string()), - ("A".to_string(), "2".to_string()), - ], + left: vec![2u128], + right: vec![0u128, 1u128], + expects: vec![(2u128, 0u128), (2u128, 1u128)], }, TestCase { - left: vec!["A".to_string(), "B".to_string()], - right: vec!["1".to_string(), "2".to_string()], + left: vec![2u128, 3u128], + right: vec![0u128, 1u128], expects: vec![ - ("A".to_string(), "1".to_string()), - ("A".to_string(), "2".to_string()), - ("B".to_string(), "1".to_string()), - ("B".to_string(), "2".to_string()), + (2u128, 0u128), + (2u128, 1u128), + (3u128, 0u128), + (3u128, 1u128), ], }, ]; @@ -1330,13 +1410,13 @@ mod test { let result = ForLoopJoinIterator::new( Box::new(case.left.iter().map(|v| { let mut vars = ResolvedVariables::with_capacity(3); - vars.merge_index(1, ResolvedVariable::Subject(Subject::Blank(v.clone()))); + vars.merge_index(1, ResolvedVariable::Subject(Subject::Blank(*v))); Ok(vars) })), Rc::new(|input| { Box::new(case.right.iter().map(move |v| { let mut vars = input.clone(); - vars.merge_index(2, ResolvedVariable::Subject(Subject::Blank(v.clone()))); + vars.merge_index(2, ResolvedVariable::Subject(Subject::Blank(*v))); Ok(vars) })) }), @@ -1349,8 +1429,8 @@ mod test { .iter() .map(|(v1, v2)| { let mut vars = ResolvedVariables::with_capacity(3); - vars.merge_index(1, ResolvedVariable::Subject(Subject::Blank(v1.clone()))); - vars.merge_index(2, ResolvedVariable::Subject(Subject::Blank(v2.clone()))); + vars.merge_index(1, ResolvedVariable::Subject(Subject::Blank(*v1))); + vars.merge_index(2, ResolvedVariable::Subject(Subject::Blank(*v2))); vars }) .collect(); @@ -1362,38 +1442,35 @@ mod test { #[test] fn cartesian_join_iter() { struct TestCase { - left: Vec, - right: Vec, - expects: Vec>, + left: Vec, + right: Vec, + expects: Vec>, } let cases = vec![ TestCase { left: vec![], - right: vec!["1".to_string(), "2".to_string()], + right: vec![0u128, 1u128], expects: vec![], }, TestCase { - left: vec!["1".to_string(), "2".to_string()], + left: vec![0u128, 1u128], right: vec![], expects: vec![], }, TestCase { - left: vec!["A".to_string()], - right: vec!["1".to_string(), "2".to_string()], - expects: vec![ - vec!["1".to_string(), "A".to_string()], - vec!["2".to_string(), "A".to_string()], - ], + left: vec![2u128], + right: vec![0u128, 1u128], + expects: vec![vec![0u128, 2u128], vec![1u128, 2u128]], }, TestCase { - left: vec!["A".to_string(), "B".to_string()], - right: vec!["1".to_string(), "2".to_string()], + left: vec![2u128, 3u128], + right: vec![0u128, 1u128], expects: vec![ - vec!["1".to_string(), "A".to_string()], - vec!["2".to_string(), "A".to_string()], - vec!["1".to_string(), "B".to_string()], - vec!["2".to_string(), "B".to_string()], + vec![0u128, 2u128], + vec![1u128, 2u128], + vec![0u128, 3u128], + vec![1u128, 3u128], ], }, ]; @@ -1404,13 +1481,13 @@ mod test { .iter() .map(|v| { let mut vars = ResolvedVariables::with_capacity(2); - vars.merge_index(0, ResolvedVariable::Subject(Subject::Blank(v.clone()))); + vars.merge_index(0, ResolvedVariable::Subject(Subject::Blank(*v))); vars }) .collect(), Box::new(case.left.iter().map(|v| { let mut vars = ResolvedVariables::with_capacity(2); - vars.merge_index(1, ResolvedVariable::Subject(Subject::Blank(v.clone()))); + vars.merge_index(1, ResolvedVariable::Subject(Subject::Blank(*v))); Ok(vars) })), VecDeque::new(), @@ -1424,10 +1501,10 @@ mod test { .map(|v| { let mut vars = ResolvedVariables::with_capacity(2); if let Some(val) = v.get(0) { - vars.merge_index(0, ResolvedVariable::Subject(Subject::Blank(val.clone()))); + vars.merge_index(0, ResolvedVariable::Subject(Subject::Blank(*val))); } if let Some(val) = v.get(1) { - vars.merge_index(1, ResolvedVariable::Subject(Subject::Blank(val.clone()))); + vars.merge_index(1, ResolvedVariable::Subject(Subject::Blank(*val))); } vars }) @@ -1439,12 +1516,12 @@ mod test { #[test] fn triple_pattern_iter_compute_io() { - let t_subject = Subject::Blank("s".to_string()); + let t_subject = Subject::Blank(0u128); let t_predicate = state::Node { namespace: 0u128, value: "whatever".to_string(), }; - let t_object = Object::Blank("o".to_string()); + let t_object = Object::Blank(1u128); let mut variables = ResolvedVariables::with_capacity(6); variables.merge_index(1, ResolvedVariable::Subject(t_subject.clone())); @@ -1523,7 +1600,7 @@ mod test { predicate: PatternValue::Variable(4), object: PatternValue::Variable(5), expects: Some(( - (Some(Subject::Blank("o".to_string())), None, None), + (Some(Subject::Blank(1u128)), None, None), (false, false), (None, Some(4), Some(5)), )), diff --git a/contracts/okp4-cognitarium/src/querier/mapper.rs b/contracts/okp4-cognitarium/src/querier/mapper.rs index c7785b52..df6c7796 100644 --- a/contracts/okp4-cognitarium/src/querier/mapper.rs +++ b/contracts/okp4-cognitarium/src/querier/mapper.rs @@ -1,48 +1,10 @@ -use crate::msg::{Literal, Node, IRI}; +use crate::msg::{Literal, IRI}; use crate::state; -use crate::state::{NamespaceResolver, Object, Predicate, Subject}; -use cosmwasm_std::{StdError, StdResult, Storage}; +use crate::state::{NamespaceResolver, Object}; +use cosmwasm_std::{StdResult, Storage}; use okp4_rdf::uri::{expand_uri, explode_iri}; use std::collections::HashMap; -pub fn node_as_subject( - ns_resolver: &mut NamespaceResolver, - storage: &dyn Storage, - prefixes: &HashMap, - node: Node, -) -> StdResult { - Ok(match node { - Node::NamedNode(iri) => Subject::Named(iri_as_node(ns_resolver, storage, prefixes, iri)?), - Node::BlankNode(blank) => Subject::Blank(blank), - }) -} - -pub fn node_as_predicate( - ns_resolver: &mut NamespaceResolver, - storage: &dyn Storage, - prefixes: &HashMap, - node: Node, -) -> StdResult { - match node { - Node::NamedNode(iri) => iri_as_node(ns_resolver, storage, prefixes, iri), - Node::BlankNode(_) => Err(StdError::generic_err( - "Predicate pattern must be a named node", - )), - } -} - -pub fn node_as_object( - ns_resolver: &mut NamespaceResolver, - storage: &dyn Storage, - prefixes: &HashMap, - node: Node, -) -> StdResult { - Ok(match node { - Node::NamedNode(iri) => Object::Named(iri_as_node(ns_resolver, storage, prefixes, iri)?), - Node::BlankNode(blank) => Object::Blank(blank), - }) -} - pub fn literal_as_object( ns_resolver: &mut NamespaceResolver, storage: &dyn Storage, diff --git a/contracts/okp4-cognitarium/src/querier/mod.rs b/contracts/okp4-cognitarium/src/querier/mod.rs index 4f068b7a..fe87d846 100644 --- a/contracts/okp4-cognitarium/src/querier/mod.rs +++ b/contracts/okp4-cognitarium/src/querier/mod.rs @@ -6,3 +6,4 @@ mod variable; pub use engine::*; pub use plan_builder::*; +pub use variable::ResolvedVariables; diff --git a/contracts/okp4-cognitarium/src/querier/plan.rs b/contracts/okp4-cognitarium/src/querier/plan.rs index 41579240..00bb2f15 100644 --- a/contracts/okp4-cognitarium/src/querier/plan.rs +++ b/contracts/okp4-cognitarium/src/querier/plan.rs @@ -22,18 +22,16 @@ pub enum PlanVariable { impl QueryPlan { /// 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)| match it { - PlanVariable::Basic(name) => { - if name == var_name { - return Some(index); - } - None - } - PlanVariable::BlankNode(_) => None, - }) + self.variables.iter().enumerate().find_map(|(index, it)| { + matches!(it, PlanVariable::Basic(name) if name == var_name).then_some(index) + }) + } + + /// Resolve the index corresponding to blank node name. + pub fn get_bnode_index(&self, bnode_name: &str) -> Option { + self.variables.iter().enumerate().find_map(|(index, it)| { + matches!(it, PlanVariable::BlankNode(name) if name == bnode_name).then_some(index) + }) } } @@ -155,16 +153,14 @@ mod tests { child: Box::new(QueryNode::ForLoopJoin { left: Box::new(QueryNode::CartesianProductJoin { left: Box::new(QueryNode::TriplePattern { - subject: PatternValue::Constant(Subject::Blank( - "_".to_string(), - )), - predicate: PatternValue::Variable(4usize), + subject: PatternValue::BlankVariable(4usize), + predicate: PatternValue::Variable(5usize), object: PatternValue::Variable(0usize), }), right: Box::new(QueryNode::TriplePattern { subject: PatternValue::Variable(3usize), predicate: PatternValue::Variable(1usize), - object: PatternValue::Constant(Object::Blank("_".to_string())), + object: PatternValue::BlankVariable(4usize), }), }), right: Box::new(QueryNode::TriplePattern { @@ -175,7 +171,7 @@ mod tests { }), }), }, - BTreeSet::from([0usize, 1usize, 2usize, 3usize, 4usize]), + BTreeSet::from([0usize, 1usize, 2usize, 3usize, 4usize, 5usize]), ), ]; diff --git a/contracts/okp4-cognitarium/src/querier/plan_builder.rs b/contracts/okp4-cognitarium/src/querier/plan_builder.rs index 52da74a1..0d36e466 100644 --- a/contracts/okp4-cognitarium/src/querier/plan_builder.rs +++ b/contracts/okp4-cognitarium/src/querier/plan_builder.rs @@ -1,11 +1,11 @@ use crate::msg::{ - Node, SimpleWhereCondition, TriplePattern, VarOrNode, VarOrNodeOrLiteral, WhereClause, - WhereCondition, + Node, SimpleWhereCondition, TriplePattern, VarOrNamedNode, VarOrNode, VarOrNodeOrLiteral, + WhereClause, WhereCondition, }; -use crate::querier::mapper::{iri_as_node, literal_as_object, node_as_predicate}; +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 cosmwasm_std::{StdResult, Storage}; use std::collections::HashMap; pub struct PlanBuilder<'a> { @@ -53,32 +53,27 @@ impl<'a> PlanBuilder<'a> { }) .collect::>>()?; - Self::build_from_bgp(bgp) - .map(|mut node| { - if let Some(skip) = self.skip { - node = QueryNode::Skip { - child: Box::new(node), - first: skip, - } - } - node - }) - .map(|mut node| { - if let Some(limit) = self.limit { - node = QueryNode::Limit { - child: Box::new(node), - first: limit, - } - } - node - }) - .map(|node| QueryPlan { - entrypoint: node, - variables: self.variables.clone(), - }) + let mut node = Self::build_from_bgp(bgp); + + if let Some(skip) = self.skip { + node = QueryNode::Skip { + child: Box::new(node), + first: skip, + } + } + if let Some(limit) = self.limit { + node = QueryNode::Limit { + child: Box::new(node), + first: limit, + } + } + Ok(QueryPlan { + entrypoint: node, + variables: self.variables.clone(), + }) } - fn build_from_bgp(bgp: Vec) -> StdResult { + fn build_from_bgp(bgp: Vec) -> QueryNode { bgp.into_iter() .reduce(|left: QueryNode, right: QueryNode| -> QueryNode { if left @@ -97,10 +92,9 @@ impl<'a> PlanBuilder<'a> { right: Box::new(right), } }) - .map_or_else( - || Err(StdError::generic_err("Empty basic graph pattern")), - Ok, - ) + .unwrap_or(QueryNode::Noop { + bound_variables: vec![], + }) } fn build_triple_pattern(&mut self, pattern: &TriplePattern) -> StdResult { @@ -152,14 +146,17 @@ impl<'a> PlanBuilder<'a> { }) } - fn build_predicate_pattern(&mut self, value: VarOrNode) -> StdResult> { + fn build_predicate_pattern( + &mut self, + value: VarOrNamedNode, + ) -> StdResult> { Ok(match value { - VarOrNode::Variable(v) => PatternValue::Variable(self.resolve_basic_variable(v)), - VarOrNode::Node(n) => PatternValue::Constant(node_as_predicate( + 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, - n, + iri, )?), }) } @@ -317,7 +314,7 @@ mod test { ( TriplePattern { subject: VarOrNode::Variable("s".to_string()), - predicate: VarOrNode::Variable("p".to_string()), + predicate: VarOrNamedNode::Variable("p".to_string()), object: VarOrNodeOrLiteral::Variable("o".to_string()), }, Ok(QueryNode::TriplePattern { @@ -329,9 +326,9 @@ mod test { ( TriplePattern { subject: VarOrNode::Node(Node::BlankNode("1".to_string())), - predicate: VarOrNode::Node(Node::NamedNode(IRI::Full( + predicate: VarOrNamedNode::NamedNode(IRI::Full( "http://okp4.space/hasTitle".to_string(), - ))), + )), object: VarOrNodeOrLiteral::Node(Node::BlankNode("2".to_string())), }, Ok(QueryNode::TriplePattern { @@ -348,7 +345,7 @@ mod test { subject: VarOrNode::Node(Node::NamedNode(IRI::Full( "http://okp4.space/123456789".to_string(), ))), - predicate: VarOrNode::Variable("p".to_string()), + predicate: VarOrNamedNode::Variable("p".to_string()), object: VarOrNodeOrLiteral::Node(Node::NamedNode(IRI::Full( "http://okp4.space/1234567892".to_string(), ))), @@ -368,7 +365,7 @@ mod test { ( TriplePattern { subject: VarOrNode::Variable("p".to_string()), - predicate: VarOrNode::Variable("s".to_string()), + predicate: VarOrNamedNode::Variable("s".to_string()), object: VarOrNodeOrLiteral::Literal(Literal::Simple("simple".to_string())), }, Ok(QueryNode::TriplePattern { @@ -382,7 +379,7 @@ mod test { ( TriplePattern { subject: VarOrNode::Variable("s".to_string()), - predicate: VarOrNode::Variable("p".to_string()), + predicate: VarOrNamedNode::Variable("p".to_string()), object: VarOrNodeOrLiteral::Literal(Literal::LanguageTaggedString { value: "tagged".to_string(), language: "en".to_string(), @@ -400,7 +397,7 @@ mod test { ( TriplePattern { subject: VarOrNode::Variable("s".to_string()), - predicate: VarOrNode::Variable("p".to_string()), + predicate: VarOrNamedNode::Variable("p".to_string()), object: VarOrNodeOrLiteral::Literal(Literal::TypedValue { value: "typed".to_string(), datatype: IRI::Full("http://okp4.space/type".to_string()), @@ -423,7 +420,7 @@ mod test { subject: VarOrNode::Node(Node::NamedNode(IRI::Full( "notexisting#outch".to_string(), ))), - predicate: VarOrNode::Variable("p".to_string()), + predicate: VarOrNamedNode::Variable("p".to_string()), object: VarOrNodeOrLiteral::Variable("o".to_string()), }, Ok(QueryNode::Noop { @@ -433,9 +430,9 @@ mod test { ( TriplePattern { subject: VarOrNode::Variable("s".to_string()), - predicate: VarOrNode::Node(Node::NamedNode(IRI::Full( + predicate: VarOrNamedNode::NamedNode(IRI::Full( "notexisting#outch".to_string(), - ))), + )), object: VarOrNodeOrLiteral::Variable("o".to_string()), }, Ok(QueryNode::Noop { @@ -445,7 +442,7 @@ mod test { ( TriplePattern { subject: VarOrNode::Variable("s".to_string()), - predicate: VarOrNode::Variable("p".to_string()), + predicate: VarOrNamedNode::Variable("p".to_string()), object: VarOrNodeOrLiteral::Node(Node::NamedNode(IRI::Full( "notexisting#outch".to_string(), ))), @@ -483,19 +480,12 @@ mod test { None, None, vec![], - Err(StdError::generic_err("Empty basic graph pattern")), - ), - ( - None, - None, - vec![TriplePattern { - subject: VarOrNode::Variable("subject".to_string()), - predicate: VarOrNode::Node(Node::BlankNode("_".to_string())), - object: VarOrNodeOrLiteral::Variable("object".to_string()), - }], - Err(StdError::generic_err( - "Predicate pattern must be a named node", - )), + Ok(QueryPlan { + entrypoint: QueryNode::Noop { + bound_variables: vec![], + }, + variables: vec![], + }), ), ( None, @@ -504,7 +494,7 @@ mod test { subject: VarOrNode::Node(Node::NamedNode(IRI::Full( "notexisting#outch".to_string(), ))), - predicate: VarOrNode::Variable("predicate".to_string()), + predicate: VarOrNamedNode::Variable("predicate".to_string()), object: VarOrNodeOrLiteral::Variable("object".to_string()), }], Ok(QueryPlan { @@ -522,7 +512,7 @@ mod test { None, vec![TriplePattern { subject: VarOrNode::Variable("subject".to_string()), - predicate: VarOrNode::Variable("predicate".to_string()), + predicate: VarOrNamedNode::Variable("predicate".to_string()), object: VarOrNodeOrLiteral::Variable("object".to_string()), }], Ok(QueryPlan { @@ -543,7 +533,7 @@ mod test { None, vec![TriplePattern { subject: VarOrNode::Variable("subject".to_string()), - predicate: VarOrNode::Variable("predicate".to_string()), + predicate: VarOrNamedNode::Variable("predicate".to_string()), object: VarOrNodeOrLiteral::Variable("object".to_string()), }], Ok(QueryPlan { @@ -567,7 +557,7 @@ mod test { Some(20usize), vec![TriplePattern { subject: VarOrNode::Variable("subject".to_string()), - predicate: VarOrNode::Variable("predicate".to_string()), + predicate: VarOrNamedNode::Variable("predicate".to_string()), object: VarOrNodeOrLiteral::Variable("object".to_string()), }], Ok(QueryPlan { @@ -591,7 +581,7 @@ mod test { Some(50usize), vec![TriplePattern { subject: VarOrNode::Variable("subject".to_string()), - predicate: VarOrNode::Variable("predicate".to_string()), + predicate: VarOrNamedNode::Variable("predicate".to_string()), object: VarOrNodeOrLiteral::Variable("object".to_string()), }], Ok(QueryPlan { @@ -619,17 +609,17 @@ mod test { vec![ TriplePattern { subject: VarOrNode::Variable("var1".to_string()), - predicate: VarOrNode::Variable("var2".to_string()), + predicate: VarOrNamedNode::Variable("var2".to_string()), object: VarOrNodeOrLiteral::Variable("var3".to_string()), }, TriplePattern { subject: VarOrNode::Variable("var4".to_string()), - predicate: VarOrNode::Variable("var5".to_string()), + predicate: VarOrNamedNode::Variable("var5".to_string()), object: VarOrNodeOrLiteral::Variable("var6".to_string()), }, TriplePattern { subject: VarOrNode::Variable("var1".to_string()), - predicate: VarOrNode::Variable("var5".to_string()), + predicate: VarOrNamedNode::Variable("var5".to_string()), object: VarOrNodeOrLiteral::Node(Node::BlankNode("blank".to_string())), }, ], @@ -670,12 +660,12 @@ mod test { vec![ TriplePattern { subject: VarOrNode::Node(Node::BlankNode("1".to_string())), - predicate: VarOrNode::Variable("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: VarOrNode::Variable("1".to_string()), + predicate: VarOrNamedNode::Variable("1".to_string()), object: VarOrNodeOrLiteral::Variable("2".to_string()), }, ], diff --git a/contracts/okp4-cognitarium/src/querier/variable.rs b/contracts/okp4-cognitarium/src/querier/variable.rs index d9a37f47..538a159b 100644 --- a/contracts/okp4-cognitarium/src/querier/variable.rs +++ b/contracts/okp4-cognitarium/src/querier/variable.rs @@ -1,6 +1,7 @@ use crate::msg::{Value, IRI}; use crate::state::{Literal, Object, Predicate, Subject}; use cosmwasm_std::StdResult; +use okp4_rdf::normalize::IdentifierIssuer; #[derive(Eq, PartialEq, Debug, Clone)] pub enum ResolvedVariable { @@ -16,7 +17,7 @@ impl ResolvedVariable { ResolvedVariable::Predicate(p) => Subject::Named(p.clone()), ResolvedVariable::Object(o) => match o { Object::Named(node) => Subject::Named(node.clone()), - Object::Blank(node) => Subject::Blank(node.clone()), + Object::Blank(node) => Subject::Blank(*node), Object::Literal(_) => None?, }, }) @@ -41,14 +42,14 @@ impl ResolvedVariable { Some(match self { ResolvedVariable::Subject(s) => match s { Subject::Named(node) => Object::Named(node.clone()), - Subject::Blank(node) => Object::Blank(node.clone()), + Subject::Blank(node) => Object::Blank(*node), }, ResolvedVariable::Predicate(p) => Object::Named(p.clone()), ResolvedVariable::Object(o) => o.clone(), }) } - pub fn as_value(&self, ns_fn: &mut F) -> StdResult + pub fn as_value(&self, ns_fn: &mut F, id_issuer: &mut IdentifierIssuer) -> StdResult where F: FnMut(u128) -> StdResult, { @@ -58,7 +59,7 @@ impl ResolvedVariable { value: IRI::Full(iri), })?, Subject::Blank(blank) => Value::BlankNode { - value: blank.to_string(), + value: id_issuer.get_str_or_issue(blank.to_string()), }, }, ResolvedVariable::Predicate(predicate) => { @@ -71,7 +72,7 @@ impl ResolvedVariable { value: IRI::Full(named.as_iri(ns_fn)?), }, Object::Blank(blank) => Value::BlankNode { - value: blank.to_string(), + value: id_issuer.get_str_or_issue(blank.to_string()), }, Object::Literal(literal) => match literal { Literal::Simple { value } => Value::Literal { @@ -155,9 +156,9 @@ mod tests { fn conversions() { let cases: Vec<(Option, Option, Option)> = vec![ ( - Some(Subject::Blank("_".to_string())), + Some(Subject::Blank(0u128)), None, - Some(Object::Blank("_".to_string())), + Some(Object::Blank(0u128)), ), ( Some(Subject::Named(Node { @@ -226,9 +227,9 @@ mod tests { }), ), ( - ResolvedVariable::Subject(Subject::Blank("_".to_string())), + ResolvedVariable::Subject(Subject::Blank(0u128)), Ok(Value::BlankNode { - value: "_".to_string(), + value: "b0".to_string(), }), ), ( @@ -250,9 +251,9 @@ mod tests { }), ), ( - ResolvedVariable::Object(Object::Blank("_".to_string())), + ResolvedVariable::Object(Object::Blank(0u128)), Ok(Value::BlankNode { - value: "_".to_string(), + value: "b0".to_string(), }), ), ( @@ -292,61 +293,38 @@ mod tests { ), ]; + let mut id_issuer = IdentifierIssuer::new("b", 0u128); for (var, expected) in cases { - assert_eq!(var.as_value(&mut ns), expected) + assert_eq!(var.as_value(&mut ns, &mut id_issuer), expected) } } #[test] fn merged_variables() { let mut vars1 = ResolvedVariables::with_capacity(3); - vars1.merge_index( - 0, - ResolvedVariable::Object(Object::Blank("foo".to_string())), - ); - vars1.merge_index( - 2, - ResolvedVariable::Object(Object::Blank("bar".to_string())), - ); + vars1.merge_index(0, ResolvedVariable::Object(Object::Blank(0u128))); + vars1.merge_index(2, ResolvedVariable::Object(Object::Blank(1u128))); let mut vars2 = ResolvedVariables::with_capacity(3); - vars2.merge_index( - 1, - ResolvedVariable::Object(Object::Blank("pop".to_string())), - ); - vars2.merge_index( - 2, - ResolvedVariable::Object(Object::Blank("bar".to_string())), - ); + vars2.merge_index(1, ResolvedVariable::Object(Object::Blank(2u128))); + vars2.merge_index(2, ResolvedVariable::Object(Object::Blank(1u128))); assert_eq!( vars2.get(1), - &Some(ResolvedVariable::Object(Object::Blank("pop".to_string()))) + &Some(ResolvedVariable::Object(Object::Blank(2u128))) ); assert_eq!(vars1.get(1), &None); let mut expected_result = ResolvedVariables::with_capacity(3); - expected_result.merge_index( - 0, - ResolvedVariable::Object(Object::Blank("foo".to_string())), - ); - expected_result.merge_index( - 1, - ResolvedVariable::Object(Object::Blank("pop".to_string())), - ); - expected_result.merge_index( - 2, - ResolvedVariable::Object(Object::Blank("bar".to_string())), - ); + expected_result.merge_index(0, ResolvedVariable::Object(Object::Blank(0u128))); + expected_result.merge_index(1, ResolvedVariable::Object(Object::Blank(2u128))); + expected_result.merge_index(2, ResolvedVariable::Object(Object::Blank(1u128))); let result = vars1.merge_with(&vars2); assert_eq!(result, Some(expected_result)); let mut vars3 = ResolvedVariables::with_capacity(3); - vars3.merge_index( - 1, - ResolvedVariable::Object(Object::Blank("pop".to_string())), - ); + vars3.merge_index(1, ResolvedVariable::Object(Object::Blank(2u128))); vars3.merge_index( 2, ResolvedVariable::Predicate(Node { diff --git a/contracts/okp4-cognitarium/src/rdf/mapper.rs b/contracts/okp4-cognitarium/src/rdf/mapper.rs index 968bf1c2..95977462 100644 --- a/contracts/okp4-cognitarium/src/rdf/mapper.rs +++ b/contracts/okp4-cognitarium/src/rdf/mapper.rs @@ -20,20 +20,15 @@ impl TryFrom<(msg::Node, &HashMap)> for Subject { } } -impl TryFrom<(msg::Node, &HashMap)> for Property { +impl TryFrom<(msg::IRI, &HashMap)> for Property { type Error = StdError; fn try_from( - (node, prefixes): (msg::Node, &HashMap), + (iri, prefixes): (msg::IRI, &HashMap), ) -> Result { - match node { - msg::Node::NamedNode(msg::IRI::Full(uri)) => Ok(Property(uri)), - msg::Node::NamedNode(msg::IRI::Prefixed(curie)) => { - Ok(Property(expand_uri(&curie, prefixes)?)) - } - _ => Err(StdError::generic_err(format!( - "Unsupported predicate node: {node:?}. Expected URI" - ))), + match iri { + msg::IRI::Full(uri) => Ok(Property(uri)), + msg::IRI::Prefixed(curie) => Ok(Property(expand_uri(&curie, prefixes)?)), } } } diff --git a/contracts/okp4-cognitarium/src/state/blank_nodes.rs b/contracts/okp4-cognitarium/src/state/blank_nodes.rs new file mode 100644 index 00000000..3149784e --- /dev/null +++ b/contracts/okp4-cognitarium/src/state/blank_nodes.rs @@ -0,0 +1,4 @@ +use cw_storage_plus::Item; + +/// A counter serving as blank node unique identifier generator. +pub const BLANK_NODE_IDENTIFIER_COUNTER: Item<'_, u128> = Item::new("blank_node_key"); diff --git a/contracts/okp4-cognitarium/src/state/mod.rs b/contracts/okp4-cognitarium/src/state/mod.rs index e2f548f8..e09022e4 100644 --- a/contracts/okp4-cognitarium/src/state/mod.rs +++ b/contracts/okp4-cognitarium/src/state/mod.rs @@ -1,7 +1,9 @@ +mod blank_nodes; mod namespaces; mod store; mod triples; +pub use blank_nodes::*; pub use namespaces::*; pub use store::*; pub use triples::*; diff --git a/contracts/okp4-cognitarium/src/state/triples.rs b/contracts/okp4-cognitarium/src/state/triples.rs index feabd2dc..f9f395e6 100644 --- a/contracts/okp4-cognitarium/src/state/triples.rs +++ b/contracts/okp4-cognitarium/src/state/triples.rs @@ -77,7 +77,7 @@ impl Subject { key } Subject::Blank(n) => { - let val = n.as_bytes(); + let val = n.to_be_bytes(); let mut key: Vec = Vec::with_capacity(val.len() + 1); key.push(b'b'); key.extend(val); @@ -108,7 +108,7 @@ impl Object { .update(n.value.as_bytes()); } Object::Blank(n) => { - hasher.update(&[b'b']).update(n.as_bytes()); + hasher.update(&[b'b']).update(n.to_be_bytes().as_slice()); } Object::Literal(l) => { hasher.update(&[b'l']); @@ -131,7 +131,8 @@ impl Object { } } -pub type BlankNode = String; +pub const BLANK_NODE_SIZE: usize = 16usize; +pub type BlankNode = u128; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct Node { @@ -191,10 +192,7 @@ mod test { value: "val".to_string(), }), ), - ( - Object::Blank("val1".to_string()), - Object::Blank("val2".to_string()), - ), + (Object::Blank(0u128), Object::Blank(1u128)), ( Object::Literal(Literal::Simple { value: "val1".to_string(), @@ -272,7 +270,7 @@ mod test { }), ), ( - Object::Blank("val".to_string()), + Object::Blank(0u128), Object::Literal(Literal::Simple { value: "val".to_string(), }), diff --git a/contracts/okp4-cognitarium/src/storer/engine.rs b/contracts/okp4-cognitarium/src/storer/engine.rs index c8f480e9..71564f95 100644 --- a/contracts/okp4-cognitarium/src/storer/engine.rs +++ b/contracts/okp4-cognitarium/src/storer/engine.rs @@ -1,9 +1,11 @@ use crate::error::StoreError; use crate::state::{ - triples, Literal, NamespaceBatchService, Node, Object, Store, Subject, Triple, STORE, + triples, Literal, NamespaceBatchService, Node, Object, Store, Subject, Triple, + BLANK_NODE_IDENTIFIER_COUNTER, BLANK_NODE_SIZE, STORE, }; use crate::ContractError; use cosmwasm_std::{StdError, StdResult, Storage, Uint128}; +use okp4_rdf::normalize::IdentifierIssuer; use okp4_rdf::serde::TripleReader; use okp4_rdf::uri::explode_iri; use rio_api::model; @@ -15,6 +17,7 @@ pub struct StoreEngine<'a> { storage: &'a mut dyn Storage, store: Store, ns_batch_svc: NamespaceBatchService, + blank_node_id_issuer: IdentifierIssuer, initial_triple_count: Uint128, initial_byte_size: Uint128, } @@ -22,11 +25,13 @@ pub struct StoreEngine<'a> { impl<'a> StoreEngine<'a> { pub fn new(storage: &'a mut dyn Storage) -> StdResult { let store = STORE.load(storage)?; + let blank_node_id_counter = BLANK_NODE_IDENTIFIER_COUNTER.load(storage)?; let ns_batch_svc = NamespaceBatchService::new(storage)?; Ok(Self { storage, store: store.clone(), ns_batch_svc, + blank_node_id_issuer: IdentifierIssuer::new("", blank_node_id_counter), initial_triple_count: store.stat.triple_count, initial_byte_size: store.stat.byte_size, }) @@ -53,11 +58,15 @@ impl<'a> StoreEngine<'a> { ))?; } - let triple = Self::rio_to_triple(t, &mut |ns_str| { - self.ns_batch_svc - .resolve_or_allocate(self.storage, ns_str) - .map(|ns| ns.key) - })?; + let triple = Self::rio_to_triple( + t, + &mut |ns_str| { + self.ns_batch_svc + .resolve_or_allocate(self.storage, ns_str) + .map(|ns| ns.key) + }, + &mut self.blank_node_id_issuer, + )?; let t_size = Uint128::from(self.triple_size(&triple).map_err(ContractError::Std)? as u128); if t_size > self.store.limits.max_triple_byte_size { Err(StoreError::TripleByteSize( @@ -159,6 +168,8 @@ impl<'a> StoreEngine<'a> { self.store.stat.namespace_count -= Uint128::new(ns_diff.neg() as u128); } + BLANK_NODE_IDENTIFIER_COUNTER.save(self.storage, &self.blank_node_id_issuer.counter)?; + STORE.save(self.storage, &self.store)?; let count_diff = self @@ -173,24 +184,34 @@ impl<'a> StoreEngine<'a> { Ok(count_diff) } - fn rio_to_triple(triple: model::Triple<'_>, ns_fn: &mut F) -> StdResult + fn rio_to_triple( + triple: model::Triple<'_>, + ns_fn: &mut F, + id_issuer: &mut IdentifierIssuer, + ) -> StdResult where F: FnMut(String) -> StdResult, { Ok(Triple { - subject: Self::rio_to_subject(triple.subject, ns_fn)?, + subject: Self::rio_to_subject(triple.subject, ns_fn, id_issuer)?, predicate: Self::rio_to_node(triple.predicate, ns_fn)?, - object: Self::rio_to_object(triple.object, ns_fn)?, + object: Self::rio_to_object(triple.object, ns_fn, id_issuer)?, }) } - fn rio_to_subject(subject: model::Subject<'_>, ns_fn: &mut F) -> StdResult + fn rio_to_subject( + subject: model::Subject<'_>, + ns_fn: &mut F, + id_issuer: &mut IdentifierIssuer, + ) -> StdResult where F: FnMut(String) -> StdResult, { match subject { model::Subject::NamedNode(node) => Self::rio_to_node(node, ns_fn).map(Subject::Named), - model::Subject::BlankNode(node) => Ok(Subject::Blank(node.id.to_string())), + model::Subject::BlankNode(node) => Ok(Subject::Blank( + id_issuer.get_n_or_issue(node.id.to_string()), + )), model::Subject::Triple(_) => Err(StdError::generic_err("RDF star syntax unsupported")), } } @@ -206,12 +227,18 @@ impl<'a> StoreEngine<'a> { }) } - fn rio_to_object(object: Term<'_>, ns_fn: &mut F) -> StdResult + fn rio_to_object( + object: Term<'_>, + ns_fn: &mut F, + id_issuer: &mut IdentifierIssuer, + ) -> StdResult where F: FnMut(String) -> StdResult, { match object { - Term::BlankNode(node) => Ok(Object::Blank(node.id.to_string())), + Term::BlankNode(node) => { + Ok(Object::Blank(id_issuer.get_n_or_issue(node.id.to_string()))) + } Term::NamedNode(node) => Self::rio_to_node(node, ns_fn).map(Object::Named), Term::Literal(literal) => Self::rio_to_literal(literal, ns_fn).map(Object::Literal), Term::Triple(_) => Err(StdError::generic_err("RDF star syntax unsupported")), @@ -248,7 +275,7 @@ impl<'a> StoreEngine<'a> { fn subject_size(&mut self, subject: &Subject) -> StdResult { match subject { Subject::Named(n) => self.node_size(n), - Subject::Blank(n) => Ok(n.len()), + Subject::Blank(_) => Ok(BLANK_NODE_SIZE), } } @@ -266,7 +293,7 @@ impl<'a> StoreEngine<'a> { fn object_size(&mut self, object: &Object) -> StdResult { Ok(match object { - Object::Blank(n) => n.len(), + Object::Blank(_) => BLANK_NODE_SIZE, Object::Named(n) => self.node_size(n)?, Object::Literal(l) => match l { Literal::Simple { value } => value.len(), diff --git a/contracts/okp4-dataverse/src/contract.rs b/contracts/okp4-dataverse/src/contract.rs index 84cfa8da..9504b3f8 100644 --- a/contracts/okp4-dataverse/src/contract.rs +++ b/contracts/okp4-dataverse/src/contract.rs @@ -131,8 +131,8 @@ mod tests { }; use okp4_cognitarium::msg::{ DataFormat, Head, Node, Results, SelectItem, SelectQuery, SelectResponse, - SimpleWhereCondition, TriplePattern, Value, VarOrNode, VarOrNodeOrLiteral, WhereCondition, - IRI, + SimpleWhereCondition, TriplePattern, Value, VarOrNamedNode, VarOrNode, VarOrNodeOrLiteral, + WhereCondition, IRI, }; use std::collections::BTreeMap; @@ -220,7 +220,7 @@ mod tests { subject: VarOrNode::Node(Node::NamedNode(IRI::Full( "http://example.edu/credentials/3732".to_string(), ))), - predicate: VarOrNode::Variable("p".to_string()), + predicate: VarOrNamedNode::Variable("p".to_string()), object: VarOrNodeOrLiteral::Variable("o".to_string()), }) )], diff --git a/contracts/okp4-dataverse/src/registrar/registry.rs b/contracts/okp4-dataverse/src/registrar/registry.rs index 72260733..0045e004 100644 --- a/contracts/okp4-dataverse/src/registrar/registry.rs +++ b/contracts/okp4-dataverse/src/registrar/registry.rs @@ -4,8 +4,8 @@ use crate::state::DATAVERSE; use crate::ContractError; use cosmwasm_std::{DepsMut, StdResult, Storage, WasmMsg}; use okp4_cognitarium::msg::{ - DataFormat, Node, SelectItem, SelectQuery, SimpleWhereCondition, TriplePattern, VarOrNode, - VarOrNodeOrLiteral, WhereCondition, IRI, + DataFormat, Node, SelectItem, SelectQuery, SimpleWhereCondition, TriplePattern, VarOrNamedNode, + VarOrNode, VarOrNodeOrLiteral, WhereCondition, IRI, }; use okp4_cognitarium_client::CognitariumClient; @@ -42,7 +42,7 @@ impl ClaimRegistrar { subject: VarOrNode::Node(Node::NamedNode(IRI::Full( credential.id.to_string(), ))), - predicate: VarOrNode::Variable("p".to_string()), + predicate: VarOrNamedNode::Variable("p".to_string()), object: VarOrNodeOrLiteral::Variable("o".to_string()), }, ))], diff --git a/docs/okp4-cognitarium.md b/docs/okp4-cognitarium.md index 9d65c31e..9ef84626 100644 --- a/docs/okp4-cognitarium.md +++ b/docs/okp4-cognitarium.md @@ -411,16 +411,16 @@ Only the smart contract owner (i.e. the address who instantiated it) is authoriz Delete the data (RDF triples) from the store matching the patterns defined by the provided query. For non-existing triples it acts as no-op. -Example: `json { "prefixes": [ { "prefix": "foaf", "namespace": "http://xmlns.com/foaf/0.1/" } ], "delete": [ { "subject": { "variable": "s" }, "predicate": { "variable": "p" }, "object": { "variable": "o" } } ], "where": [ { "simple": { "triplePattern": { "subject": { "variable": "s" }, "predicate": { "node": { "namedNode": {"prefixed": "foaf:givenName"} } }, "object": { "literal": { "simple": "Myrddin" } } } } }, { "simple": { "triplePattern": { "subject": { "variable": "s" }, "predicate": { "variable": "p" }, "object": { "variable": "o" } } } } ] ` +Example: `json { "prefixes": [ { "prefix": "foaf", "namespace": "http://xmlns.com/foaf/0.1/" } ], "delete": [ { "subject": { "variable": "s" }, "predicate": { "variable": "p" }, "object": { "variable": "o" } } ], "where": [ { "simple": { "triplePattern": { "subject": { "variable": "s" }, "predicate": { "namedNode": {"prefixed": "foaf:givenName"} }, "object": { "literal": { "simple": "Myrddin" } } } } }, { "simple": { "triplePattern": { "subject": { "variable": "s" }, "predicate": { "variable": "p" }, "object": { "variable": "o" } } } } ] ` 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<[TriplePattern](#triplepattern)>**. Specifies the specific triple patterns 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, 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. | ## QueryMsg @@ -536,11 +536,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<[TriplePattern](#triplepattern)>**. 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, 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. | ### DataFormat @@ -747,6 +747,26 @@ Contains usage information about the triple store. | `namespace_count` | _(Required.) _ **[Uint128](#uint128)**. The total number of IRI namespace present in the store. | | `triple_count` | _(Required.) _ **[Uint128](#uint128)**. The total number of triple present in the store. | +### TripleConstructTemplate + +Represents a triple template to be forged for a construct query. + +| property | description | +| ----------- | ----------------------------------------------------------------------------------------------- | +| `object` | _(Required.) _ **[VarOrNodeOrLiteral](#varornodeorliteral)**. The object of the triple pattern. | +| `predicate` | _(Required.) _ **[VarOrNamedNode](#varornamednode)**. The predicate of the triple pattern. | +| `subject` | _(Required.) _ **[VarOrNode](#varornode)**. The subject of the triple pattern. | + +### TripleDeleteTemplate + +Represents a triple template to be deleted. + +| property | description | +| ----------- | --------------------------------------------------------------------------------------------------------- | +| `object` | _(Required.) _ **[VarOrNamedNodeOrLiteral](#varornamednodeorliteral)**. The object of the triple pattern. | +| `predicate` | _(Required.) _ **[VarOrNamedNode](#varornamednode)**. The predicate of the triple pattern. | +| `subject` | _(Required.) _ **[VarOrNamedNode](#varornamednode)**. The subject of the triple pattern. | + ### TriplePattern Represents a triple pattern in a [SimpleWhereCondition]. @@ -754,7 +774,7 @@ Represents a triple pattern in a [SimpleWhereCondition]. | property | description | | ----------- | ----------------------------------------------------------------------------------------------- | | `object` | _(Required.) _ **[VarOrNodeOrLiteral](#varornodeorliteral)**. The object of the triple pattern. | -| `predicate` | _(Required.) _ **[VarOrNode](#varornode)**. The predicate of the triple pattern. | +| `predicate` | _(Required.) _ **[VarOrNamedNode](#varornamednode)**. The predicate of the triple pattern. | | `subject` | _(Required.) _ **[VarOrNode](#varornode)**. The subject of the triple pattern. | ### Turtle @@ -809,6 +829,16 @@ Represents either a variable or a named node (IRI). | [Variable](#variable) | **object**. A variable. | | [NamedNode](#namednode) | **object**. An RDF [IRI](https://www.w3.org/TR/rdf11-concepts/#dfn-iri). | +### VarOrNamedNodeOrLiteral + +Represents either a variable, a named node or a literal. + +| variant | description | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Variable](#variable) | **object**. A variable. | +| [NamedNode](#namednode) | **object**. An RDF [IRI](https://www.w3.org/TR/rdf11-concepts/#dfn-iri). | +| [Literal](#literal) | **object**. An RDF [literal](https://www.w3.org/TR/rdf11-concepts/#dfn-literal), i.e. a simple literal, a language-tagged string or a typed value. | + ### VarOrNode Represents either a variable or a node. @@ -846,4 +876,4 @@ Represents a condition in a [WhereClause]. --- -_Rendered by [Fadroma](https://fadroma.tech) ([@fadroma/schema 1.1.0](https://www.npmjs.com/package/@fadroma/schema)) from `okp4-cognitarium.json` (`b3bea598ea8fda42`)_ +_Rendered by [Fadroma](https://fadroma.tech) ([@fadroma/schema 1.1.0](https://www.npmjs.com/package/@fadroma/schema)) from `okp4-cognitarium.json` (`7aa73b0502a6138b`)_ diff --git a/packages/okp4-rdf/src/normalize.rs b/packages/okp4-rdf/src/normalize.rs index cede5069..37c0dc91 100644 --- a/packages/okp4-rdf/src/normalize.rs +++ b/packages/okp4-rdf/src/normalize.rs @@ -38,7 +38,10 @@ impl<'a> Normalizer<'a> { blank_node_to_quads: HashMap::new(), hash_to_blank_nodes: BTreeMap::new(), blank_node_to_hash: HashMap::new(), - canonical_issuer: IdentifierIssuer::new(Self::CANONICAL_BLANK_NODES_IDENTIFIER_PREFIX), + canonical_issuer: IdentifierIssuer::new( + Self::CANONICAL_BLANK_NODES_IDENTIFIER_PREFIX, + 0u128, + ), } } @@ -69,7 +72,7 @@ impl<'a> Normalizer<'a> { self.hash_to_blank_nodes = BTreeMap::new(); self.blank_node_to_hash = HashMap::new(); self.canonical_issuer = - IdentifierIssuer::new(Self::CANONICAL_BLANK_NODES_IDENTIFIER_PREFIX); + IdentifierIssuer::new(Self::CANONICAL_BLANK_NODES_IDENTIFIER_PREFIX, 0u128); } fn track_blank_nodes(&mut self, dataset: &[Quad<'a>]) { @@ -124,7 +127,7 @@ impl<'a> Normalizer<'a> { for (hash, node) in unique_nodes { self.hash_to_blank_nodes.remove(&hash); - self.canonical_issuer.get_or_issue(&node); + self.canonical_issuer.get_or_issue(node); } Ok(()) @@ -146,8 +149,8 @@ impl<'a> Normalizer<'a> { } let mut scoped_issuer = - IdentifierIssuer::new(Self::TEMPORARY_BLANK_NODES_IDENTIFIER_PREFIX); - scoped_issuer.get_or_issue(node); + IdentifierIssuer::new(Self::TEMPORARY_BLANK_NODES_IDENTIFIER_PREFIX, 0u128); + scoped_issuer.get_or_issue(node.clone()); let (n_degree_hash, issuer) = self.compute_n_degree_hash(&mut scoped_issuer, node)?; @@ -157,7 +160,7 @@ impl<'a> Normalizer<'a> { hash_path_list.sort_by(|left, right| left.0.cmp(&right.0)); for (_, issuer) in hash_path_list { for node in issuer.issue_log { - self.canonical_issuer.get_or_issue(&node); + self.canonical_issuer.get_or_issue(node); } } } @@ -212,7 +215,7 @@ impl<'a> Normalizer<'a> { let mut hasher = sha2::Sha256::new(); let mut chosen_issuer = - IdentifierIssuer::new(Self::TEMPORARY_BLANK_NODES_IDENTIFIER_PREFIX); + IdentifierIssuer::new(Self::TEMPORARY_BLANK_NODES_IDENTIFIER_PREFIX, 0u128); let mut chosen_path = String::new(); for (hash, related) in hashes { @@ -230,7 +233,7 @@ impl<'a> Normalizer<'a> { if !issuer.issued(&related) { recursion_list.push(related.clone()); } - path.push_str(&issuer.get_or_issue(&related)); + path.push_str(&issuer.get_str_or_issue(related)); } } @@ -242,7 +245,7 @@ impl<'a> Normalizer<'a> { for related in recursion_list { let (result, mut issuer) = self.compute_n_degree_hash(&mut issuer, &related)?; path.push_str("_:"); - path.push_str(&issuer.get_or_issue(&related)); + path.push_str(&issuer.get_str_or_issue(related)); path.push('<'); path.push_str(&result); path.push('>'); @@ -327,38 +330,47 @@ impl<'a> Default for Normalizer<'a> { /// Canonical blank node identifier issuer, specified by: https://www.w3.org/TR/rdf-canon/#issue-identifier. #[derive(Clone, Eq, PartialEq, Debug)] -struct IdentifierIssuer { +pub struct IdentifierIssuer { prefix: String, - counter: u64, - issued: HashMap, + pub counter: u128, + issued: HashMap, issue_log: Vec, } impl IdentifierIssuer { - pub fn new(prefix: &str) -> Self { + pub fn new(prefix: &str, counter_offset: u128) -> Self { Self { prefix: prefix.to_string(), - counter: 0, + counter: counter_offset, issued: HashMap::new(), issue_log: Vec::new(), } } - pub fn get_or_issue(&mut self, identifier: &str) -> String { - match self.issued.entry(identifier.to_string()) { + pub fn get_or_issue(&mut self, identifier: String) -> (u128, String) { + match self.issued.entry(identifier.clone()) { Entry::Occupied(e) => e.get().clone(), Entry::Vacant(e) => { - let generated_id = format!("{}{}", self.prefix, self.counter); + let n = self.counter; + let str = format!("{}{}", self.prefix, n); self.counter += 1; - self.issue_log.push(identifier.to_string()); - e.insert(generated_id).clone() + self.issue_log.push(identifier); + e.insert((n, str)).clone() } } } + pub fn get_n_or_issue(&mut self, identifier: String) -> u128 { + self.get_or_issue(identifier).0 + } + + pub fn get_str_or_issue(&mut self, identifier: String) -> String { + self.get_or_issue(identifier).1 + } + pub fn get(&self, identifier: &str) -> Option<&str> { - self.issued.get(identifier).map(String::as_str) + self.issued.get(identifier).map(|(_, str)| str.as_str()) } pub fn issued(&self, identifier: &str) -> bool {