From e21edc40859e1632ad2a64039c71a69f14a4849b Mon Sep 17 00:00:00 2001 From: ccamel Date: Thu, 15 Jun 2023 14:06:56 +0200 Subject: [PATCH] feat(cognitarium): implement describe query --- contracts/okp4-cognitarium/src/contract.rs | 202 +++++++++++++++++++- contracts/okp4-cognitarium/src/error.rs | 3 + contracts/okp4-cognitarium/src/rdf/atom.rs | 33 ++++ contracts/okp4-cognitarium/src/rdf/mod.rs | 4 +- contracts/okp4-cognitarium/src/rdf/serde.rs | 5 +- contracts/okp4-cognitarium/src/rdf/uri.rs | 3 +- 6 files changed, 236 insertions(+), 14 deletions(-) diff --git a/contracts/okp4-cognitarium/src/contract.rs b/contracts/okp4-cognitarium/src/contract.rs index b714f380..a6134791 100644 --- a/contracts/okp4-cognitarium/src/contract.rs +++ b/contracts/okp4-cognitarium/src/contract.rs @@ -77,14 +77,27 @@ 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)?), - _ => Err(StdError::generic_err("Not implemented")), + QueryMsg::Describe { query, format } => to_binary(&query::describe( + deps, + query, + format.unwrap_or(DataFormat::Turtle), + )?), } } pub mod query { + use std::collections::BTreeMap; + + use rio_api::model::Triple; + use super::*; - use crate::msg::{SelectQuery, SelectResponse, StoreResponse}; + use crate::msg::{ + DescribeQuery, DescribeResponse, Node, SelectItem, SelectQuery, SelectResponse, + SimpleWhereCondition, StoreResponse, TriplePattern, Value, VarOrNamedNode, VarOrNode, + VarOrNodeOrLiteral, WhereCondition, IRI, + }; use crate::querier::{PlanBuilder, QueryEngine}; + use crate::rdf::{self, expand_uri, Atom, TripleWriter}; pub fn store(deps: Deps) -> StdResult { STORE.load(deps.storage).map(|s| s.into()) @@ -104,12 +117,186 @@ pub mod query { Err(StdError::generic_err("Maximum query limit exceeded"))? } - let plan = - PlanBuilder::new(deps.storage, query.prefixes).with_limit(count as usize) + let plan = PlanBuilder::new(deps.storage, query.prefixes) + .with_limit(count as usize) .build_plan(query.r#where)?; QueryEngine::new(deps.storage).select(plan, query.select) } + + pub fn describe( + deps: Deps, + query: DescribeQuery, + format: DataFormat, + ) -> StdResult { + fn get_value( + index: usize, + vars: &Vec, + bindings: &BTreeMap, + ) -> Result { + vars.get(index) + .and_then(|it| bindings.get(it.as_str())) + .map(|it| it.to_owned()) + .ok_or_else(|| { + StdError::generic_err(format!( + "Variable index {} not found (this was unexpected)", + index + )) + }) + } + + let (s, p, o) = ("_1s".to_owned(), "_2p".to_owned(), "_3o".to_owned()); + + let store = STORE.load(deps.storage)?; + + let (select, r#where) = match &query.resource { + VarOrNamedNode::Variable(var) => { + let mut r#where = query.r#where; + r#where.push(WhereCondition::Simple(SimpleWhereCondition::TriplePattern( + TriplePattern { + subject: VarOrNode::Variable(var.clone()), + predicate: VarOrNode::Variable(format!("{}{}", var, p)), + object: VarOrNodeOrLiteral::Variable(format!("{}{}", var, o)), + }, + ))); + + ( + vec![ + SelectItem::Variable(var.clone()), + SelectItem::Variable(format!("{}{}", var, p)), + SelectItem::Variable(format!("{}{}", var, o)), + ], + r#where, + ) + } + VarOrNamedNode::NamedNode(iri) => ( + vec![ + SelectItem::Variable(p.clone()), + SelectItem::Variable(o.clone()), + ], + vec![WhereCondition::Simple(SimpleWhereCondition::TriplePattern( + TriplePattern { + subject: VarOrNode::Node(Node::NamedNode(iri.clone())), + predicate: VarOrNode::Variable(p), + object: VarOrNodeOrLiteral::Variable(o), + }, + ))], + ), + }; + + let plan = PlanBuilder::new(deps.storage, query.prefixes.clone()) + .with_limit(store.limits.max_query_limit as usize) + .build_plan(r#where)?; + + let response = QueryEngine::new(deps.storage).select(plan, select)?; + + let mut vars = response.head.vars; + if let VarOrNamedNode::NamedNode(_) = &query.resource { + vars.insert(0, s.clone()); + } + + let mut bindings = response.results.bindings; + if let VarOrNamedNode::NamedNode(iri) = &query.resource { + for b in &mut bindings { + b.insert( + s.clone(), + Value::URI { + value: iri.to_owned(), + }, + ); + } + } + + let out: Vec = Vec::default(); + let mut writer = TripleWriter::new(&format, out.clone()); + + for r in &bindings { + let subject_value = get_value(0, &vars, r)?; + let subject = match subject_value { + Value::URI { + value: IRI::Full(uri), + } => uri, + Value::URI { + value: IRI::Prefixed(curie), + } => expand_uri(curie, &query.prefixes)?, + _ => Err(StdError::generic_err(format!( + "Unexpected value: {:?} (this was unexpected)", + subject_value + )))?, + }; + + let predicate_value = get_value(1, &vars, r)?; + let predicate = match predicate_value { + Value::URI { + value: IRI::Full(uri), + } => uri.clone(), + Value::URI { + value: IRI::Prefixed(curie), + } => expand_uri(curie, &query.prefixes)?, + _ => Err(StdError::generic_err(format!( + "Unexpected value: {:?} (this was unexpected)", + predicate_value + )))?, + }; + + let object_value = get_value(2, &vars, r)?; + let object = match object_value { + Value::URI { + value: IRI::Full(uri), + } => rdf::Value::NamedNode(uri), + Value::URI { + value: IRI::Prefixed(curie), + } => rdf::Value::NamedNode(expand_uri(curie, &query.prefixes)?), + Value::Literal { + value, + lang: None, + datatype: None, + } => rdf::Value::LiteralSimple(value), + Value::Literal { + value, + lang: Some(lang), + datatype: None, + } => rdf::Value::LiteralLang(value, lang), + Value::Literal { + value, + lang: None, + datatype: Some(IRI::Full(uri)), + } => rdf::Value::LiteralDatatype(value, uri), + Value::Literal { + value, + lang: None, + datatype: Some(IRI::Prefixed(curie)), + } => rdf::Value::LiteralDatatype(value, expand_uri(curie, &query.prefixes)?), + Value::BlankNode { value } => rdf::Value::BlankNode(value), + _ => Err(StdError::generic_err(format!( + "Unexpected value: {:?} (this was unexpected)", + object_value + )))?, + }; + + let atom = Atom { + subject: subject, + property: predicate, + value: object, + }; + let triple = Triple::from(&atom); + + 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)) + })?; + + Ok(DescribeResponse { + format: format, + data: Binary::from(out), + }) + } } #[cfg(test)] @@ -121,14 +308,15 @@ mod tests { use crate::msg::SimpleWhereCondition::TriplePattern; use crate::msg::IRI::{Full, Prefixed}; use crate::msg::{ - Head, Literal, Prefix, Results, SelectItem, SelectQuery, SelectResponse, StoreLimitsInput, - StoreLimitsInputBuilder, StoreResponse, Value, VarOrNode, VarOrNodeOrLiteral, - WhereCondition, + DescribeQuery, DescribeResponse, Head, Literal, Prefix, Results, SelectItem, SelectQuery, + SelectResponse, StoreLimitsInput, StoreLimitsInputBuilder, StoreResponse, Value, + VarOrNamedNode, VarOrNode, VarOrNodeOrLiteral, WhereCondition, }; use crate::state::{ namespaces, triples, Namespace, Node, Object, StoreLimits, StoreStat, Subject, Triple, }; use crate::{msg, state}; + use base64::{engine::general_purpose, Engine as _}; use blake3::Hash; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; use cosmwasm_std::{from_binary, Addr, Attribute, Order, Uint128}; diff --git a/contracts/okp4-cognitarium/src/error.rs b/contracts/okp4-cognitarium/src/error.rs index 64ebeab5..ab075f21 100644 --- a/contracts/okp4-cognitarium/src/error.rs +++ b/contracts/okp4-cognitarium/src/error.rs @@ -11,6 +11,9 @@ pub enum ContractError { #[error("{0}")] ParseRDF(#[from] RDFParseError), + #[error("{0}")] + FormatRDF(String), + #[error("{0}")] Store(#[from] StoreError), diff --git a/contracts/okp4-cognitarium/src/rdf/atom.rs b/contracts/okp4-cognitarium/src/rdf/atom.rs index 7e9858dd..0b0bff5f 100644 --- a/contracts/okp4-cognitarium/src/rdf/atom.rs +++ b/contracts/okp4-cognitarium/src/rdf/atom.rs @@ -8,6 +8,8 @@ pub enum Value { } use std::fmt; + +use rio_api::model::{Literal, NamedNode, Triple}; impl fmt::Display for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -36,3 +38,34 @@ impl std::fmt::Display for Atom { Ok(()) } } + +impl<'a> From<&'a Atom> for Triple<'a> { + fn from(atom: &'a Atom) -> Self { + Triple { + subject: NamedNode::from(NamedNode { + iri: atom.subject.as_str(), + }) + .into(), + predicate: NamedNode::from(NamedNode { + iri: atom.property.as_str(), + }), + object: match &atom.value { + Value::NamedNode(s) => NamedNode::from(NamedNode { iri: s.as_str() }).into(), + Value::BlankNode(s) => NamedNode::from(NamedNode { iri: s.as_str() }).into(), + Value::LiteralSimple(s) => { + Literal::from(Literal::Simple { value: s.as_str() }).into() + } + Value::LiteralLang(s, l) => Literal::from(Literal::LanguageTaggedString { + value: s.as_str(), + language: l.as_str(), + }) + .into(), + Value::LiteralDatatype(s, d) => Literal::from(Literal::Typed { + value: s.as_str(), + datatype: NamedNode::from(NamedNode { iri: d.as_str() }), + }) + .into(), + }, + } + } +} diff --git a/contracts/okp4-cognitarium/src/rdf/mod.rs b/contracts/okp4-cognitarium/src/rdf/mod.rs index f3012a87..8c054df9 100644 --- a/contracts/okp4-cognitarium/src/rdf/mod.rs +++ b/contracts/okp4-cognitarium/src/rdf/mod.rs @@ -1,7 +1,7 @@ +mod atom; mod serde; mod uri; -mod atom; +pub use self::atom::*; pub use self::serde::*; pub use self::uri::*; -pub use self::atom::*; diff --git a/contracts/okp4-cognitarium/src/rdf/serde.rs b/contracts/okp4-cognitarium/src/rdf/serde.rs index 377cdf94..64346181 100644 --- a/contracts/okp4-cognitarium/src/rdf/serde.rs +++ b/contracts/okp4-cognitarium/src/rdf/serde.rs @@ -1,7 +1,6 @@ -use crate::msg::{DataFormat, Prefix}; -use cosmwasm_std::{StdError, StdResult}; +use crate::msg::DataFormat; use rio_api::formatter::TriplesFormatter; -use rio_api::model::{NamedNode, Quad, Triple}; +use rio_api::model::{Quad, Triple}; use rio_api::parser::{QuadsParser, TriplesParser}; use rio_turtle::{ NQuadsFormatter, NQuadsParser, NTriplesFormatter, NTriplesParser, TurtleError, TurtleFormatter, diff --git a/contracts/okp4-cognitarium/src/rdf/uri.rs b/contracts/okp4-cognitarium/src/rdf/uri.rs index 98a66c35..a2b14b1b 100644 --- a/contracts/okp4-cognitarium/src/rdf/uri.rs +++ b/contracts/okp4-cognitarium/src/rdf/uri.rs @@ -20,7 +20,6 @@ pub fn explode_iri(iri: &str) -> StdResult<(String, String)> { Err(StdError::generic_err("Couldn't extract IRI namespace")) } - // Expand a compacted URI (CURIE - URI with prefix) to a full URI. pub fn expand_uri<'a>(curie: String, prefixes: &Vec) -> StdResult { let idx = curie @@ -81,4 +80,4 @@ mod tests { Err(StdError::generic_err("Couldn't extract IRI namespace")) ); } -} \ No newline at end of file +}