diff --git a/contracts/okp4-cognitarium/src/contract.rs b/contracts/okp4-cognitarium/src/contract.rs index 00cd04a5..cd02b3b3 100644 --- a/contracts/okp4-cognitarium/src/contract.rs +++ b/contracts/okp4-cognitarium/src/contract.rs @@ -1,4 +1,3 @@ -use crate::contract::execute::insert; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ @@ -38,38 +37,108 @@ pub fn execute( ) -> Result { match msg { ExecuteMsg::InsertData { format, data } => { - insert(deps, info, format.unwrap_or(DataFormat::Turtle), data) + execute::insert(deps, info, format.unwrap_or_default(), data) } - _ => Err(StdError::generic_err("Not implemented").into()), + ExecuteMsg::DeleteData { + prefixes, + delete, + r#where, + } => execute::delete(deps, info, prefixes, delete, r#where), } } pub mod execute { use super::*; - use crate::msg::DataFormat; - use crate::rdf::TripleReader; - use crate::storer::TripleStorer; + use crate::msg::{ + DataFormat, Prefix, SelectItem, SimpleWhereCondition, TriplePattern, WhereClause, + WhereCondition, + }; + use crate::querier::{PlanBuilder, QueryEngine}; + use crate::rdf::{Atom, PrefixMap, TripleReader}; + use crate::storer::StoreEngine; + use std::collections::HashSet; use std::io::BufReader; + pub fn verify_owner(deps: &DepsMut<'_>, info: &MessageInfo) -> Result<(), ContractError> { + if STORE.load(deps.storage)?.owner != info.sender { + Err(ContractError::Unauthorized) + } else { + Ok(()) + } + } + pub fn insert( deps: DepsMut<'_>, info: MessageInfo, format: DataFormat, data: Binary, ) -> Result { - if STORE.load(deps.storage)?.owner != info.sender { - Err(ContractError::Unauthorized)?; - } + verify_owner(&deps, &info)?; let buf = BufReader::new(data.as_slice()); let mut reader = TripleReader::new(&format, buf); - let mut storer = TripleStorer::new(deps.storage)?; + let mut storer = StoreEngine::new(deps.storage)?; let count = storer.store_all(&mut reader)?; Ok(Response::new() .add_attribute("action", "insert") .add_attribute("triple_count", count)) } + + pub fn delete( + deps: DepsMut<'_>, + info: MessageInfo, + prefixes: Vec, + delete: Vec, + r#where: WhereClause, + ) -> Result { + verify_owner(&deps, &info)?; + + let patterns: Vec = if delete.is_empty() { + r#where + .iter() + .map(|c| match c { + WhereCondition::Simple(SimpleWhereCondition::TriplePattern(tp)) => { + Ok(tp.clone()) + } + }) + .collect::>()? + } else { + delete + }; + let variables = patterns + .iter() + .flat_map(TriplePattern::variables) + .collect::>() + .into_iter() + .map(SelectItem::Variable) + .collect(); + let prefix_map = ::from(prefixes).into_inner(); + let plan = PlanBuilder::new(deps.storage, &prefix_map).build_plan(&r#where)?; + + let response = QueryEngine::new(deps.storage).select(plan, variables)?; + let atoms: Vec = if response.results.bindings.is_empty() { + vec![] + } else { + response + .results + .bindings + .iter() + .flat_map(|row| { + patterns + .iter() + .map(|pattern| pattern.resolve(row, &prefix_map)) + }) + .collect::>()? + }; + + let mut store = StoreEngine::new(deps.storage)?; + let count = store.delete_all(&atoms)?; + + Ok(Response::new() + .add_attribute("action", "delete") + .add_attribute("triple_count", count)) + } } #[cfg_attr(not(feature = "library"), entry_point)] @@ -77,11 +146,9 @@ pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::Store => to_binary(&query::store(deps)?), QueryMsg::Select { query } => to_binary(&query::select(deps, query)?), - QueryMsg::Describe { query, format } => to_binary(&query::describe( - deps, - query, - format.unwrap_or(DataFormat::default()), - )?), + QueryMsg::Describe { query, format } => { + to_binary(&query::describe(deps, query, format.unwrap_or_default())?) + } QueryMsg::Construct { query, format } => to_binary(&query::construct( deps, query, @@ -95,12 +162,12 @@ pub mod query { use super::*; use crate::msg::{ - ConstructQuery, DescribeQuery, DescribeResponse, Node, Prefix, SelectItem, SelectQuery, + ConstructQuery, DescribeQuery, DescribeResponse, Node, SelectItem, SelectQuery, SelectResponse, SimpleWhereCondition, StoreResponse, TriplePattern, Value, VarOrNamedNode, VarOrNode, VarOrNodeOrLiteral, WhereCondition, }; use crate::querier::{PlanBuilder, QueryEngine}; - use crate::rdf::{self, Atom, TripleWriter}; + use crate::rdf::{self, Atom, PrefixMap, TripleWriter}; pub fn store(deps: Deps<'_>) -> StdResult { STORE.load(deps.storage).map(Into::into) @@ -120,7 +187,8 @@ pub mod query { Err(StdError::generic_err("Maximum query limit exceeded"))?; } - let plan = PlanBuilder::new(deps.storage, &query.prefixes) + let prefix_map = PrefixMap::from(query.prefixes).into_inner(); + let plan = PlanBuilder::new(deps.storage, &prefix_map) .with_limit(count as usize) .build_plan(&query.r#where)?; @@ -138,7 +206,7 @@ pub mod query { bindings: &BTreeMap, ) -> Result { vars.get(index) - .and_then(|it| bindings.get(it.as_str())) + .and_then(|it| bindings.get(it)) .cloned() .ok_or_else(|| { StdError::generic_err(format!( @@ -185,8 +253,8 @@ pub mod query { ))], ), }; - - let plan = PlanBuilder::new(deps.storage, &query.prefixes) + let prefix_map = ::from(query.prefixes).into_inner(); + let plan = PlanBuilder::new(deps.storage, &prefix_map) .with_limit(store.limits.max_query_limit as usize) .build_plan(&r#where)?; @@ -205,11 +273,10 @@ pub mod query { let mut writer = TripleWriter::new(&format, out); for r in &bindings { - let prefixes: &[Prefix] = &query.prefixes; let atom = &Atom { - subject: rdf::Subject::try_from((get_value(0, &vars, r)?, prefixes))?, - property: rdf::Property::try_from((get_value(1, &vars, r)?, prefixes))?, - value: rdf::Value::try_from((get_value(2, &vars, r)?, prefixes))?, + subject: rdf::Subject::try_from((get_value(0, &vars, r)?, &prefix_map))?, + property: rdf::Property::try_from((get_value(1, &vars, r)?, &prefix_map))?, + value: rdf::Value::try_from((get_value(2, &vars, r)?, &prefix_map))?, }; let triple = atom.into(); @@ -243,7 +310,7 @@ pub mod query { mod tests { use super::*; use crate::error::StoreError; - use crate::msg::ExecuteMsg::InsertData; + use crate::msg::ExecuteMsg::{DeleteData, InsertData}; use crate::msg::Node::NamedNode; use crate::msg::QueryMsg::Construct; use crate::msg::SimpleWhereCondition::TriplePattern; @@ -344,9 +411,7 @@ mod tests { deps.as_mut(), mock_env(), info.clone(), - InstantiateMsg { - limits: StoreLimitsInput::default(), - }, + InstantiateMsg::default(), ) .unwrap(); @@ -439,9 +504,7 @@ mod tests { deps.as_mut(), mock_env(), mock_info("owner", &[]), - InstantiateMsg { - limits: StoreLimitsInput::default(), - }, + InstantiateMsg::default(), ) .unwrap(); @@ -567,6 +630,293 @@ mod tests { } } + #[test] + fn proper_delete() { + let id = "https://ontology.okp4.space/dataverse/dataspace/metadata/dcf48417-01c5-4b43-9bc7-49e54c028473"; + let cases = vec![ + ( + DeleteData { + prefixes: vec![], + delete: vec![msg::TriplePattern { + subject: VarOrNode::Node(NamedNode(Full( + "https://ontology.okp4.space/dataverse/dataspace/metadata/unknown" + .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![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( + "https://ontology.okp4.space/core/hasTopic".to_string(), + ))), + object: VarOrNodeOrLiteral::Node(NamedNode(Full( + "https://ontology.okp4.space/thesaurus/topic/Test".to_string(), + ))), + }))], + }, + 0, + ), + ( + DeleteData { + prefixes: vec![], + delete: vec![msg::TriplePattern { + subject: VarOrNode::Node(NamedNode(Full(id.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![WhereCondition::Simple(TriplePattern(msg::TriplePattern { + subject: VarOrNode::Node(NamedNode(Full(id.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(), + ))), + }))], + }, + 1, + ), + ( + 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::TriplePattern { + subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), + predicate: VarOrNode::Node(NamedNode(Prefixed( + "core:hasTopic".to_string(), + ))), + object: VarOrNodeOrLiteral::Node(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(), + ))), + object: VarOrNodeOrLiteral::Node(NamedNode(Prefixed( + "thesaurus:Test".to_string(), + ))), + }))], + }, + 1, + ), + ( + 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::TriplePattern { + subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), + predicate: VarOrNode::Node(NamedNode(Prefixed( + "core:hasTopic".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(Prefixed( + "core:hasTopic".to_string(), + ))), + object: VarOrNodeOrLiteral::Variable("o".to_string()), + }))], + }, + 1, + ), + ( + 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()), + }], + r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { + subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), + predicate: VarOrNode::Variable("p".to_string()), + object: VarOrNodeOrLiteral::Variable("o".to_string()), + }))], + }, + 11, + ), + ( + DeleteData { + prefixes: vec![], + delete: vec![], + r#where: vec![WhereCondition::Simple(TriplePattern(msg::TriplePattern { + subject: VarOrNode::Node(NamedNode(Full(id.to_string()))), + predicate: VarOrNode::Variable("p".to_string()), + object: VarOrNodeOrLiteral::Variable("o".to_string()), + }))], + }, + 11, + ), + ]; + + for case in cases { + let mut deps = mock_dependencies(); + + let info = mock_info("owner", &[]); + instantiate( + deps.as_mut(), + mock_env(), + info.clone(), + InstantiateMsg::default(), + ) + .unwrap(); + + execute( + deps.as_mut(), + mock_env(), + info.clone(), + InsertData { + format: Some(DataFormat::RDFXml), + data: read_test_data("sample.rdf.xml"), + }, + ) + .unwrap(); + + let res = execute(deps.as_mut(), mock_env(), info, case.0); + + assert!(res.is_ok()); + assert_eq!( + res.unwrap().attributes, + vec![ + Attribute::new("action", "delete"), + Attribute::new("triple_count", case.1.to_string()) + ] + ); + } + } + + #[test] + fn invalid_delete() { + struct TC { + command: ExecuteMsg, + expected: ContractError, + } + let cases = vec![ + TC { + command: DeleteData { + prefixes: vec![], + delete: vec![msg::TriplePattern { + subject: VarOrNode::Node(NamedNode(Prefixed("foo:bar".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![WhereCondition::Simple(TriplePattern(msg::TriplePattern { + subject: VarOrNode::Node(NamedNode(Prefixed("foo:bar".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(), + ))), + }))], + }, + expected: StdError::generic_err("Prefix not found: foo").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::Variable("z".to_string()), + object: VarOrNodeOrLiteral::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()), + 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 { + let mut deps = mock_dependencies(); + + let info = mock_info("owner", &[]); + instantiate( + deps.as_mut(), + mock_env(), + info.clone(), + InstantiateMsg::default(), + ) + .unwrap(); + + execute( + deps.as_mut(), + mock_env(), + info.clone(), + InsertData { + format: Some(DataFormat::RDFXml), + data: read_test_data("sample.rdf.xml"), + }, + ) + .unwrap(); + + let res = execute(deps.as_mut(), mock_env(), info, case.command); + + assert!(res.is_err()); + assert_eq!(res.unwrap_err(), case.expected); + } + } + #[test] fn proper_store() { let mut deps = mock_dependencies(); @@ -621,7 +971,7 @@ mod tests { let mut bytes: Vec = Vec::new(); File::open( - Path::new(env::var("CARGO_MANIFEST_DIR").unwrap().as_str()) + Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()) .join("testdata") .join(file), ) @@ -804,9 +1154,7 @@ mod tests { deps.as_mut(), mock_env(), info.clone(), - InstantiateMsg { - limits: StoreLimitsInput::default(), - }, + InstantiateMsg::default(), ) .unwrap(); @@ -875,9 +1223,7 @@ mod tests { }))], limit: None, }, - Err(StdError::generic_err( - "Malformed prefixed IRI: prefix not found", - )), + Err(StdError::generic_err("Prefix not found: invalid")), ), ( SelectQuery { @@ -1060,9 +1406,7 @@ mod tests { deps.as_mut(), mock_env(), info.clone(), - InstantiateMsg { - limits: StoreLimitsInput::default(), - }, + InstantiateMsg::default(), ) .unwrap(); @@ -1132,9 +1476,7 @@ mod tests { deps.as_mut(), mock_env(), info.clone(), - InstantiateMsg { - limits: StoreLimitsInput::default(), - }, + InstantiateMsg::default(), ) .unwrap(); @@ -1199,9 +1541,7 @@ mod tests { deps.as_mut(), mock_env(), info.clone(), - InstantiateMsg { - limits: StoreLimitsInput::default(), - }, + InstantiateMsg::default(), ) .unwrap(); @@ -1266,9 +1606,7 @@ mod tests { deps.as_mut(), mock_env(), info.clone(), - InstantiateMsg { - limits: StoreLimitsInput::default(), - }, + InstantiateMsg::default(), ) .unwrap(); @@ -1337,9 +1675,7 @@ mod tests { deps.as_mut(), mock_env(), info.clone(), - InstantiateMsg { - limits: StoreLimitsInput::default(), - }, + InstantiateMsg::default(), ) .unwrap(); diff --git a/contracts/okp4-cognitarium/src/msg.rs b/contracts/okp4-cognitarium/src/msg.rs index 2635f7af..50e617be 100644 --- a/contracts/okp4-cognitarium/src/msg.rs +++ b/contracts/okp4-cognitarium/src/msg.rs @@ -5,6 +5,7 @@ use std::collections::BTreeMap; /// Instantiate message #[cw_serde] +#[derive(Default)] pub struct InstantiateMsg { /// Limitations regarding store usage. #[serde(default)] @@ -66,11 +67,12 @@ pub enum ExecuteMsg { DeleteData { /// The prefixes used in the operation. prefixes: Vec, - /// The items to delete. + /// Specifies the specific triple patterns to delete. + /// If nothing is provided, the patterns from the `where` clause are used for deletion. delete: Vec, - /// The WHERE clause to apply. - /// If not provided, all the RDF triples are considered. - r#where: Option, + /// Defines the patterns that data (RDF triples) should match in order for it to be + /// considered for deletion. + r#where: WhereClause, }, } @@ -430,7 +432,7 @@ pub struct ConstructQuery { } /// # Prefix -/// Represents a prefix in a [SelectQuery]. A prefix is a shortcut for a namespace used in the query. +/// Represents a prefix, i.e. a shortcut for a namespace used in a query. #[cw_serde] pub struct Prefix { /// The prefix. @@ -483,6 +485,27 @@ pub struct TriplePattern { 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] @@ -562,7 +585,12 @@ pub enum Node { #[cfg(test)] mod tests { - use crate::msg::{InstantiateMsg, StoreLimitsInput}; + 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 cosmwasm_std::Uint128; use schemars::_serde_json; @@ -597,4 +625,77 @@ 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 8cb5cf44..e7346c7b 100644 --- a/contracts/okp4-cognitarium/src/querier/engine.rs +++ b/contracts/okp4-cognitarium/src/querier/engine.rs @@ -27,7 +27,7 @@ impl<'a> QueryEngine<'a> { SelectItem::Variable(v) => v, }) .map(|name| -> StdResult<(String, usize)> { - match plan.get_var_index(name.as_str()) { + match plan.get_var_index(name) { Some(index) => Ok((name.clone(), index)), None => Err(StdError::generic_err( "Selected variable not found in query", @@ -443,7 +443,7 @@ mod test { use crate::rdf::TripleReader; use crate::state; use crate::state::{Literal, Store, StoreStat, NAMESPACE_KEY_INCREMENT, STORE}; - use crate::storer::TripleStorer; + use crate::storer::StoreEngine; use cosmwasm_std::testing::mock_dependencies; use cosmwasm_std::{Addr, Uint128}; use std::env; @@ -455,7 +455,7 @@ mod test { let mut bytes: Vec = Vec::new(); File::open( - Path::new(env::var("CARGO_MANIFEST_DIR").unwrap().as_str()) + Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()) .join("testdata") .join(file), ) @@ -481,7 +481,7 @@ mod test { let data = read_test_data("sample.rdf.xml"); let buf = BufReader::new(data.as_slice()); let mut reader = TripleReader::new(&DataFormat::RDFXml, buf); - let mut storer = TripleStorer::new(storage).unwrap(); + let mut storer = StoreEngine::new(storage).unwrap(); let count = storer.store_all(&mut reader).unwrap(); assert_eq!(count, Uint128::new(40u128)); diff --git a/contracts/okp4-cognitarium/src/querier/plan.rs b/contracts/okp4-cognitarium/src/querier/plan.rs index decdf269..e2d86fc5 100644 --- a/contracts/okp4-cognitarium/src/querier/plan.rs +++ b/contracts/okp4-cognitarium/src/querier/plan.rs @@ -16,7 +16,7 @@ pub struct QueryPlan { impl QueryPlan { pub fn get_var_index(&self, var_name: &str) -> Option { self.variables.iter().enumerate().find_map(|(index, it)| { - if it.as_str() == var_name { + if it == var_name { return Some(index); } None diff --git a/contracts/okp4-cognitarium/src/querier/plan_builder.rs b/contracts/okp4-cognitarium/src/querier/plan_builder.rs index aab92221..4c3c6fff 100644 --- a/contracts/okp4-cognitarium/src/querier/plan_builder.rs +++ b/contracts/okp4-cognitarium/src/querier/plan_builder.rs @@ -1,8 +1,9 @@ use crate::msg::{ - Literal, Node, Prefix, SimpleWhereCondition, TriplePattern, VarOrNode, VarOrNodeOrLiteral, - WhereClause, WhereCondition, IRI, + Literal, Node, SimpleWhereCondition, TriplePattern, VarOrNode, VarOrNodeOrLiteral, WhereClause, + WhereCondition, IRI, }; use crate::querier::plan::{PatternValue, QueryNode, QueryPlan}; +use crate::rdf::expand_uri; use crate::state::{namespaces, Object, Predicate, Subject}; use crate::{rdf, state}; use cosmwasm_std::{StdError, StdResult, Storage}; @@ -10,17 +11,17 @@ use std::collections::HashMap; pub struct PlanBuilder<'a> { storage: &'a dyn Storage, - prefixes: HashMap, + prefixes: &'a HashMap, variables: Vec, limit: Option, skip: Option, } impl<'a> PlanBuilder<'a> { - pub fn new(storage: &'a dyn Storage, prefixes: &[Prefix]) -> Self { + pub fn new(storage: &'a dyn Storage, prefixes: &'a HashMap) -> Self { Self { storage, - prefixes: Self::make_prefixes(prefixes), + prefixes, variables: Vec::new(), skip: None, limit: None, @@ -156,34 +157,10 @@ impl<'a> PlanBuilder<'a> { fn build_named_node(&mut self, value: IRI) -> StdResult { match value { - IRI::Prefixed(prefixed) => prefixed - .rfind(':') - .map_or_else( - || { - Err(StdError::generic_err( - "Malformed prefixed IRI: no prefix delimiter found", - )) - }, - Ok, - ) - .and_then(|index| { - self.prefixes - .get(&prefixed.as_str()[..index]) - .map(|resolved_prefix| { - [resolved_prefix, &prefixed.as_str()[index + 1..]].join("") - }) - .map_or_else( - || { - Err(StdError::generic_err( - "Malformed prefixed IRI: prefix not found", - )) - }, - Ok, - ) - }), + IRI::Prefixed(prefixed) => expand_uri(&prefixed, self.prefixes), IRI::Full(full) => Ok(full), } - .and_then(|iri| rdf::explode_iri(iri.as_str())) + .and_then(|iri| rdf::explode_iri(&iri)) .and_then(|(ns_key, v)| { namespaces() .load(self.storage, ns_key) @@ -202,18 +179,13 @@ impl<'a> PlanBuilder<'a> { self.variables.push(v); self.variables.len() - 1 } - - fn make_prefixes(as_list: &[Prefix]) -> HashMap { - as_list.iter().fold(HashMap::new(), |mut map, prefix| { - map.insert(prefix.prefix.clone(), prefix.namespace.clone()); - map - }) - } } #[cfg(test)] mod test { use super::*; + use crate::msg::Prefix; + use crate::rdf::PrefixMap; use crate::state::Namespace; use cosmwasm_std::testing::mock_dependencies; @@ -273,14 +245,16 @@ mod test { let deps = mock_dependencies(); for case in cases { - let builder = PlanBuilder::new(&deps.storage, &case.0); + let prefixes = &PrefixMap::from(case.0).into_inner(); + let builder = PlanBuilder::new(&deps.storage, prefixes); assert_eq!(builder.skip, None); assert_eq!(builder.limit, None); assert_eq!(builder.variables, Vec::::new()); - assert_eq!(builder.prefixes, case.1); + assert_eq!(builder.prefixes, &case.1); } - let mut builder = PlanBuilder::new(&deps.storage, &[]); + let prefixes = &PrefixMap::default().into_inner(); + let mut builder = PlanBuilder::new(&deps.storage, prefixes); builder = builder.with_skip(20usize).with_limit(50usize); assert_eq!(builder.skip, Some(20usize)); assert_eq!(builder.limit, Some(50usize)); @@ -315,15 +289,11 @@ mod test { ), ( IRI::Prefixed("resource".to_string()), - Err(StdError::generic_err( - "Malformed prefixed IRI: no prefix delimiter found", - )), + Err(StdError::generic_err("Malformed CURIE: resource")), ), ( IRI::Prefixed("okp5:resource".to_string()), - Err(StdError::generic_err( - "Malformed prefixed IRI: prefix not found", - )), + Err(StdError::generic_err("Prefix not found: okp5")), ), ]; @@ -351,19 +321,19 @@ mod test { ) .unwrap(); - let mut builder = PlanBuilder::new( - &deps.storage, - &[ - Prefix { - prefix: "rdf".to_string(), - namespace: "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(), - }, - Prefix { - prefix: "okp4".to_string(), - namespace: "http://okp4.space/".to_string(), - }, - ], - ); + let prefixes = &::from(vec![ + Prefix { + prefix: "rdf".to_string(), + namespace: "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(), + }, + Prefix { + prefix: "okp4".to_string(), + namespace: "http://okp4.space/".to_string(), + }, + ]) + .into_inner(); + let mut builder = PlanBuilder::new(&deps.storage, prefixes); + for case in cases { assert_eq!(builder.build_named_node(case.0), case.1); } @@ -526,7 +496,9 @@ mod test { }, ) .unwrap(); - let mut builder = PlanBuilder::new(&deps.storage, &[]); + let prefixes = &PrefixMap::default().into_inner(); + let mut builder = PlanBuilder::new(&deps.storage, prefixes); + for case in cases { assert_eq!(builder.build_triple_pattern(&case.0), case.1); } @@ -729,7 +701,8 @@ mod test { .unwrap(); for case in cases { - let mut builder = PlanBuilder::new(&deps.storage, &[]); + let prefixes = &PrefixMap::default().into_inner(); + let mut builder = PlanBuilder::new(&deps.storage, prefixes); if let Some(skip) = case.0 { builder = builder.with_skip(skip); } diff --git a/contracts/okp4-cognitarium/src/rdf/atom.rs b/contracts/okp4-cognitarium/src/rdf/atom.rs index ee381b9d..be37d8ee 100644 --- a/contracts/okp4-cognitarium/src/rdf/atom.rs +++ b/contracts/okp4-cognitarium/src/rdf/atom.rs @@ -1,10 +1,10 @@ use cosmwasm_std::StdError; use rio_api::model::{Literal, NamedNode, Triple}; +use std::collections::{BTreeMap, HashMap}; use std::fmt; -use crate::msg::{self, Prefix, IRI}; - -use super::expand_uri; +use crate::msg; +use crate::msg::TriplePattern; #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub enum Subject { @@ -21,7 +21,7 @@ impl fmt::Display for Subject { } #[derive(Eq, PartialEq, Debug, Clone, Hash)] -pub struct Property(String); +pub struct Property(pub String); impl fmt::Display for Property { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -55,13 +55,9 @@ pub struct Atom { pub value: Value, } -impl std::fmt::Display for Atom { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - fmt.write_str(&format!( - "<{}> <{}> '{}'", - self.subject, self.property, self.value - ))?; - Ok(()) +impl fmt::Display for Atom { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "<{}> <{}> '{}'", self.subject, self.property, self.value) } } @@ -73,19 +69,19 @@ impl<'a> From<&'a Atom> for Triple<'a> { } .into(), predicate: NamedNode { - iri: atom.property.0.as_str(), + iri: &atom.property.0, }, object: match &atom.value { Value::NamedNode(s) | Value::BlankNode(s) => NamedNode { iri: s.as_str() }.into(), Value::LiteralSimple(s) => Literal::Simple { value: s.as_str() }.into(), Value::LiteralLang(s, l) => Literal::LanguageTaggedString { - value: s.as_str(), - language: l.as_str(), + value: s, + language: l, } .into(), Value::LiteralDatatype(s, d) => Literal::Typed { - value: s.as_str(), - datatype: NamedNode { iri: d.as_str() }, + value: s, + datatype: NamedNode { iri: d }, } .into(), }, @@ -93,358 +89,554 @@ impl<'a> From<&'a Atom> for Triple<'a> { } } -impl TryFrom<(msg::Value, &[Prefix])> for Subject { - type Error = StdError; - - fn try_from((value, prefixes): (msg::Value, &[Prefix])) -> Result { - match value { - msg::Value::URI { - value: IRI::Full(uri), - } => Ok(Subject::NamedNode(uri)), - msg::Value::URI { - value: IRI::Prefixed(curie), - } => Ok(Subject::NamedNode(expand_uri(&curie, prefixes)?)), - msg::Value::BlankNode { value: id } => Ok(Subject::BlankNode(id)), - _ => Err(StdError::generic_err(format!( - "Unsupported subject value: {value:?}" - ))), - } - } -} - -impl TryFrom<(msg::Value, &[Prefix])> for Property { - type Error = StdError; +impl TriplePattern { + pub fn resolve( + &self, + bindings: &BTreeMap, + prefixes: &HashMap, + ) -> Result { + let subject = match &self.subject { + msg::VarOrNode::Variable(var) => { + let value = bindings.get(var).ok_or_else(|| { + StdError::generic_err(format!("Unbound subject variable: {:?}", var)) + })?; + (value.clone(), prefixes).try_into()? + } + msg::VarOrNode::Node(node) => (node.clone(), prefixes).try_into()?, + }; - fn try_from((value, prefixes): (msg::Value, &[Prefix])) -> Result { - match value { - msg::Value::URI { - value: IRI::Full(uri), - } => Ok(Property(uri)), - msg::Value::URI { - value: IRI::Prefixed(curie), - } => Ok(Property(expand_uri(&curie, prefixes)?)), - _ => Err(StdError::generic_err(format!( - "Unsupported predicate value: {value:?}" - ))), - } - } -} + let property = match &self.predicate { + msg::VarOrNode::Variable(var) => { + let value = bindings.get(var).ok_or_else(|| { + StdError::generic_err(format!("Unbound predicate variable: {:?}", var)) + })?; + (value.clone(), prefixes).try_into()? + } + msg::VarOrNode::Node(node) => (node.clone(), prefixes).try_into()?, + }; -impl TryFrom<(msg::Value, &[Prefix])> for Value { - type Error = StdError; + let value = match &self.object { + msg::VarOrNodeOrLiteral::Variable(var) => { + let value = bindings.get(var).ok_or_else(|| { + StdError::generic_err(format!("Unbound object variable: {:?}", var)) + })?; + (value.clone(), prefixes).try_into()? + } + msg::VarOrNodeOrLiteral::Node(node) => (node.clone(), prefixes).try_into()?, + msg::VarOrNodeOrLiteral::Literal(literal) => (literal.clone(), prefixes).try_into()?, + }; - fn try_from((value, prefixes): (msg::Value, &[Prefix])) -> Result { - match value { - msg::Value::URI { - value: IRI::Full(uri), - } => Ok(Value::NamedNode(uri)), - msg::Value::URI { - value: IRI::Prefixed(curie), - } => Ok(Value::NamedNode(expand_uri(&curie, prefixes)?)), - msg::Value::Literal { - value, - lang: None, - datatype: None, - } => Ok(Value::LiteralSimple(value)), - msg::Value::Literal { - value, - lang: Some(lang), - datatype: None, - } => Ok(Value::LiteralLang(value, lang)), - msg::Value::Literal { - value, - lang: None, - datatype: Some(IRI::Full(uri)), - } => Ok(Value::LiteralDatatype(value, uri)), - msg::Value::Literal { - value, - lang: None, - datatype: Some(IRI::Prefixed(curie)), - } => Ok(Value::LiteralDatatype(value, expand_uri(&curie, prefixes)?)), - msg::Value::BlankNode { value } => Ok(Value::BlankNode(value)), - _ => Err(StdError::generic_err(format!( - "Unsupported object value: {value:?}" - )))?, - } + Ok(Atom { + subject, + property, + value, + }) } } #[cfg(test)] mod tests { use super::*; + use crate::msg::IRI; + use crate::rdf::PrefixMap; #[test] fn proper_display() { - // # Subject - assert_eq!( - format!("{}", Subject::BlankNode("blank".to_string())), - "blank".to_string() - ); - assert_eq!( - format!("{}", Subject::NamedNode("named".to_string())), - "named".to_string() - ); - - // # Property - assert_eq!( - format!("{}", Property("foo".to_string())), - "foo".to_string() - ); - - // # Value - assert_eq!( - format!("{}", Value::NamedNode("named".to_string())), - "named".to_string() - ); - assert_eq!( - format!("{}", Value::BlankNode("blank".to_string())), - "blank".to_string() - ); - assert_eq!( - format!("{}", Value::LiteralSimple("simple".to_string())), - "simple".to_string() - ); - assert_eq!( - format!( - "{}", - Value::LiteralLang("lang".to_string(), "en".to_string()) - ), - "lang@en".to_string() - ); - assert_eq!( - format!( - "{}", - Value::LiteralDatatype("data".to_string(), "uri".to_string()) - ), - "data^^uri".to_string() - ); - - // # Atom - assert_eq!( - format!( - "{}", - Atom { - subject: Subject::NamedNode("subject".to_string()), - property: Property("predicate".to_string()), - value: Value::LiteralLang("object".to_string(), "en".to_string()), - } - ), - " 'object@en'".to_string() - ); + struct TC<'a> { + input: Box, + expected: String, + } + let cases = vec![ + // # Subject + TC { + input: Box::new(Subject::BlankNode("blank".into())), + expected: "blank".into(), + }, + TC { + input: Box::new(Subject::NamedNode("named".into())), + expected: "named".into(), + }, + // # Property + TC { + input: Box::new(Property("foo".into())), + expected: "foo".into(), + }, + // # Value + TC { + input: Box::new(Value::NamedNode("named".into())), + expected: "named".into(), + }, + TC { + input: Box::new(Value::BlankNode("blank".into())), + expected: "blank".into(), + }, + TC { + input: Box::new(Value::LiteralSimple("simple".into())), + expected: "simple".into(), + }, + TC { + input: Box::new(Value::LiteralLang("lang".into(), "en".into())), + expected: "lang@en".into(), + }, + TC { + input: Box::new(Value::LiteralDatatype("data".into(), "uri".into())), + expected: "data^^uri".into(), + }, + // # Atom + TC { + input: Box::new(Atom { + subject: Subject::NamedNode("subject".into()), + property: Property("predicate".into()), + value: Value::LiteralLang("object".into(), "en".into()), + }), + expected: " 'object@en'".into(), + }, + ]; + for tc in cases { + assert_eq!(format!("{}", tc.input), tc.expected); + } } #[test] - fn try_from_subject() { - assert_eq!( + fn triple_pattern_resolve() { + struct TC<'a> { + triple_pattern: TriplePattern, + bindings: &'a BTreeMap, + prefixes: &'a Vec, + expected: Result, + } + let bindings: BTreeMap = BTreeMap::from([ ( + "s1".to_string(), msg::Value::URI { - value: IRI::Full("http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string()), + value: msg::IRI::Full( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .to_string(), + ), }, - vec![].as_slice(), - ) - .try_into(), - Ok(Subject::NamedNode( - "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(), - )) - ); - assert_eq!( - ( - msg::Value::BlankNode { - value: "blank".to_string(), - }, - vec![].as_slice(), - ) - .try_into(), - Ok(Subject::BlankNode("blank".to_string())) - ); - assert_eq!( + ), ( + "s2".to_string(), msg::Value::URI { - value: IRI::Prefixed("rdf:".to_string()), + value: msg::IRI::Prefixed( + "core:dcf48417-01c5-4b43-9bc7-49e54c028473".to_string(), + ), }, - vec![Prefix { - prefix: "rdf".to_string(), - namespace: "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(), - }] - .as_slice(), - ) - .try_into(), - Ok(Subject::NamedNode( - "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(), - )) - ); - assert_eq!( - Subject::try_from(( - msg::Value::Literal { - value: "rdf".to_string(), - lang: None, - datatype: None, + ), + ( + "s3".to_string(), + msg::Value::BlankNode { + value: "_1".to_string(), }, - vec![].as_slice(), - )), - Err(StdError::generic_err( - "Unsupported subject value: Literal { value: \"rdf\", lang: None, datatype: None }" - )) - ); - } - - #[test] - fn try_from_property() { - assert_eq!( + ), ( + "p1".to_string(), msg::Value::URI { - value: IRI::Full("http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string()), + value: msg::IRI::Full( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .to_string(), + ), }, - vec![].as_slice(), - ) - .try_into(), - Ok(Property( - "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string() - )) - ); - assert_eq!( + ), ( + "p2".to_string(), msg::Value::URI { - value: IRI::Prefixed("rdf:".to_string()), - }, - vec![Prefix { - prefix: "rdf".to_string(), - namespace: "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(), - }] - .as_slice(), - ) - .try_into(), - Ok(Property( - "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(), - )) - ); - assert_eq!( - Property::try_from(( - msg::Value::BlankNode { - value: "blank".to_string(), + value: msg::IRI::Prefixed( + "core:dcf48417-01c5-4b43-9bc7-49e54c028473".to_string(), + ), }, - vec![].as_slice(), - )), - Err(StdError::generic_err( - "Unsupported predicate value: BlankNode { value: \"blank\" }" - )) - ); - } - - #[test] - fn try_from_value() { - assert_eq!( + ), ( + "o1".to_string(), msg::Value::URI { - value: IRI::Full("http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string()), + value: msg::IRI::Full( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .to_string(), + ), }, - vec![].as_slice(), - ) - .try_into(), - Ok(Value::NamedNode( - "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string() - )) - ); - assert_eq!( + ), ( + "o2".to_string(), msg::Value::URI { - value: IRI::Prefixed("rdf:".to_string()), + value: msg::IRI::Prefixed( + "core:dcf48417-01c5-4b43-9bc7-49e54c028473".to_string(), + ), }, - vec![Prefix { - prefix: "rdf".to_string(), - namespace: "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(), - }] - .as_slice(), - ) - .try_into(), - Ok(Value::NamedNode( - "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(), - )) - ); - assert_eq!( + ), ( + "o3".to_string(), msg::Value::Literal { - value: "foo".to_string(), - lang: None, + value: "foo".into(), datatype: None, + lang: None, }, - vec![].as_slice(), - ) - .try_into(), - Ok(Value::LiteralSimple("foo".to_string())) - ); - assert_eq!( + ), ( + "o4".to_string(), msg::Value::Literal { - value: "foo".to_string(), - lang: Some("en".to_string()), - datatype: None, + value: "foo".into(), + datatype: Some(msg::IRI::Prefixed("owl:foo".into())), + lang: None, }, - vec![].as_slice(), - ) - .try_into(), - Ok(Value::LiteralLang("foo".to_string(), "en".to_string())) - ); - assert_eq!( + ), ( + "o5".to_string(), msg::Value::Literal { - value: "foo".to_string(), - lang: None, - datatype: Some(IRI::Full( - "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string() + value: "foo".into(), + datatype: None, + lang: Some("en".into()), + }, + ), + ]); + let prefixes = vec![ + msg::Prefix { + prefix: "core".to_string(), + namespace: "https://ontology.okp4.space/core/".to_string(), + }, + msg::Prefix { + prefix: "owl".to_string(), + namespace: "http://www.w3.org/2002/07/owl#".to_string(), + }, + ]; + let cases = vec![ + // Bindings + TC { + triple_pattern: TriplePattern { + subject: msg::VarOrNode::Variable("s1".into()), + predicate: msg::VarOrNode::Variable("p1".into()), + object: msg::VarOrNodeOrLiteral::Variable("o1".into()), + }, + bindings: &bindings, + prefixes: &prefixes, + expected: Ok(Atom { + subject: Subject::NamedNode( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .into(), + ), + property: Property( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .into(), + ), + value: Value::NamedNode( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .into(), + ), + }), + }, + TC { + triple_pattern: TriplePattern { + subject: msg::VarOrNode::Variable("s2".into()), + predicate: msg::VarOrNode::Variable("p2".into()), + object: msg::VarOrNodeOrLiteral::Variable("o2".into()), + }, + bindings: &bindings, + prefixes: &prefixes, + expected: Ok(Atom { + subject: Subject::NamedNode( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .into(), + ), + property: Property( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .into(), + ), + value: Value::NamedNode( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .into(), + ), + }), + }, + TC { + triple_pattern: TriplePattern { + subject: msg::VarOrNode::Variable("s3".into()), + predicate: msg::VarOrNode::Variable("p1".into()), + object: msg::VarOrNodeOrLiteral::Variable("o3".into()), + }, + bindings: &bindings, + prefixes: &prefixes, + expected: Ok(Atom { + subject: Subject::BlankNode("_1".into()), + property: Property( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .into(), + ), + value: Value::LiteralSimple("foo".into()), + }), + }, + TC { + triple_pattern: TriplePattern { + subject: msg::VarOrNode::Variable("s1".into()), + predicate: msg::VarOrNode::Variable("p1".into()), + object: msg::VarOrNodeOrLiteral::Variable("o4".into()), + }, + bindings: &bindings, + prefixes: &prefixes, + expected: Ok(Atom { + subject: Subject::NamedNode( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .into(), + ), + property: Property( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .into(), + ), + value: Value::LiteralDatatype( + "foo".into(), + "http://www.w3.org/2002/07/owl#foo".into(), + ), + }), + }, + TC { + triple_pattern: TriplePattern { + subject: msg::VarOrNode::Variable("s1".into()), + predicate: msg::VarOrNode::Variable("p1".into()), + object: msg::VarOrNodeOrLiteral::Variable("o5".into()), + }, + bindings: &bindings, + prefixes: &prefixes, + expected: Ok(Atom { + subject: Subject::NamedNode( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .into(), + ), + property: Property( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .into(), + ), + value: Value::LiteralLang("foo".into(), "en".into()), + }), + }, + // Plain values + TC { + triple_pattern: TriplePattern { + subject: msg::VarOrNode::Node(msg::Node::NamedNode(msg::IRI::Prefixed( + "core:dcf48417-01c5-4b43-9bc7-49e54c028473".into(), + ))), + predicate: msg::VarOrNode::Node(msg::Node::NamedNode(msg::IRI::Prefixed( + "owl:foo".into(), + ))), + object: msg::VarOrNodeOrLiteral::Node(msg::Node::NamedNode( + msg::IRI::Prefixed("core:dcf48417-01c5-4b43-9bc7-49e54c028473".into()), )), }, - vec![].as_slice(), - ) - .try_into(), - Ok(Value::LiteralDatatype( - "foo".to_string(), - "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string() - )) - ); - assert_eq!( - ( - msg::Value::Literal { - value: "foo".to_string(), - lang: None, - datatype: Some(IRI::Prefixed("rdf:".to_string())), + bindings: &bindings, + prefixes: &prefixes, + expected: Ok(Atom { + subject: Subject::NamedNode( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .into(), + ), + property: Property("http://www.w3.org/2002/07/owl#foo".into()), + value: Value::NamedNode( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .into(), + ), + }), + }, + TC { + triple_pattern: TriplePattern { + subject: msg::VarOrNode::Node(msg::Node::NamedNode(msg::IRI::Full( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .into(), + ))), + predicate: msg::VarOrNode::Node(msg::Node::NamedNode(msg::IRI::Full( + "http://www.w3.org/2002/07/owl#foo".into(), + ))), + object: msg::VarOrNodeOrLiteral::Node(msg::Node::NamedNode(msg::IRI::Full( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .into(), + ))), }, - vec![Prefix { - prefix: "rdf".to_string(), - namespace: "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(), - }] - .as_slice(), - ) - .try_into(), - Ok(Value::LiteralDatatype( - "foo".to_string(), - "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string() - )) - ); - assert_eq!( - ( - msg::Value::BlankNode { - value: "foo".to_string() + bindings: &bindings, + prefixes: &prefixes, + expected: Ok(Atom { + subject: Subject::NamedNode( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .into(), + ), + property: Property("http://www.w3.org/2002/07/owl#foo".into()), + value: Value::NamedNode( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .into(), + ), + }), + }, + TC { + triple_pattern: TriplePattern { + subject: msg::VarOrNode::Node(msg::Node::BlankNode("_1".into())), + predicate: msg::VarOrNode::Node(msg::Node::NamedNode(msg::IRI::Full( + "http://www.w3.org/2002/07/owl#foo".into(), + ))), + object: msg::VarOrNodeOrLiteral::Node(msg::Node::BlankNode("_2".into())), }, - vec![].as_slice(), - ) - .try_into(), - Ok(Value::BlankNode("foo".to_string())) - ); - assert_eq!( - Value::try_from(( - msg::Value::Literal { - value: "blank".to_string(), - lang: Some("en".to_string()), - datatype: Some(IRI::Full( - "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string() + bindings: &bindings, + prefixes: &prefixes, + expected: Ok(Atom { + subject: Subject::BlankNode("_1".into()), + property: Property("http://www.w3.org/2002/07/owl#foo".into()), + value: Value::BlankNode("_2".into()), + }), + }, + TC { + triple_pattern: TriplePattern { + subject: msg::VarOrNode::Node(msg::Node::NamedNode(msg::IRI::Prefixed( + "core:dcf48417-01c5-4b43-9bc7-49e54c028473".into(), + ))), + predicate: msg::VarOrNode::Node(msg::Node::NamedNode(msg::IRI::Prefixed( + "owl:foo".into(), + ))), + object: msg::VarOrNodeOrLiteral::Literal(msg::Literal::Simple("foo".into())), + }, + bindings: &bindings, + prefixes: &prefixes, + expected: Ok(Atom { + subject: Subject::NamedNode( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .into(), + ), + property: Property("http://www.w3.org/2002/07/owl#foo".into()), + value: Value::LiteralSimple("foo".into()), + }), + }, + TC { + triple_pattern: TriplePattern { + subject: msg::VarOrNode::Node(msg::Node::NamedNode(msg::IRI::Prefixed( + "core:dcf48417-01c5-4b43-9bc7-49e54c028473".into(), + ))), + predicate: msg::VarOrNode::Node(msg::Node::NamedNode(msg::IRI::Prefixed( + "owl:foo".into(), + ))), + object: msg::VarOrNodeOrLiteral::Literal(msg::Literal::LanguageTaggedString { + value: "foo".into(), + language: "en".into(), + }), + }, + bindings: &bindings, + prefixes: &prefixes, + expected: Ok(Atom { + subject: Subject::NamedNode( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .into(), + ), + property: Property("http://www.w3.org/2002/07/owl#foo".into()), + value: Value::LiteralLang("foo".into(), "en".into()), + }), + }, + TC { + triple_pattern: TriplePattern { + subject: msg::VarOrNode::Node(msg::Node::NamedNode(msg::IRI::Prefixed( + "core:dcf48417-01c5-4b43-9bc7-49e54c028473".into(), + ))), + predicate: msg::VarOrNode::Node(msg::Node::NamedNode(msg::IRI::Prefixed( + "owl:foo".into(), + ))), + object: msg::VarOrNodeOrLiteral::Literal(msg::Literal::TypedValue { + value: "foo".into(), + datatype: IRI::Prefixed("owl:type".into()), + }), + }, + bindings: &bindings, + prefixes: &prefixes, + expected: Ok(Atom { + subject: Subject::NamedNode( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .into(), + ), + property: Property("http://www.w3.org/2002/07/owl#foo".into()), + value: Value::LiteralDatatype( + "foo".into(), + "http://www.w3.org/2002/07/owl#type".into(), + ), + }), + }, + TC { + triple_pattern: TriplePattern { + subject: msg::VarOrNode::Node(msg::Node::NamedNode(msg::IRI::Prefixed( + "core:dcf48417-01c5-4b43-9bc7-49e54c028473".into(), + ))), + predicate: msg::VarOrNode::Node(msg::Node::NamedNode(msg::IRI::Prefixed( + "owl:foo".into(), + ))), + object: msg::VarOrNodeOrLiteral::Literal(msg::Literal::TypedValue { + value: "foo".into(), + datatype: IRI::Full("http://www.w3.org/2002/07/owl#type".into()), + }), + }, + bindings: &bindings, + prefixes: &prefixes, + expected: Ok(Atom { + subject: Subject::NamedNode( + "https://ontology.okp4.space/core/dcf48417-01c5-4b43-9bc7-49e54c028473" + .into(), + ), + property: Property("http://www.w3.org/2002/07/owl#foo".into()), + value: Value::LiteralDatatype( + "foo".into(), + "http://www.w3.org/2002/07/owl#type".into(), + ), + }), + }, + // Error + TC { + triple_pattern: TriplePattern { + subject: msg::VarOrNode::Variable("unknown".into()), + predicate: msg::VarOrNode::Variable("p1".into()), + object: msg::VarOrNodeOrLiteral::Variable("o1".into()), + }, + bindings: &bindings, + prefixes: &prefixes, + expected: Err(StdError::generic_err( + "Unbound subject variable: \"unknown\"", + )), + }, + TC { + triple_pattern: TriplePattern { + subject: msg::VarOrNode::Variable("s1".into()), + predicate: msg::VarOrNode::Variable("unknown".into()), + object: msg::VarOrNodeOrLiteral::Variable("o1".into()), + }, + bindings: &bindings, + prefixes: &prefixes, + expected: Err(StdError::generic_err( + "Unbound predicate variable: \"unknown\"", + )), + }, + TC { + triple_pattern: TriplePattern { + subject: msg::VarOrNode::Variable("s1".into()), + predicate: msg::VarOrNode::Variable("p1".into()), + object: msg::VarOrNodeOrLiteral::Variable("unknown".into()), + }, + bindings: &bindings, + prefixes: &prefixes, + expected: Err(StdError::generic_err( + "Unbound object variable: \"unknown\"", + )), + }, + TC { + triple_pattern: TriplePattern { + subject: msg::VarOrNode::Node(msg::Node::BlankNode("_1".into())), + predicate: msg::VarOrNode::Node(msg::Node::BlankNode("_2".into())), + object: msg::VarOrNodeOrLiteral::Node(msg::Node::NamedNode( + msg::IRI::Prefixed("core:dcf48417-01c5-4b43-9bc7-49e54c028473".into()), )), }, - vec![].as_slice(), - )), - Err(StdError::generic_err( - "Unsupported object value: Literal { value: \"blank\", lang: Some(\"en\"), datatype: Some(Full(\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\")) }" - )) - ); + bindings: &bindings, + prefixes: &prefixes, + expected: Err(StdError::generic_err( + "Unsupported predicate node: BlankNode(\"_2\"). Expected URI", + )), + }, + ]; + + for tc in cases { + assert_eq!( + tc.triple_pattern.resolve( + tc.bindings, + &::from(tc.prefixes.clone()).into_inner() + ), + tc.expected + ); + } } } diff --git a/contracts/okp4-cognitarium/src/rdf/mapper.rs b/contracts/okp4-cognitarium/src/rdf/mapper.rs new file mode 100644 index 00000000..6fa94418 --- /dev/null +++ b/contracts/okp4-cognitarium/src/rdf/mapper.rs @@ -0,0 +1,407 @@ +use crate::msg; +use crate::rdf::{expand_uri, Property, Subject, Value}; +use cosmwasm_std::StdError; +use std::collections::HashMap; + +impl TryFrom<(msg::Value, &HashMap)> for Subject { + type Error = StdError; + + fn try_from( + (value, prefixes): (msg::Value, &HashMap), + ) -> Result { + match value { + msg::Value::URI { + value: msg::IRI::Full(uri), + } => Ok(Subject::NamedNode(uri)), + msg::Value::URI { + value: msg::IRI::Prefixed(curie), + } => Ok(Subject::NamedNode(expand_uri(&curie, prefixes)?)), + msg::Value::BlankNode { value: id } => Ok(Subject::BlankNode(id)), + _ => Err(StdError::generic_err(format!( + "Unsupported subject value: {value:?}. Expected URI or BlankNode", + ))), + } + } +} + +impl TryFrom<(msg::Value, &HashMap)> for Property { + type Error = StdError; + + fn try_from( + (value, prefixes): (msg::Value, &HashMap), + ) -> Result { + match value { + msg::Value::URI { + value: msg::IRI::Full(uri), + } => Ok(Property(uri)), + msg::Value::URI { + value: msg::IRI::Prefixed(curie), + } => Ok(Property(expand_uri(&curie, prefixes)?)), + _ => Err(StdError::generic_err(format!( + "Unsupported predicate value: {value:?}. Expected URI" + ))), + } + } +} + +impl TryFrom<(msg::Value, &HashMap)> for Value { + type Error = StdError; + + fn try_from( + (value, prefixes): (msg::Value, &HashMap), + ) -> Result { + match value { + msg::Value::URI { + value: msg::IRI::Full(uri), + } => Ok(Value::NamedNode(uri)), + msg::Value::URI { + value: msg::IRI::Prefixed(curie), + } => Ok(Value::NamedNode(expand_uri(&curie, prefixes)?)), + msg::Value::Literal { + value, + lang: None, + datatype: None, + } => Ok(Value::LiteralSimple(value)), + msg::Value::Literal { + value, + lang: Some(lang), + datatype: None, + } => Ok(Value::LiteralLang(value, lang)), + msg::Value::Literal { + value, + lang: None, + datatype: Some(msg::IRI::Full(uri)), + } => Ok(Value::LiteralDatatype(value, uri)), + msg::Value::Literal { + value, + lang: None, + datatype: Some(msg::IRI::Prefixed(curie)), + } => Ok(Value::LiteralDatatype(value, expand_uri(&curie, prefixes)?)), + msg::Value::BlankNode { value } => Ok(Value::BlankNode(value)), + _ => Err(StdError::generic_err(format!( + "Unsupported object value: {value:?}. Expected URI, BlankNode or Literal" + )))?, + } + } +} + +impl TryFrom<(msg::Node, &HashMap)> for Subject { + type Error = StdError; + + fn try_from( + (node, prefixes): (msg::Node, &HashMap), + ) -> Result { + match node { + msg::Node::BlankNode(id) => Ok(Subject::BlankNode(id)), + msg::Node::NamedNode(msg::IRI::Full(uri)) => Ok(Subject::NamedNode(uri)), + msg::Node::NamedNode(msg::IRI::Prefixed(curie)) => { + Ok(Subject::NamedNode(expand_uri(&curie, prefixes)?)) + } + } + } +} + +impl TryFrom<(msg::Node, &HashMap)> for Property { + type Error = StdError; + + fn try_from( + (node, prefixes): (msg::Node, &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" + ))), + } + } +} + +impl TryFrom<(msg::Node, &HashMap)> for Value { + type Error = StdError; + + fn try_from( + (node, prefixes): (msg::Node, &HashMap), + ) -> Result { + match node { + msg::Node::NamedNode(msg::IRI::Full(uri)) => Ok(Value::NamedNode(uri)), + msg::Node::NamedNode(msg::IRI::Prefixed(curie)) => { + Ok(Value::NamedNode(expand_uri(&curie, prefixes)?)) + } + msg::Node::BlankNode(id) => Ok(Value::BlankNode(id)), + } + } +} + +impl TryFrom<(msg::Literal, &HashMap)> for Value { + type Error = StdError; + + fn try_from( + (literal, prefixes): (msg::Literal, &HashMap), + ) -> Result { + match literal { + msg::Literal::Simple(value) => Ok(Value::LiteralSimple(value)), + msg::Literal::LanguageTaggedString { value, language } => { + Ok(Value::LiteralLang(value, language)) + } + msg::Literal::TypedValue { + value, + datatype: msg::IRI::Full(uri), + } => Ok(Value::LiteralDatatype(value, uri)), + msg::Literal::TypedValue { + value, + datatype: msg::IRI::Prefixed(prefix), + } => Ok(Value::LiteralDatatype( + value, + expand_uri(&prefix, prefixes)?, + )), + } + } +} + +#[derive(Default)] +pub struct PrefixMap(HashMap); +impl PrefixMap { + pub fn into_inner(self) -> HashMap { + self.0 + } +} + +impl From> for PrefixMap { + fn from(as_list: Vec) -> Self { + PrefixMap( + as_list + .into_iter() + .map(|prefix| (prefix.prefix, prefix.namespace)) + .collect(), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn try_from_subject() { + assert_eq!( + ( + msg::Value::URI { + value: msg::IRI::Full( + "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string() + ), + }, + &PrefixMap::default().into_inner(), + ) + .try_into(), + Ok(Subject::NamedNode( + "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(), + )) + ); + assert_eq!( + ( + msg::Value::BlankNode { + value: "blank".to_string(), + }, + &PrefixMap::default().into_inner(), + ) + .try_into(), + Ok(Subject::BlankNode("blank".to_string())) + ); + assert_eq!( + ( + msg::Value::URI { + value: msg::IRI::Prefixed("rdf:".to_string()), + }, + &::from(vec![msg::Prefix { + prefix: "rdf".to_string(), + namespace: "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(), + }]) + .into_inner(), + ) + .try_into(), + Ok(Subject::NamedNode( + "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(), + )) + ); + assert_eq!( + Subject::try_from(( + msg::Value::Literal { + value: "rdf".to_string(), + lang: None, + datatype: None, + }, + &PrefixMap::default().into_inner(), + )), + Err(StdError::generic_err( + "Unsupported subject value: Literal { value: \"rdf\", lang: None, datatype: None }. Expected URI or BlankNode" + )) + ); + } + + #[test] + fn try_from_property() { + assert_eq!( + ( + msg::Value::URI { + value: msg::IRI::Full( + "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string() + ), + }, + &PrefixMap::default().into_inner(), + ) + .try_into(), + Ok(Property( + "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string() + )) + ); + assert_eq!( + ( + msg::Value::URI { + value: msg::IRI::Prefixed("rdf:".to_string()), + }, + &::from(vec![msg::Prefix { + prefix: "rdf".to_string(), + namespace: "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(), + }]) + .into_inner(), + ) + .try_into(), + Ok(Property( + "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(), + )) + ); + assert_eq!( + Property::try_from(( + msg::Value::BlankNode { + value: "blank".to_string(), + }, + &PrefixMap::default().into_inner(), + )), + Err(StdError::generic_err( + "Unsupported predicate value: BlankNode { value: \"blank\" }. Expected URI" + )) + ); + } + + #[test] + fn try_from_value() { + assert_eq!( + ( + msg::Value::URI { + value: msg::IRI::Full( + "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string() + ), + }, + &PrefixMap::default().into_inner() + ) + .try_into(), + Ok(Value::NamedNode( + "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string() + )) + ); + assert_eq!( + ( + msg::Value::URI { + value: msg::IRI::Prefixed("rdf:".to_string()), + }, + &::from(vec![msg::Prefix { + prefix: "rdf".to_string(), + namespace: "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(), + }]) + .into_inner(), + ) + .try_into(), + Ok(Value::NamedNode( + "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(), + )) + ); + assert_eq!( + ( + msg::Value::Literal { + value: "foo".to_string(), + lang: None, + datatype: None, + }, + &PrefixMap::default().into_inner(), + ) + .try_into(), + Ok(Value::LiteralSimple("foo".to_string())) + ); + assert_eq!( + ( + msg::Value::Literal { + value: "foo".to_string(), + lang: Some("en".to_string()), + datatype: None, + }, + &PrefixMap::default().into_inner() + ) + .try_into(), + Ok(Value::LiteralLang("foo".to_string(), "en".to_string())) + ); + assert_eq!( + ( + msg::Value::Literal { + value: "foo".to_string(), + lang: None, + datatype: Some(msg::IRI::Full( + "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string() + )), + }, + &PrefixMap::default().into_inner(), + ) + .try_into(), + Ok(Value::LiteralDatatype( + "foo".to_string(), + "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string() + )) + ); + assert_eq!( + ( + msg::Value::Literal { + value: "foo".to_string(), + lang: None, + datatype: Some(msg::IRI::Prefixed("rdf:".to_string())), + }, + &::from(vec![msg::Prefix { + prefix: "rdf".to_string(), + namespace: "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(), + }]) + .into_inner(), + ) + .try_into(), + Ok(Value::LiteralDatatype( + "foo".to_string(), + "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string() + )) + ); + assert_eq!( + ( + msg::Value::BlankNode { + value: "foo".to_string() + }, + &PrefixMap::default().into_inner(), + ) + .try_into(), + Ok(Value::BlankNode("foo".to_string())) + ); + assert_eq!( + Value::try_from(( + msg::Value::Literal { + value: "blank".to_string(), + lang: Some("en".to_string()), + datatype: Some(msg::IRI::Full( + "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string() + )), + }, + &PrefixMap::default().into_inner(), + )), + Err(StdError::generic_err( + "Unsupported object value: Literal { value: \"blank\", lang: Some(\"en\"), datatype: Some(Full(\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\")) }. Expected URI, BlankNode or Literal" + )) + ); + } +} diff --git a/contracts/okp4-cognitarium/src/rdf/mod.rs b/contracts/okp4-cognitarium/src/rdf/mod.rs index 8c054df9..3d8c6c8f 100644 --- a/contracts/okp4-cognitarium/src/rdf/mod.rs +++ b/contracts/okp4-cognitarium/src/rdf/mod.rs @@ -1,7 +1,9 @@ mod atom; +mod mapper; mod serde; mod uri; pub use self::atom::*; +pub use self::mapper::*; pub use self::serde::*; pub use self::uri::*; diff --git a/contracts/okp4-cognitarium/src/rdf/serde.rs b/contracts/okp4-cognitarium/src/rdf/serde.rs index ad3e5b5b..35cc04dd 100644 --- a/contracts/okp4-cognitarium/src/rdf/serde.rs +++ b/contracts/okp4-cognitarium/src/rdf/serde.rs @@ -66,7 +66,7 @@ impl TripleReader { } } -impl TripleWriter { +impl TripleWriter { pub fn new(format: &DataFormat, dst: W) -> Self { TripleWriter { writer: match format { diff --git a/contracts/okp4-cognitarium/src/rdf/uri.rs b/contracts/okp4-cognitarium/src/rdf/uri.rs index b24fb9bc..eb0d35a7 100644 --- a/contracts/okp4-cognitarium/src/rdf/uri.rs +++ b/contracts/okp4-cognitarium/src/rdf/uri.rs @@ -1,6 +1,5 @@ use cosmwasm_std::{StdError, StdResult}; - -use crate::msg::Prefix; +use std::collections::HashMap; pub fn explode_iri(iri: &str) -> StdResult<(String, String)> { let mut marker_index: Option = None; @@ -21,26 +20,25 @@ pub fn explode_iri(iri: &str) -> StdResult<(String, String)> { } // Expand a compacted URI (CURIE - URI with prefix) to a full URI. -pub fn expand_uri(curie: &str, prefixes: &[Prefix]) -> StdResult { +pub fn expand_uri(curie: &str, prefixes: &HashMap) -> StdResult { let idx = curie .rfind(':') .ok_or_else(|| StdError::generic_err(format!("Malformed CURIE: {curie}")))?; let prefix = curie[..idx].to_string(); + let namespace = prefixes + .get(&prefix) + .ok_or_else(|| StdError::generic_err(format!("Prefix not found: {prefix}")))?; let suffix = curie[idx + 1..].to_string(); - let namespace = &prefixes - .iter() - .find(|p| p.prefix == prefix) - .ok_or_else(|| StdError::generic_err(format!("Prefix not found: {prefix}")))? - .namespace; - Ok(format!("{namespace}{suffix}")) } #[cfg(test)] mod tests { use super::*; + use crate::msg::Prefix; + use crate::rdf::PrefixMap; #[test] fn proper_explode_iri() { @@ -83,7 +81,7 @@ mod tests { #[test] fn test_expand_uri() { - let prefixes = vec![ + let prefixes = &::from(vec![ Prefix { prefix: "ex".to_string(), namespace: "http://example.com/".to_string(), @@ -92,30 +90,31 @@ mod tests { prefix: "rdf".to_string(), namespace: "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(), }, - ]; + ]) + .into_inner(); assert_eq!( - expand_uri("ex:resource", &prefixes), + expand_uri("ex:resource", prefixes), Ok("http://example.com/resource".to_string()) ); assert_eq!( - expand_uri("ex:", &prefixes), + expand_uri("ex:", prefixes), Ok("http://example.com/".to_string()) ); assert_eq!( - expand_uri("unknown:resource", &prefixes), + expand_uri("unknown:resource", prefixes), Err(StdError::generic_err("Prefix not found: unknown")) ); assert_eq!( - expand_uri("malformed_curie:", &prefixes), + expand_uri("malformed_curie:", prefixes), Err(StdError::generic_err("Prefix not found: malformed_curie")) ); assert_eq!( - expand_uri("malformed_curie", &prefixes), + expand_uri("malformed_curie", prefixes), Err(StdError::generic_err("Malformed CURIE: malformed_curie")) ); } diff --git a/contracts/okp4-cognitarium/src/state/triples.rs b/contracts/okp4-cognitarium/src/state/triples.rs index 95d36240..e2890e8f 100644 --- a/contracts/okp4-cognitarium/src/state/triples.rs +++ b/contracts/okp4-cognitarium/src/state/triples.rs @@ -134,7 +134,7 @@ impl Node { where F: FnMut(u128) -> StdResult, { - ns_fn(self.namespace).map(|ns| [ns.as_str(), self.value.as_str()].join("")) + Ok(ns_fn(self.namespace)? + &self.value) } } diff --git a/contracts/okp4-cognitarium/src/storer.rs b/contracts/okp4-cognitarium/src/storer/engine.rs similarity index 85% rename from contracts/okp4-cognitarium/src/storer.rs rename to contracts/okp4-cognitarium/src/storer/engine.rs index 234b39e8..6b4f9dc7 100644 --- a/contracts/okp4-cognitarium/src/storer.rs +++ b/contracts/okp4-cognitarium/src/storer/engine.rs @@ -12,7 +12,7 @@ use rio_api::model::Term; use std::collections::BTreeMap; use std::io::BufRead; -pub struct TripleStorer<'a> { +pub struct StoreEngine<'a> { storage: &'a mut dyn Storage, store: Store, ns_key_inc_offset: u128, @@ -21,7 +21,7 @@ pub struct TripleStorer<'a> { initial_byte_size: Uint128, } -impl<'a> TripleStorer<'a> { +impl<'a> StoreEngine<'a> { pub fn new(storage: &'a mut dyn Storage) -> StdResult { let store = STORE.load(storage)?; let ns_key_inc_offset = NAMESPACE_KEY_INCREMENT.load(storage)?; @@ -43,7 +43,7 @@ impl<'a> TripleStorer<'a> { self.finish() } - pub fn store_triple(&mut self, t: model::Triple<'_>) -> Result<(), ContractError> { + fn store_triple(&mut self, t: model::Triple<'_>) -> Result<(), ContractError> { self.store.stat.triple_count += Uint128::one(); if self.store.stat.triple_count > self.store.limits.max_triple_count { Err(StoreError::TripleCount(self.store.limits.max_triple_count))?; @@ -91,6 +91,33 @@ impl<'a> TripleStorer<'a> { .map_err(ContractError::Std) } + pub fn delete_all(&mut self, atoms: &[rdf::Atom]) -> Result { + for atom in atoms { + self.delete_triple(atom)?; + } + self.finish() + } + + pub fn delete_triple(&mut self, atom: &rdf::Atom) -> Result<(), ContractError> { + let triple = self.rio_to_triple(atom.into())?; + let object_hash: Hash = triple.object.as_hash(); + + self.store.stat.triple_count -= Uint128::one(); + self.store.stat.byte_size -= Uint128::from(Self::triple_size(t) as u128); + triples() + .remove( + self.storage, + ( + object_hash.as_bytes(), + triple.predicate.key(), + triple.subject.key(), + ), + ) + .map_err(ContractError::Std) + } + + /// Flushes the store to the storage. + /// Returns the number of triples added or removed (absolute value). pub fn finish(&mut self) -> Result { STORE.save(self.storage, &self.store)?; NAMESPACE_KEY_INCREMENT.save(self.storage, &self.ns_key_inc_offset)?; @@ -98,11 +125,15 @@ impl<'a> TripleStorer<'a> { namespaces().save(self.storage, entry.0.to_string(), entry.1)?; } - Ok(self.store.stat.triple_count - self.initial_triple_count) + Ok(self + .store + .stat + .triple_count + .abs_diff(self.initial_triple_count)) } fn resolve_namespace_key(&mut self, ns_str: String) -> StdResult { - if let Some(namespace) = self.ns_cache.get_mut(ns_str.as_str()) { + if let Some(namespace) = self.ns_cache.get_mut(&ns_str) { namespace.counter += 1; Ok(namespace.key) } else { diff --git a/contracts/okp4-cognitarium/src/storer/mod.rs b/contracts/okp4-cognitarium/src/storer/mod.rs new file mode 100644 index 00000000..520992db --- /dev/null +++ b/contracts/okp4-cognitarium/src/storer/mod.rs @@ -0,0 +1,3 @@ +mod engine; + +pub use engine::*; diff --git a/docs/okp4-cognitarium.md b/docs/okp4-cognitarium.md index 2f8b3c50..6701bd7b 100644 --- a/docs/okp4-cognitarium.md +++ b/docs/okp4-cognitarium.md @@ -48,9 +48,9 @@ Only the smart contract owner (i.e. the address who instantiated it) is authoriz |parameter|description| |----------|-----------| |`delete_data`|*(Required.) * **object**. | -|`delete_data.delete`|*(Required.) * **Array<[TriplePattern](#triplepattern)>**. The items to delete.| +|`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`|**array\|null**. The WHERE clause to apply. If not provided, all the RDF triples are considered.| +|`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 @@ -273,7 +273,7 @@ Represents either an IRI (named node) or a blank node. ### Prefix -Represents a prefix in a [SelectQuery]. A prefix is a shortcut for a namespace used in the query. +Represents a prefix, i.e. a shortcut for a namespace used in a query. |property|description| |----------|-----------|