diff --git a/contracts/okp4-cognitarium/src/contract.rs b/contracts/okp4-cognitarium/src/contract.rs index 94da3445..c0df6740 100644 --- a/contracts/okp4-cognitarium/src/contract.rs +++ b/contracts/okp4-cognitarium/src/contract.rs @@ -378,7 +378,7 @@ mod tests { use super::*; use crate::error::StoreError; use crate::msg::ExecuteMsg::{DeleteData, InsertData}; - use crate::msg::Node::NamedNode; + use crate::msg::Node::{BlankNode, NamedNode}; use crate::msg::SimpleWhereCondition::TriplePattern; use crate::msg::IRI::{Full, Prefixed}; use crate::msg::{ @@ -489,7 +489,7 @@ mod tests { res.unwrap().attributes, vec![ Attribute::new("action", "insert"), - Attribute::new("triple_count", "40") + Attribute::new("triple_count", "40"), ] ); @@ -512,7 +512,7 @@ mod tests { namespaces() .load( &deps.storage, - "https://ontology.okp4.space/dataverse/dataspace/".to_string() + "https://ontology.okp4.space/dataverse/dataspace/".to_string(), ) .unwrap(), Namespace { @@ -529,35 +529,35 @@ mod tests { Object::Named(Node { namespace: 4u128, value: "0x04d1f1b8f8a7a28f9a5a254c326a963a22f5a5b5d5f5e5d5c5b5a5958575655" - .to_string() + .to_string(), }).as_hash() - .as_bytes(), + .as_bytes(), Node { namespace: 3u128, - value: "hasRegistrar".to_string() + value: "hasRegistrar".to_string(), } - .key(), + .key(), Subject::Named(Node { namespace: 0u128, - value: "97ff7e16-c08d-47be-8475-211016c82e33".to_string() + value: "97ff7e16-c08d-47be-8475-211016c82e33".to_string(), }) - .key() - ) + .key() + ), ) .unwrap(), Triple { object: Object::Named(Node { namespace: 4u128, value: "0x04d1f1b8f8a7a28f9a5a254c326a963a22f5a5b5d5f5e5d5c5b5a5958575655" - .to_string() + .to_string(), }), predicate: Node { namespace: 3u128, - value: "hasRegistrar".to_string() + value: "hasRegistrar".to_string(), }, subject: Subject::Named(Node { namespace: 0u128, - value: "97ff7e16-c08d-47be-8475-211016c82e33".to_string() + value: "97ff7e16-c08d-47be-8475-211016c82e33".to_string(), }), } ) @@ -603,7 +603,7 @@ mod tests { res.unwrap().attributes, vec![ Attribute::new("action", "insert"), - Attribute::new("triple_count", "0") + Attribute::new("triple_count", "0"), ] ); @@ -963,7 +963,7 @@ mod tests { res.unwrap().attributes, vec![ Attribute::new("action", "delete"), - Attribute::new("triple_count", case.1.to_string()) + Attribute::new("triple_count", case.1.to_string()), ] ); @@ -1136,7 +1136,7 @@ mod tests { triple_count: 1u128.into(), namespace_count: 2u128.into(), byte_size: 3u128.into(), - } + }, } ); } @@ -1246,7 +1246,7 @@ mod tests { datatype: None, } ) - ]) + ]), ], }, }, @@ -1255,35 +1255,35 @@ mod tests { SelectQuery { prefixes: vec![Prefix { prefix: "core".to_string(), namespace: "https://ontology.okp4.space/core/".to_string() }], select: vec![ - SelectItem::Variable("a".to_string()), + SelectItem::Variable("a".to_string()), ], r#where: vec![WhereCondition::Simple(TriplePattern( - msg::TriplePattern { - subject: VarOrNode::Variable("a".to_string()), - predicate: VarOrNode::Node(NamedNode(Prefixed( - "core:hasDescription".to_string(), - ))), - object: VarOrNodeOrLiteral::Literal(Literal::LanguageTaggedString { value: "A test Dataset.".to_string(), language: "en".to_string() }), - }, + msg::TriplePattern { + subject: VarOrNode::Variable("a".to_string()), + predicate: VarOrNode::Node(NamedNode(Prefixed( + "core:hasDescription".to_string(), + ))), + object: VarOrNodeOrLiteral::Literal(Literal::LanguageTaggedString { value: "A test Dataset.".to_string(), language: "en".to_string() }), + }, ))], limit: None, }, SelectResponse { - head: Head { - vars: vec!["a".to_string()], - }, - results: Results { - bindings: vec![ - BTreeMap::from([ - ( - "a".to_string(), - Value::URI { - value: Full("https://ontology.okp4.space/dataverse/dataset/metadata/d1615703-4ee1-4e2f-997e-15aecf1eea4e".to_string()) - } - ), - ]) - ], - }, + head: Head { + vars: vec!["a".to_string()], + }, + results: Results { + bindings: vec![ + BTreeMap::from([ + ( + "a".to_string(), + Value::URI { + value: Full("https://ontology.okp4.space/dataverse/dataset/metadata/d1615703-4ee1-4e2f-997e-15aecf1eea4e".to_string()) + } + ), + ]) + ], + }, }, ), ( @@ -1318,7 +1318,7 @@ mod tests { ], }, }, - ) + ), ]; let mut deps = mock_dependencies(); @@ -1352,6 +1352,227 @@ mod tests { } } + #[test] + fn proper_select_blank_nodes() { + let cases = vec![ + ( + SelectQuery { + prefixes: vec![Prefix { prefix: "core".to_string(), namespace: "https://ontology.okp4.space/core/".to_string() }], + select: vec![SelectItem::Variable("a".to_string()), SelectItem::Variable("b".to_string())], + r#where: vec![ + WhereCondition::Simple(TriplePattern( + msg::TriplePattern { + subject: VarOrNode::Variable("a".to_string()), + predicate: VarOrNode::Node(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( + "core:hasStartDate".to_string(), + ))), + object: VarOrNodeOrLiteral::Variable("b".to_string()), + }, + ))], + limit: None, + }, + SelectResponse { + head: Head { vars: vec!["a".to_string(), "b".to_string()] }, + results: Results { + bindings: vec![ + BTreeMap::from([ + ( + "a".to_string(), + Value::URI { + value: Full("https://ontology.okp4.space/dataverse/dataset/metadata/80b1f84e-86dc-4730-b54f-701ad9b1888a".to_string()) + } + ), + ( + "b".to_string(), + Value::Literal { + value: "2022-01-01T00:00:00+00:00".to_string(), + lang: None, + datatype: Some(Full("http://www.w3.org/2001/XMLSchema#dateTime".to_string())), + } + ) + ]) + ], + }, + }, + ), + ( + SelectQuery { + prefixes: vec![Prefix { prefix: "core".to_string(), namespace: "https://ontology.okp4.space/core/".to_string() }], + select: vec![SelectItem::Variable("a".to_string()), SelectItem::Variable("b".to_string())], + r#where: vec![ + WhereCondition::Simple(TriplePattern( + msg::TriplePattern { + subject: VarOrNode::Variable("a".to_string()), + predicate: VarOrNode::Node(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( + "core:hasStartDate".to_string(), + ))), + object: VarOrNodeOrLiteral::Variable("b".to_string()), + }, + ))], + limit: None, + }, + SelectResponse { + head: Head { vars: vec!["a".to_string(), "b".to_string()] }, + results: Results { + bindings: vec![ + BTreeMap::from([ + ( + "a".to_string(), + Value::URI { + value: Full("https://ontology.okp4.space/dataverse/dataset/metadata/80b1f84e-86dc-4730-b54f-701ad9b1888a".to_string()) + } + ), + ( + "b".to_string(), + Value::Literal { + value: "2022-01-01T00:00:00+00:00".to_string(), + lang: None, + datatype: Some(Full("http://www.w3.org/2001/XMLSchema#dateTime".to_string())), + } + ) + ]) + ], + }, + }, + ), + ( + SelectQuery { + prefixes: vec![Prefix { prefix: "core".to_string(), namespace: "https://ontology.okp4.space/core/".to_string() }], + select: vec![SelectItem::Variable("a".to_string()), SelectItem::Variable("b".to_string())], + r#where: vec![ + WhereCondition::Simple(TriplePattern( + msg::TriplePattern { + subject: VarOrNode::Variable("a".to_string()), + predicate: VarOrNode::Node(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( + "core:hasInformation".to_string(), + ))), + object: VarOrNodeOrLiteral::Variable("b".to_string()), + }, + ))], + limit: None, + }, + SelectResponse { + head: Head { vars: vec!["a".to_string(), "b".to_string()] }, + results: Results { + bindings: vec![ + BTreeMap::from([ + ( + "a".to_string(), + Value::URI { + value: Full("https://ontology.okp4.space/dataverse/dataset/metadata/80b1f84e-86dc-4730-b54f-701ad9b1888a".to_string()) + } + ), + ( + "b".to_string(), + Value::Literal { + value: "this is a dataset".to_string(), + lang: None, + datatype: None, + } + ) + ]) + ], + }, + }, + ), + ( + SelectQuery { + prefixes: vec![Prefix { prefix: "core".to_string(), namespace: "https://ontology.okp4.space/core/".to_string() }], + select: vec![SelectItem::Variable("a".to_string()), SelectItem::Variable("b".to_string())], + r#where: vec![ + WhereCondition::Simple(TriplePattern( + msg::TriplePattern { + subject: VarOrNode::Variable("a".to_string()), + predicate: VarOrNode::Node(NamedNode(Prefixed( + "core:hasTemporalCoverage".to_string(), + ))), + object: VarOrNodeOrLiteral::Variable("b".to_string()), + }, + ))], + limit: None, + }, + SelectResponse { + head: Head { vars: vec!["a".to_string(), "b".to_string()] }, + results: Results { + bindings: vec![ + BTreeMap::from([ + ( + "a".to_string(), + Value::URI { + value: Full("https://ontology.okp4.space/dataverse/dataset/metadata/80b1f84e-86dc-4730-b54f-701ad9b1888a".to_string()) + } + ), + ( + "b".to_string(), + Value::BlankNode { + value: "riog00000001".to_string(), + } + ) + ]) + ], + }, + }, + ), + ]; + + 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, + InsertData { + format: Some(DataFormat::Turtle), + data: read_test_data("blank-nodes.ttl"), + }, + ) + .unwrap(); + + for (q, expected) in cases { + let res = query(deps.as_ref(), mock_env(), QueryMsg::Select { query: q }); + assert!(res.is_ok()); + + let result = from_json::(&res.unwrap()).unwrap(); + assert_eq!(result, expected); + } + } + #[test] fn invalid_select() { let cases = vec![ @@ -1457,19 +1678,19 @@ mod tests { #[test] fn formats_describe() { let cases = vec![ - ( - QueryMsg::Describe { - query: DescribeQuery { - prefixes: vec![], - resource: VarOrNamedNode::NamedNode(Full("https://ontology.okp4.space/dataverse/dataspace/metadata/dcf48417-01c5-4b43-9bc7-49e54c028473".to_string())), - r#where: vec![], + ( + QueryMsg::Describe { + query: DescribeQuery { + prefixes: vec![], + resource: VarOrNamedNode::NamedNode(Full("https://ontology.okp4.space/dataverse/dataspace/metadata/dcf48417-01c5-4b43-9bc7-49e54c028473".to_string())), + r#where: vec![], + }, + format: Some(DataFormat::Turtle), }, - format: Some(DataFormat::Turtle), - }, - DescribeResponse { - format: DataFormat::Turtle, - data: Binary::from( - " , ; + DescribeResponse { + format: DataFormat::Turtle, + data: Binary::from( + " , ; \t \"Test\" , \"OKP4\" ; \t \"Data Space de test\"@fr , \"Test Data Space\"@en ; \t ; @@ -1478,21 +1699,21 @@ mod tests { \t \"A test Data Space.\"@en , \"Un Data Space de test.\"@fr . \ ".to_string().as_bytes().to_vec()), - } - ), - ( - QueryMsg::Describe { - query: DescribeQuery { - prefixes: vec![], - resource: VarOrNamedNode::NamedNode(Full("https://ontology.okp4.space/dataverse/dataspace/metadata/dcf48417-01c5-4b43-9bc7-49e54c028473".to_string())), - r#where: vec![], + } + ), + ( + QueryMsg::Describe { + query: DescribeQuery { + prefixes: vec![], + resource: VarOrNamedNode::NamedNode(Full("https://ontology.okp4.space/dataverse/dataspace/metadata/dcf48417-01c5-4b43-9bc7-49e54c028473".to_string())), + r#where: vec![], + }, + format: Some(DataFormat::RDFXml), }, - format: Some(DataFormat::RDFXml), - }, - DescribeResponse { - format: DataFormat::RDFXml, - data: Binary::from( - "\ + DescribeResponse { + format: DataFormat::RDFXml, + data: Binary::from( + "\ \ \ \ @@ -1507,29 +1728,29 @@ mod tests { \ \ ".to_string().as_bytes().to_vec()), - } - ), - ( - QueryMsg::Describe { - query: DescribeQuery { - prefixes: vec![], - resource: VarOrNamedNode::NamedNode(Full("https://ontology.okp4.space/dataverse/dataspace/metadata/dcf48417-01c5-4b43-9bc7-49e54c028473".to_string())), - r#where: vec![WhereCondition::Simple(TriplePattern( - msg::TriplePattern { - subject: VarOrNode::Variable("a".to_string()), - predicate: VarOrNode::Node(NamedNode(Full( - "https://ontology.okp4.space/core/hasDescription".to_string(), - ))), - object: VarOrNodeOrLiteral::Variable("b".to_string()), - }, - ))], + } + ), + ( + QueryMsg::Describe { + query: DescribeQuery { + prefixes: vec![], + resource: VarOrNamedNode::NamedNode(Full("https://ontology.okp4.space/dataverse/dataspace/metadata/dcf48417-01c5-4b43-9bc7-49e54c028473".to_string())), + r#where: vec![WhereCondition::Simple(TriplePattern( + msg::TriplePattern { + subject: VarOrNode::Variable("a".to_string()), + predicate: VarOrNode::Node(NamedNode(Full( + "https://ontology.okp4.space/core/hasDescription".to_string(), + ))), + object: VarOrNodeOrLiteral::Variable("b".to_string()), + }, + ))], + }, + format: Some(DataFormat::NTriples), }, - format: Some(DataFormat::NTriples), - }, - DescribeResponse { - format: DataFormat::NTriples, - data: Binary::from( - " . + DescribeResponse { + format: DataFormat::NTriples, + data: Binary::from( + " . . \"Test\" . \"OKP4\" . @@ -1542,21 +1763,21 @@ mod tests { \"Un Data Space de test.\"@fr . \ ".to_string().as_bytes().to_vec()), - } - ), - ( - QueryMsg::Describe { - query: DescribeQuery { - prefixes: vec![], - resource: VarOrNamedNode::NamedNode(Full("https://ontology.okp4.space/dataverse/dataspace/metadata/dcf48417-01c5-4b43-9bc7-49e54c028473".to_string())), - r#where: vec![], + } + ), + ( + QueryMsg::Describe { + query: DescribeQuery { + prefixes: vec![], + resource: VarOrNamedNode::NamedNode(Full("https://ontology.okp4.space/dataverse/dataspace/metadata/dcf48417-01c5-4b43-9bc7-49e54c028473".to_string())), + r#where: vec![], + }, + format: Some(DataFormat::NQuads), }, - format: Some(DataFormat::NQuads), - }, - DescribeResponse { - format: DataFormat::NQuads, - data: Binary::from( - " . + DescribeResponse { + format: DataFormat::NQuads, + data: Binary::from( + " . . \"Test\" . \"OKP4\" . @@ -1569,9 +1790,9 @@ mod tests { \"Un Data Space de test.\"@fr . \ ".to_string().as_bytes().to_vec()), - } - ), - ]; + } + ), + ]; let mut deps = mock_dependencies(); @@ -1613,24 +1834,24 @@ mod tests { #[test] fn prefixes_describe() { let cases = vec![ - ( - QueryMsg::Describe { - query: DescribeQuery { - prefixes: vec![ - Prefix { - prefix: "metadata".to_string(), - namespace: "https://ontology.okp4.space/dataverse/dataspace/metadata/".to_string(), - }, - ], - resource: VarOrNamedNode::NamedNode(Prefixed("metadata:dcf48417-01c5-4b43-9bc7-49e54c028473".to_string())), - r#where: vec![], + ( + QueryMsg::Describe { + query: DescribeQuery { + prefixes: vec![ + Prefix { + prefix: "metadata".to_string(), + namespace: "https://ontology.okp4.space/dataverse/dataspace/metadata/".to_string(), + }, + ], + resource: VarOrNamedNode::NamedNode(Prefixed("metadata:dcf48417-01c5-4b43-9bc7-49e54c028473".to_string())), + r#where: vec![], + }, + format: Some(DataFormat::Turtle), }, - format: Some(DataFormat::Turtle), - }, - DescribeResponse { - format: DataFormat::Turtle, - data: Binary::from( - " , ; + DescribeResponse { + format: DataFormat::Turtle, + data: Binary::from( + " , ; \t \"Test\" , \"OKP4\" ; \t \"Data Space de test\"@fr , \"Test Data Space\"@en ; \t ; @@ -1639,8 +1860,8 @@ mod tests { \t \"A test Data Space.\"@en , \"Un Data Space de test.\"@fr . \ ".to_string().as_bytes().to_vec()), - } - ), + } + ), ]; let mut deps = mock_dependencies(); @@ -1683,29 +1904,29 @@ mod tests { #[test] fn variable_describe() { let cases = vec![ - ( - QueryMsg::Describe { - query: DescribeQuery { - prefixes: vec![Prefix { prefix: "core".to_string(), namespace: "https://ontology.okp4.space/core/".to_string() }], - resource: VarOrNamedNode::Variable("a".to_string()), - r#where: vec![WhereCondition::Simple(TriplePattern( - msg::TriplePattern { - subject: VarOrNode::Variable("a".to_string()), - predicate: VarOrNode::Node(NamedNode(Prefixed( - "core:hasDescription".to_string(), - ))), - object: VarOrNodeOrLiteral::Literal(Literal::LanguageTaggedString { value: "A test Dataset.".to_string(), language: "en".to_string() }), - }, - ))], + ( + QueryMsg::Describe { + query: DescribeQuery { + prefixes: vec![Prefix { prefix: "core".to_string(), namespace: "https://ontology.okp4.space/core/".to_string() }], + resource: VarOrNamedNode::Variable("a".to_string()), + r#where: vec![WhereCondition::Simple(TriplePattern( + msg::TriplePattern { + subject: VarOrNode::Variable("a".to_string()), + predicate: VarOrNode::Node(NamedNode(Prefixed( + "core:hasDescription".to_string(), + ))), + object: VarOrNodeOrLiteral::Literal(Literal::LanguageTaggedString { value: "A test Dataset.".to_string(), language: "en".to_string() }), + }, + ))], + }, + format: Some(DataFormat::Turtle), }, - format: Some(DataFormat::Turtle), - }, - DescribeResponse { - format: DataFormat::Turtle, - data: Binary::from( - " , ;\n\t \"test\" ;\n\t \"test Dataset\"@en , \"Dataset de test\"@fr ;\n\t ;\n\t ;\n\t ;\n\t \"Me\" ;\n\t ;\n\t \"OKP4\" ;\n\t \"Un Dataset de test.\"@fr , \"A test Dataset.\"@en .\n".to_string().as_bytes().to_vec()), - } - ), + DescribeResponse { + format: DataFormat::Turtle, + data: Binary::from( + " , ;\n\t \"test\" ;\n\t \"test Dataset\"@en , \"Dataset de test\"@fr ;\n\t ;\n\t ;\n\t ;\n\t \"Me\" ;\n\t ;\n\t \"OKP4\" ;\n\t \"Un Dataset de test.\"@fr , \"A test Dataset.\"@en .\n".to_string().as_bytes().to_vec()), + } + ), ]; let mut deps = mock_dependencies(); @@ -1748,29 +1969,29 @@ mod tests { #[test] fn variable_mutiple_resources_describe() { let cases = vec![ - ( - QueryMsg::Describe { - query: DescribeQuery { - prefixes: vec![Prefix { prefix: "core".to_string(), namespace: "https://ontology.okp4.space/core/".to_string() }], - resource: VarOrNamedNode::Variable("a".to_string()), - r#where: vec![WhereCondition::Simple(TriplePattern( - msg::TriplePattern { - subject: VarOrNode::Variable("a".to_string()), - predicate: VarOrNode::Node(NamedNode(Prefixed( - "core:hasPublisher".to_string(), - ))), - object: VarOrNodeOrLiteral::Literal(Literal::Simple("OKP4".to_string())), - }, - ))], + ( + QueryMsg::Describe { + query: DescribeQuery { + prefixes: vec![Prefix { prefix: "core".to_string(), namespace: "https://ontology.okp4.space/core/".to_string() }], + resource: VarOrNamedNode::Variable("a".to_string()), + r#where: vec![WhereCondition::Simple(TriplePattern( + msg::TriplePattern { + subject: VarOrNode::Variable("a".to_string()), + predicate: VarOrNode::Node(NamedNode(Prefixed( + "core:hasPublisher".to_string(), + ))), + object: VarOrNodeOrLiteral::Literal(Literal::Simple("OKP4".to_string())), + }, + ))], + }, + format: Some(DataFormat::Turtle), }, - format: Some(DataFormat::Turtle), - }, - DescribeResponse { - format: DataFormat::Turtle, - data: Binary::from( - " , ;\n\t \"Test\" , \"OKP4\" ;\n\t \"Data Space de test\"@fr , \"Test Data Space\"@en ;\n\t ;\n\t ;\n\t \"OKP4\" ;\n\t \"A test Data Space.\"@en , \"Un Data Space de test.\"@fr .\n , ;\n\t \"test\" ;\n\t \"test Dataset\"@en , \"Dataset de test\"@fr ;\n\t ;\n\t ;\n\t ;\n\t \"Me\" ;\n\t ;\n\t \"OKP4\" ;\n\t \"Un Dataset de test.\"@fr , \"A test Dataset.\"@en .\n".to_string().as_bytes().to_vec()), - } - ), + DescribeResponse { + format: DataFormat::Turtle, + data: Binary::from( + " , ;\n\t \"Test\" , \"OKP4\" ;\n\t \"Data Space de test\"@fr , \"Test Data Space\"@en ;\n\t ;\n\t ;\n\t \"OKP4\" ;\n\t \"A test Data Space.\"@en , \"Un Data Space de test.\"@fr .\n , ;\n\t \"test\" ;\n\t \"test Dataset\"@en , \"Dataset de test\"@fr ;\n\t ;\n\t ;\n\t ;\n\t \"Me\" ;\n\t ;\n\t \"OKP4\" ;\n\t \"Un Dataset de test.\"@fr , \"A test Dataset.\"@en .\n".to_string().as_bytes().to_vec()), + } + ), ]; let mut deps = mock_dependencies(); @@ -1813,33 +2034,33 @@ mod tests { #[test] fn blanknode_describe() { let cases = vec![ - ( - QueryMsg::Describe { - query: DescribeQuery { - 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()} - ], - resource: VarOrNamedNode::Variable("x".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: VarOrNode::Node(NamedNode(Prefixed( - "core:hasTemporalCoverage".to_string(), - ))), - object: VarOrNodeOrLiteral::Variable("x".to_string()), - }, - )), - ], + ( + QueryMsg::Describe { + query: DescribeQuery { + 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() }, + ], + resource: VarOrNamedNode::Variable("x".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: VarOrNode::Node(NamedNode(Prefixed( + "core:hasTemporalCoverage".to_string(), + ))), + object: VarOrNodeOrLiteral::Variable("x".to_string()), + }, + )), + ], + }, + format: Some(DataFormat::Turtle), }, - format: Some(DataFormat::Turtle), - }, - DescribeResponse { - format: DataFormat::Turtle, - data: Binary::from( - " , ;\n\t \"2022-01-01T00:00:00+00:00\"^^ .\n".to_string().as_bytes().to_vec()), - } - ), + DescribeResponse { + format: DataFormat::Turtle, + data: Binary::from( + " , ;\n\t \"2022-01-01T00:00:00+00:00\"^^ .\n".to_string().as_bytes().to_vec()), + } + ), ]; let mut deps = mock_dependencies(); @@ -1883,58 +2104,58 @@ mod tests { 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()), - }, - )]; + 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()), + }, + )]; for (q, expected) in cases { let mut deps = mock_dependencies(); diff --git a/contracts/okp4-cognitarium/src/querier/engine.rs b/contracts/okp4-cognitarium/src/querier/engine.rs index 515e68b8..dc4617da 100644 --- a/contracts/okp4-cognitarium/src/querier/engine.rs +++ b/contracts/okp4-cognitarium/src/querier/engine.rs @@ -219,6 +219,7 @@ struct TriplePatternIterator<'a> { } type TriplePatternFilters = (Option, Option, Option); +type TriplePatternBlankFilters = (bool, bool); type TriplePatternBindings = (Option, Option, Option); impl<'a> TriplePatternIterator<'a> { @@ -229,13 +230,13 @@ impl<'a> TriplePatternIterator<'a> { predicate: PatternValue, object: PatternValue, ) -> Self { - if let Some((filters, output_bindings)) = + if let Some((filters, blank_filters, output_bindings)) = Self::compute_iter_io(&input, subject, predicate, object) { return Self { input, output_bindings, - triple_iter: Self::make_state_iter(storage, filters), + triple_iter: Self::make_state_iter(storage, filters, blank_filters), }; } @@ -249,7 +250,14 @@ impl<'a> TriplePatternIterator<'a> { fn make_state_iter( storage: &'a dyn Storage, filters: TriplePatternFilters, + blank_filters: (bool, bool), ) -> Box> + 'a> { + let post_filter = move |t: &Triple| { + let s = !blank_filters.0 || matches!(t.subject, Subject::Blank(_)); + let o = !blank_filters.1 || matches!(t.object, Object::Blank(_)); + o && s + }; + match filters { (Some(s), Some(p), Some(o)) => { let res = triples().load(storage, (o.as_hash().as_bytes(), p.key(), s.key())); @@ -264,12 +272,20 @@ impl<'a> TriplePatternIterator<'a> { .subject_and_predicate .prefix((s.key(), p.key())) .range(storage, None, None, Order::Ascending) + .filter(move |res| match res { + Ok((_, triple)) => post_filter(triple), + Err(_) => true, + }) .map(|res| res.map(|(_, t)| t)), ), (None, Some(p), Some(o)) => Box::new( triples() .prefix((o.as_hash().as_bytes(), p.key())) .range(storage, None, None, Order::Ascending) + .filter(move |res| match res { + Ok((_, triple)) => post_filter(triple), + Err(_) => true, + }) .map(|res| res.map(|(_, t)| t)), ), (Some(s), None, Some(o)) => Box::new( @@ -279,7 +295,7 @@ impl<'a> TriplePatternIterator<'a> { .sub_prefix(s.key()) .range(storage, None, None, Order::Ascending) .filter(move |res| match res { - Ok((_, triple)) => triple.object == o, + Ok((_, triple)) => triple.object == o && post_filter(triple), Err(_) => true, }) .map(|res| res.map(|(_, t)| t)), @@ -290,13 +306,17 @@ impl<'a> TriplePatternIterator<'a> { .subject_and_predicate .sub_prefix(s.key()) .range(storage, None, None, Order::Ascending) + .filter(move |res| match res { + Ok((_, triple)) => post_filter(triple), + Err(_) => true, + }) .map(|res| res.map(|(_, t)| t)), ), (None, Some(p), None) => Box::new( triples() .range(storage, None, None, Order::Ascending) .filter(move |res| match res { - Ok((_, triple)) => triple.predicate == p, + Ok((_, triple)) => triple.predicate == p && post_filter(triple), Err(_) => true, }) .map(|res| res.map(|(_, t)| t)), @@ -305,11 +325,19 @@ impl<'a> TriplePatternIterator<'a> { triples() .sub_prefix(o.as_hash().as_bytes()) .range(storage, None, None, Order::Ascending) + .filter(move |res| match res { + Ok((_, triple)) => post_filter(triple), + Err(_) => true, + }) .map(|res| res.map(|(_, t)| t)), ), (None, None, None) => Box::new( triples() .range(storage, None, None, Order::Ascending) + .filter(move |res| match res { + Ok((_, triple)) => post_filter(triple), + Err(_) => true, + }) .map(|res| res.map(|(_, t)| t)), ), } @@ -320,56 +348,82 @@ impl<'a> TriplePatternIterator<'a> { subject: PatternValue, predicate: PatternValue, object: PatternValue, - ) -> Option<(TriplePatternFilters, TriplePatternBindings)> { - let (s_filter, s_bind) = + ) -> Option<( + TriplePatternFilters, + TriplePatternBlankFilters, + TriplePatternBindings, + )> { + let (s_filter, sb_filter, s_bind) = Self::resolve_pattern_part(subject, ResolvedVariable::as_subject, input)?; - let (p_filter, p_bind) = + let (p_filter, pb_filter, p_bind) = Self::resolve_pattern_part(predicate, ResolvedVariable::as_predicate, input)?; - let (o_filter, o_bind) = + let (o_filter, ob_filter, o_bind) = Self::resolve_pattern_part(object, ResolvedVariable::as_object, input)?; - Some(((s_filter, p_filter, o_filter), (s_bind, p_bind, o_bind))) + if pb_filter { + None?; + } + + Some(( + (s_filter, p_filter, o_filter), + (sb_filter, ob_filter), + (s_bind, p_bind, o_bind), + )) } fn resolve_pattern_part( pattern_part: PatternValue, map_fn: M, input: &ResolvedVariables, - ) -> Option<(Option, Option)> + ) -> Option<(Option, bool, Option)> where M: FnOnce(&ResolvedVariable) -> Option, { Some(match pattern_part { - PatternValue::Constant(s) => (Some(s), None), + PatternValue::Constant(s) => (Some(s), false, None), + PatternValue::BlankVariable(v) => match input.get(v) { + Some(var) => (Some(map_fn(var)?), false, None), + None => (None, true, Some(v)), + }, PatternValue::Variable(v) => match input.get(v) { - Some(var) => (Some(map_fn(var)?), None), - None => (None, Some(v)), + Some(var) => (Some(map_fn(var)?), false, None), + None => (None, false, Some(v)), }, }) } + + fn map_triple(&self, triple: Triple) -> Option { + let mut vars: ResolvedVariables = self.input.clone(); + + if let Some(v) = self.output_bindings.0 { + vars.merge_index(v, ResolvedVariable::Subject(triple.subject))?; + } + if let Some(v) = self.output_bindings.1 { + vars.merge_index(v, ResolvedVariable::Predicate(triple.predicate))?; + } + if let Some(v) = self.output_bindings.2 { + vars.merge_index(v, ResolvedVariable::Object(triple.object))?; + } + + Some(vars) + } } impl<'a> Iterator for TriplePatternIterator<'a> { type Item = StdResult; fn next(&mut self) -> Option { - self.triple_iter.next().map(|res| { - res.map(|triple| -> ResolvedVariables { - let mut vars: ResolvedVariables = self.input.clone(); + let next = self.triple_iter.next()?; - if let Some(v) = self.output_bindings.0 { - vars.set(v, ResolvedVariable::Subject(triple.subject)); - } - if let Some(v) = self.output_bindings.1 { - vars.set(v, ResolvedVariable::Predicate(triple.predicate)); - } - if let Some(v) = self.output_bindings.2 { - vars.set(v, ResolvedVariable::Object(triple.object)); - } + let maybe_next = match next { + Ok(triple) => self.map_triple(triple).map(Ok), + Err(e) => Some(Err(e)), + }; - vars - }) - }) + if maybe_next.is_none() { + return self.next(); + } + maybe_next } } @@ -860,6 +914,7 @@ impl AtomTemplate { mod test { use super::*; use crate::msg::StoreLimitsInput; + use crate::querier::plan::PlanVariable; use crate::state; use crate::state::Object::{Literal, Named}; use crate::state::{Node, Store, StoreStat, NAMESPACE_KEY_INCREMENT, STORE}; @@ -927,7 +982,11 @@ mod test { predicate: PatternValue::Variable(1), object: PatternValue::Variable(2), }, - variables: vec!["v1".to_string(), "v2".to_string(), "v3".to_string()], + variables: vec![ + PlanVariable::Basic("v1".to_string()), + PlanVariable::Basic("v2".to_string()), + PlanVariable::Basic("v3".to_string()), + ], }, selection: vec![SelectItem::Variable("v4".to_string())], expects: Err(StdError::generic_err( @@ -947,7 +1006,7 @@ mod test { }), object: PatternValue::Variable(0), }, - variables: vec!["registrar".to_string()], + variables: vec![PlanVariable::Basic("registrar".to_string())], }, selection: vec![SelectItem::Variable("registrar".to_string())], expects: Ok(( @@ -963,6 +1022,21 @@ mod test { )])], )), }, + TestCase { + plan: QueryPlan { + entrypoint: QueryNode::TriplePattern { + subject: PatternValue::Constant(Subject::Named(state::Node { + namespace: 0, + value: "97ff7e16-c08d-47be-8475-211016c82e33".to_string(), + })), + predicate: PatternValue::Variable(0), + object: PatternValue::Variable(0), + }, + variables: vec![PlanVariable::Basic("v".to_string())], + }, + selection: vec![SelectItem::Variable("v".to_string())], + expects: Ok((vec!["v".to_string()], vec![])), + }, TestCase { plan: QueryPlan { entrypoint: QueryNode::Limit { @@ -977,9 +1051,9 @@ mod test { first: 3, }, variables: vec![ - "subject".to_string(), - "predicate".to_string(), - "object".to_string(), + PlanVariable::Basic("subject".to_string()), + PlanVariable::Basic("predicate".to_string()), + PlanVariable::Basic("object".to_string()), ], }, selection: vec![ @@ -1099,7 +1173,11 @@ mod test { predicate: PatternValue::Variable(1), object: PatternValue::Variable(2), }, - variables: vec!["v1".to_string(), "v2".to_string(), "v3".to_string()], + variables: vec![ + PlanVariable::Basic("v1".to_string()), + PlanVariable::Basic("v2".to_string()), + PlanVariable::Basic("v3".to_string()), + ], }, expects: 40, }, @@ -1113,7 +1191,11 @@ mod test { }), first: 30, }, - variables: vec!["v1".to_string(), "v2".to_string(), "v3".to_string()], + variables: vec![ + PlanVariable::Basic("v1".to_string()), + PlanVariable::Basic("v2".to_string()), + PlanVariable::Basic("v3".to_string()), + ], }, expects: 30, }, @@ -1130,7 +1212,11 @@ mod test { }), first: 30, }, - variables: vec!["v1".to_string(), "v2".to_string(), "v3".to_string()], + variables: vec![ + PlanVariable::Basic("v1".to_string()), + PlanVariable::Basic("v2".to_string()), + PlanVariable::Basic("v3".to_string()), + ], }, expects: 20, }, @@ -1161,7 +1247,10 @@ mod test { )), }), }, - variables: vec!["v1".to_string(), "v2".to_string()], + variables: vec![ + PlanVariable::Basic("v1".to_string()), + PlanVariable::Basic("v2".to_string()), + ], }, expects: 10, }, @@ -1188,7 +1277,10 @@ mod test { object: PatternValue::Variable(1), }), }, - variables: vec!["v1".to_string(), "v2".to_string()], + variables: vec![ + PlanVariable::Basic("v1".to_string()), + PlanVariable::Basic("v2".to_string()), + ], }, expects: 3, }, @@ -1238,13 +1330,13 @@ mod test { let result = ForLoopJoinIterator::new( Box::new(case.left.iter().map(|v| { let mut vars = ResolvedVariables::with_capacity(3); - vars.set(1, ResolvedVariable::Subject(Subject::Blank(v.clone()))); + vars.merge_index(1, ResolvedVariable::Subject(Subject::Blank(v.clone()))); Ok(vars) })), Rc::new(|input| { Box::new(case.right.iter().map(move |v| { let mut vars = input.clone(); - vars.set(2, ResolvedVariable::Subject(Subject::Blank(v.clone()))); + vars.merge_index(2, ResolvedVariable::Subject(Subject::Blank(v.clone()))); Ok(vars) })) }), @@ -1257,8 +1349,8 @@ mod test { .iter() .map(|(v1, v2)| { let mut vars = ResolvedVariables::with_capacity(3); - vars.set(1, ResolvedVariable::Subject(Subject::Blank(v1.clone()))); - vars.set(2, ResolvedVariable::Subject(Subject::Blank(v2.clone()))); + vars.merge_index(1, ResolvedVariable::Subject(Subject::Blank(v1.clone()))); + vars.merge_index(2, ResolvedVariable::Subject(Subject::Blank(v2.clone()))); vars }) .collect(); @@ -1312,13 +1404,13 @@ mod test { .iter() .map(|v| { let mut vars = ResolvedVariables::with_capacity(2); - vars.set(0, ResolvedVariable::Subject(Subject::Blank(v.clone()))); + vars.merge_index(0, ResolvedVariable::Subject(Subject::Blank(v.clone()))); vars }) .collect(), Box::new(case.left.iter().map(|v| { let mut vars = ResolvedVariables::with_capacity(2); - vars.set(1, ResolvedVariable::Subject(Subject::Blank(v.clone()))); + vars.merge_index(1, ResolvedVariable::Subject(Subject::Blank(v.clone()))); Ok(vars) })), VecDeque::new(), @@ -1332,10 +1424,10 @@ mod test { .map(|v| { let mut vars = ResolvedVariables::with_capacity(2); if let Some(val) = v.get(0) { - vars.set(0, ResolvedVariable::Subject(Subject::Blank(val.clone()))); + vars.merge_index(0, ResolvedVariable::Subject(Subject::Blank(val.clone()))); } if let Some(val) = v.get(1) { - vars.set(1, ResolvedVariable::Subject(Subject::Blank(val.clone()))); + vars.merge_index(1, ResolvedVariable::Subject(Subject::Blank(val.clone()))); } vars }) @@ -1355,22 +1447,46 @@ mod test { let t_object = Object::Blank("o".to_string()); let mut variables = ResolvedVariables::with_capacity(6); - variables.set(1, ResolvedVariable::Subject(t_subject.clone())); - variables.set(2, ResolvedVariable::Predicate(t_predicate.clone())); - variables.set(3, ResolvedVariable::Object(t_object.clone())); + variables.merge_index(1, ResolvedVariable::Subject(t_subject.clone())); + variables.merge_index(2, ResolvedVariable::Predicate(t_predicate.clone())); + variables.merge_index(3, ResolvedVariable::Object(t_object.clone())); struct TestCase { subject: PatternValue, predicate: PatternValue, object: PatternValue, - expects: Option<(TriplePatternFilters, TriplePatternBindings)>, + expects: Option<( + TriplePatternFilters, + TriplePatternBlankFilters, + TriplePatternBindings, + )>, } let cases = vec![ TestCase { subject: PatternValue::Variable(0), predicate: PatternValue::Variable(4), object: PatternValue::Variable(5), - expects: Some(((None, None, None), (Some(0), Some(4), Some(5)))), + expects: Some(( + (None, None, None), + (false, false), + (Some(0), Some(4), Some(5)), + )), + }, + TestCase { + subject: PatternValue::BlankVariable(0), + predicate: PatternValue::Variable(4), + object: PatternValue::BlankVariable(5), + expects: Some(( + (None, None, None), + (true, true), + (Some(0), Some(4), Some(5)), + )), + }, + TestCase { + subject: PatternValue::BlankVariable(0), + predicate: PatternValue::BlankVariable(4), + object: PatternValue::BlankVariable(5), + expects: None, }, TestCase { subject: PatternValue::Variable(1), @@ -1378,6 +1494,7 @@ mod test { object: PatternValue::Variable(5), expects: Some(( (Some(t_subject.clone()), None, None), + (false, false), (None, Some(4), Some(5)), )), }, @@ -1387,6 +1504,7 @@ mod test { object: PatternValue::Variable(5), expects: Some(( (Some(t_subject.clone()), Some(t_predicate.clone()), None), + (false, false), (None, None, Some(5)), )), }, @@ -1396,6 +1514,7 @@ mod test { object: PatternValue::Variable(3), expects: Some(( (Some(t_subject), Some(t_predicate), Some(t_object)), + (false, false), (None, None, None), )), }, @@ -1405,6 +1524,7 @@ mod test { object: PatternValue::Variable(5), expects: Some(( (Some(Subject::Blank("o".to_string())), None, None), + (false, false), (None, Some(4), Some(5)), )), }, @@ -1542,7 +1662,8 @@ mod test { for case in cases { assert_eq!( - TriplePatternIterator::make_state_iter(&deps.storage, case.filters).count(), + TriplePatternIterator::make_state_iter(&deps.storage, case.filters, (false, false)) + .count(), case.expects ); } diff --git a/contracts/okp4-cognitarium/src/querier/plan.rs b/contracts/okp4-cognitarium/src/querier/plan.rs index 4882770b..41579240 100644 --- a/contracts/okp4-cognitarium/src/querier/plan.rs +++ b/contracts/okp4-cognitarium/src/querier/plan.rs @@ -10,17 +10,30 @@ pub struct QueryPlan { /// Contains all the query variables, their index in this array are internally used as /// identifiers. - pub variables: Vec, + pub variables: Vec, +} + +#[derive(Eq, PartialEq, Debug, Clone)] +pub enum PlanVariable { + Basic(String), + BlankNode(String), } 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)| { - if it == var_name { - return Some(index); - } - None - }) + self.variables + .iter() + .enumerate() + .find_map(|(index, it)| match it { + PlanVariable::Basic(name) => { + if name == var_name { + return Some(index); + } + None + } + PlanVariable::BlankNode(_) => None, + }) } } @@ -44,7 +57,7 @@ pub enum QueryNode { /// Join two nodes by applying the cartesian product of the nodes variables. /// - /// This should be used when the nodes doesn't have variables in common, and can be seen as a + /// This should be used when the nodes don't have variables in common, and can be seen as a /// full join of disjoint datasets. CartesianProductJoin { left: Box, right: Box }, @@ -101,11 +114,13 @@ impl QueryNode { pub enum PatternValue { Constant(V), Variable(usize), + /// Special variable that is expected to resolve as a blank node. + BlankVariable(usize), } impl PatternValue { pub fn lookup_bound_variable(&self, callback: &mut impl FnMut(usize)) { - if let PatternValue::Variable(v) = self { + if let PatternValue::Variable(v) | PatternValue::BlankVariable(v) = self { callback(*v); } } @@ -117,35 +132,75 @@ mod tests { #[test] fn bound_variables() { - let query = QueryNode::Limit { - first: 20usize, - child: Box::new(QueryNode::Skip { - first: 20usize, - 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), - object: PatternValue::Variable(0usize), - }), - right: Box::new(QueryNode::TriplePattern { - subject: PatternValue::Variable(3usize), - predicate: PatternValue::Variable(1usize), - object: PatternValue::Constant(Object::Blank("_".to_string())), + let cases = vec![ + ( + QueryNode::TriplePattern { + subject: PatternValue::Variable(0usize), + predicate: PatternValue::Variable(1usize), + object: PatternValue::Variable(2usize), + }, + BTreeSet::from([0usize, 1usize, 2usize]), + ), + ( + QueryNode::Noop { + bound_variables: vec![0usize, 1usize], + }, + BTreeSet::from([0usize, 1usize]), + ), + ( + QueryNode::Limit { + first: 20usize, + child: Box::new(QueryNode::Skip { + first: 20usize, + 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), + object: PatternValue::Variable(0usize), + }), + right: Box::new(QueryNode::TriplePattern { + subject: PatternValue::Variable(3usize), + predicate: PatternValue::Variable(1usize), + object: PatternValue::Constant(Object::Blank("_".to_string())), + }), + }), + right: Box::new(QueryNode::TriplePattern { + subject: PatternValue::Variable(0usize), + predicate: PatternValue::Variable(1usize), + object: PatternValue::Variable(2usize), + }), }), }), - right: Box::new(QueryNode::TriplePattern { - subject: PatternValue::Variable(0usize), - predicate: PatternValue::Variable(1usize), - object: PatternValue::Variable(2usize), - }), - }), - }), + }, + BTreeSet::from([0usize, 1usize, 2usize, 3usize, 4usize]), + ), + ]; + + for case in cases { + assert_eq!(case.0.bound_variables(), case.1) + } + } + + #[test] + fn get_var_index() { + let plan = QueryPlan { + entrypoint: QueryNode::TriplePattern { + subject: PatternValue::Variable(0usize), + predicate: PatternValue::Variable(1usize), + object: PatternValue::BlankVariable(2usize), + }, + variables: vec![ + PlanVariable::Basic("1".to_string()), + PlanVariable::Basic("2".to_string()), + PlanVariable::BlankNode("3".to_string()), + ], }; - assert_eq!( - query.bound_variables(), - BTreeSet::from([0usize, 1usize, 2usize, 3usize, 4usize]) - ) + assert_eq!(plan.get_var_index("1"), Some(0usize)); + assert_eq!(plan.get_var_index("2"), Some(1usize)); + assert_eq!(plan.get_var_index("3"), None); } } diff --git a/contracts/okp4-cognitarium/src/querier/plan_builder.rs b/contracts/okp4-cognitarium/src/querier/plan_builder.rs index 9b094aed..52da74a1 100644 --- a/contracts/okp4-cognitarium/src/querier/plan_builder.rs +++ b/contracts/okp4-cognitarium/src/querier/plan_builder.rs @@ -1,10 +1,9 @@ use crate::msg::{ - SimpleWhereCondition, TriplePattern, VarOrNode, VarOrNodeOrLiteral, WhereClause, WhereCondition, + Node, SimpleWhereCondition, TriplePattern, VarOrNode, VarOrNodeOrLiteral, WhereClause, + WhereCondition, }; -use crate::querier::mapper::{ - literal_as_object, node_as_object, node_as_predicate, node_as_subject, -}; -use crate::querier::plan::{PatternValue, QueryNode, QueryPlan}; +use crate::querier::mapper::{iri_as_node, literal_as_object, node_as_predicate}; +use crate::querier::plan::{PatternValue, PlanVariable, QueryNode, QueryPlan}; use crate::state::{HasCachedNamespaces, Namespace, NamespaceResolver, Object, Predicate, Subject}; use cosmwasm_std::{StdError, StdResult, Storage}; use std::collections::HashMap; @@ -13,7 +12,7 @@ pub struct PlanBuilder<'a> { storage: &'a dyn Storage, ns_resolver: NamespaceResolver, prefixes: &'a HashMap, - variables: Vec, + variables: Vec, limit: Option, skip: Option, } @@ -143,19 +142,19 @@ impl<'a> PlanBuilder<'a> { fn build_subject_pattern(&mut self, value: VarOrNode) -> StdResult> { Ok(match value { - VarOrNode::Variable(v) => PatternValue::Variable(self.resolve_variable(v)), - VarOrNode::Node(n) => PatternValue::Constant(node_as_subject( - &mut self.ns_resolver, - self.storage, - self.prefixes, - n, - )?), + VarOrNode::Variable(v) => PatternValue::Variable(self.resolve_basic_variable(v)), + VarOrNode::Node(Node::BlankNode(b)) => { + PatternValue::BlankVariable(self.resolve_blank_variable(b)) + } + VarOrNode::Node(Node::NamedNode(iri)) => PatternValue::Constant(Subject::Named( + iri_as_node(&mut self.ns_resolver, self.storage, self.prefixes, iri)?, + )), }) } fn build_predicate_pattern(&mut self, value: VarOrNode) -> StdResult> { Ok(match value { - VarOrNode::Variable(v) => PatternValue::Variable(self.resolve_variable(v)), + VarOrNode::Variable(v) => PatternValue::Variable(self.resolve_basic_variable(v)), VarOrNode::Node(n) => PatternValue::Constant(node_as_predicate( &mut self.ns_resolver, self.storage, @@ -170,13 +169,20 @@ impl<'a> PlanBuilder<'a> { value: VarOrNodeOrLiteral, ) -> StdResult> { Ok(match value { - VarOrNodeOrLiteral::Variable(v) => PatternValue::Variable(self.resolve_variable(v)), - VarOrNodeOrLiteral::Node(n) => PatternValue::Constant(node_as_object( - &mut self.ns_resolver, - self.storage, - self.prefixes, - n, - )?), + VarOrNodeOrLiteral::Variable(v) => { + PatternValue::Variable(self.resolve_basic_variable(v)) + } + VarOrNodeOrLiteral::Node(Node::BlankNode(b)) => { + PatternValue::BlankVariable(self.resolve_blank_variable(b)) + } + VarOrNodeOrLiteral::Node(Node::NamedNode(iri)) => { + PatternValue::Constant(Object::Named(iri_as_node( + &mut self.ns_resolver, + self.storage, + self.prefixes, + iri, + )?)) + } VarOrNodeOrLiteral::Literal(l) => PatternValue::Constant(literal_as_object( &mut self.ns_resolver, self.storage, @@ -186,12 +192,27 @@ impl<'a> PlanBuilder<'a> { }) } - fn resolve_variable(&mut self, v: String) -> usize { - if let Some(index) = self.variables.iter().position(|name| name == &v) { + fn resolve_basic_variable(&mut self, v: String) -> usize { + if let Some(index) = self.variables.iter().position(|var| match var { + PlanVariable::Basic(name) => name == &v, + PlanVariable::BlankNode(_) => false, + }) { return index; } - self.variables.push(v); + self.variables.push(PlanVariable::Basic(v)); + self.variables.len() - 1 + } + + fn resolve_blank_variable(&mut self, v: String) -> usize { + if let Some(index) = self.variables.iter().position(|var| match var { + PlanVariable::BlankNode(name) => name == &v, + PlanVariable::Basic(_) => false, + }) { + return index; + } + + self.variables.push(PlanVariable::BlankNode(v)); self.variables.len() - 1 } } @@ -275,7 +296,7 @@ mod test { let builder = PlanBuilder::new(&deps.storage, prefixes, None); assert_eq!(builder.skip, None); assert_eq!(builder.limit, None); - assert_eq!(builder.variables, Vec::::new()); + assert_eq!(builder.variables, Vec::::new()); assert_eq!(builder.prefixes, &case.1); } @@ -307,19 +328,19 @@ mod test { ), ( TriplePattern { - subject: VarOrNode::Node(Node::BlankNode("_".to_string())), + subject: VarOrNode::Node(Node::BlankNode("1".to_string())), predicate: VarOrNode::Node(Node::NamedNode(IRI::Full( "http://okp4.space/hasTitle".to_string(), ))), - object: VarOrNodeOrLiteral::Node(Node::BlankNode("_".to_string())), + object: VarOrNodeOrLiteral::Node(Node::BlankNode("2".to_string())), }, Ok(QueryNode::TriplePattern { - subject: PatternValue::Constant(Subject::Blank("_".to_string())), + subject: PatternValue::BlankVariable(0usize), predicate: PatternValue::Constant(state::Node { namespace: 0u128, value: "hasTitle".to_string(), }), - object: PatternValue::Constant(Object::Blank("_".to_string())), + object: PatternValue::BlankVariable(1usize), }), ), ( @@ -337,7 +358,7 @@ mod test { namespace: 0u128, value: "123456789".to_string(), })), - predicate: PatternValue::Variable(1usize), + predicate: PatternValue::Variable(0usize), object: PatternValue::Constant(Object::Named(state::Node { namespace: 0u128, value: "1234567892".to_string(), @@ -351,8 +372,8 @@ mod test { object: VarOrNodeOrLiteral::Literal(Literal::Simple("simple".to_string())), }, Ok(QueryNode::TriplePattern { - subject: PatternValue::Variable(1usize), - predicate: PatternValue::Variable(0usize), + subject: PatternValue::Variable(0usize), + predicate: PatternValue::Variable(1usize), object: PatternValue::Constant(Object::Literal(state::Literal::Simple { value: "simple".to_string(), })), @@ -406,7 +427,7 @@ mod test { object: VarOrNodeOrLiteral::Variable("o".to_string()), }, Ok(QueryNode::Noop { - bound_variables: vec![1usize, 2usize], + bound_variables: vec![0usize, 1usize], }), ), ( @@ -418,7 +439,7 @@ mod test { object: VarOrNodeOrLiteral::Variable("o".to_string()), }, Ok(QueryNode::Noop { - bound_variables: vec![0usize, 2usize], + bound_variables: vec![0usize, 1usize], }), ), ( @@ -447,10 +468,10 @@ mod test { }, ) .unwrap(); - let prefixes = &PrefixMap::default().into_inner(); - let mut builder = PlanBuilder::new(&deps.storage, prefixes, None); - for case in cases { + let prefixes = &PrefixMap::default().into_inner(); + let mut builder = PlanBuilder::new(&deps.storage, prefixes, None); + assert_eq!(builder.build_triple_pattern(&case.0), case.1); } } @@ -490,7 +511,10 @@ mod test { entrypoint: QueryNode::Noop { bound_variables: vec![0usize, 1usize], }, - variables: vec!["predicate".to_string(), "object".to_string()], + variables: vec![ + PlanVariable::Basic("predicate".to_string()), + PlanVariable::Basic("object".to_string()), + ], }), ), ( @@ -508,9 +532,9 @@ mod test { object: PatternValue::Variable(2usize), }, variables: vec![ - "subject".to_string(), - "predicate".to_string(), - "object".to_string(), + PlanVariable::Basic("subject".to_string()), + PlanVariable::Basic("predicate".to_string()), + PlanVariable::Basic("object".to_string()), ], }), ), @@ -532,9 +556,9 @@ mod test { }), }, variables: vec![ - "subject".to_string(), - "predicate".to_string(), - "object".to_string(), + PlanVariable::Basic("subject".to_string()), + PlanVariable::Basic("predicate".to_string()), + PlanVariable::Basic("object".to_string()), ], }), ), @@ -556,9 +580,9 @@ mod test { }), }, variables: vec![ - "subject".to_string(), - "predicate".to_string(), - "object".to_string(), + PlanVariable::Basic("subject".to_string()), + PlanVariable::Basic("predicate".to_string()), + PlanVariable::Basic("object".to_string()), ], }), ), @@ -583,9 +607,9 @@ mod test { }), }, variables: vec![ - "subject".to_string(), - "predicate".to_string(), - "object".to_string(), + PlanVariable::Basic("subject".to_string()), + PlanVariable::Basic("predicate".to_string()), + PlanVariable::Basic("object".to_string()), ], }), ), @@ -626,16 +650,53 @@ mod test { right: Box::new(QueryNode::TriplePattern { subject: PatternValue::Variable(0usize), predicate: PatternValue::Variable(4usize), - object: PatternValue::Constant(Object::Blank("blank".to_string())), + object: PatternValue::BlankVariable(6usize), + }), + }, + variables: vec![ + PlanVariable::Basic("var1".to_string()), + PlanVariable::Basic("var2".to_string()), + PlanVariable::Basic("var3".to_string()), + PlanVariable::Basic("var4".to_string()), + PlanVariable::Basic("var5".to_string()), + PlanVariable::Basic("var6".to_string()), + PlanVariable::BlankNode("blank".to_string()), + ], + }), + ), + ( + None, + None, + vec![ + TriplePattern { + subject: VarOrNode::Node(Node::BlankNode("1".to_string())), + predicate: VarOrNode::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()), + object: VarOrNodeOrLiteral::Variable("2".to_string()), + }, + ], + Ok(QueryPlan { + entrypoint: QueryNode::ForLoopJoin { + left: Box::new(QueryNode::TriplePattern { + subject: PatternValue::BlankVariable(0usize), + predicate: PatternValue::Variable(1usize), + object: PatternValue::BlankVariable(2usize), + }), + right: Box::new(QueryNode::TriplePattern { + subject: PatternValue::BlankVariable(0usize), + predicate: PatternValue::Variable(1usize), + object: PatternValue::Variable(3usize), }), }, variables: vec![ - "var1".to_string(), - "var2".to_string(), - "var3".to_string(), - "var4".to_string(), - "var5".to_string(), - "var6".to_string(), + PlanVariable::BlankNode("1".to_string()), + PlanVariable::Basic("1".to_string()), + PlanVariable::BlankNode("2".to_string()), + PlanVariable::Basic("2".to_string()), ], }), ), diff --git a/contracts/okp4-cognitarium/src/querier/variable.rs b/contracts/okp4-cognitarium/src/querier/variable.rs index 163f4e4b..d9a37f47 100644 --- a/contracts/okp4-cognitarium/src/querier/variable.rs +++ b/contracts/okp4-cognitarium/src/querier/variable.rs @@ -131,8 +131,13 @@ impl ResolvedVariables { Some(Self { variables: merged }) } - pub fn set(&mut self, index: usize, var: ResolvedVariable) { - self.variables[index] = Some(var); + pub fn merge_index(&mut self, index: usize, var: ResolvedVariable) -> Option<()> { + if let Some(old) = self.get(index) { + (*old == var).then_some(()) + } else { + self.variables[index] = Some(var); + Some(()) + } } pub fn get(&self, index: usize) -> &Option { @@ -295,21 +300,21 @@ mod tests { #[test] fn merged_variables() { let mut vars1 = ResolvedVariables::with_capacity(3); - vars1.set( + vars1.merge_index( 0, ResolvedVariable::Object(Object::Blank("foo".to_string())), ); - vars1.set( + vars1.merge_index( 2, ResolvedVariable::Object(Object::Blank("bar".to_string())), ); let mut vars2 = ResolvedVariables::with_capacity(3); - vars2.set( + vars2.merge_index( 1, ResolvedVariable::Object(Object::Blank("pop".to_string())), ); - vars2.set( + vars2.merge_index( 2, ResolvedVariable::Object(Object::Blank("bar".to_string())), ); @@ -321,15 +326,15 @@ mod tests { assert_eq!(vars1.get(1), &None); let mut expected_result = ResolvedVariables::with_capacity(3); - expected_result.set( + expected_result.merge_index( 0, ResolvedVariable::Object(Object::Blank("foo".to_string())), ); - expected_result.set( + expected_result.merge_index( 1, ResolvedVariable::Object(Object::Blank("pop".to_string())), ); - expected_result.set( + expected_result.merge_index( 2, ResolvedVariable::Object(Object::Blank("bar".to_string())), ); @@ -338,11 +343,11 @@ mod tests { assert_eq!(result, Some(expected_result)); let mut vars3 = ResolvedVariables::with_capacity(3); - vars3.set( + vars3.merge_index( 1, ResolvedVariable::Object(Object::Blank("pop".to_string())), ); - vars3.set( + vars3.merge_index( 2, ResolvedVariable::Predicate(Node { namespace: 0, diff --git a/contracts/okp4-cognitarium/testdata/blank-nodes.ttl b/contracts/okp4-cognitarium/testdata/blank-nodes.ttl index 7e621126..1c847229 100644 --- a/contracts/okp4-cognitarium/testdata/blank-nodes.ttl +++ b/contracts/okp4-cognitarium/testdata/blank-nodes.ttl @@ -28,6 +28,9 @@ a owl:NamedIndividual, core:Period ; core:hasStartDate "2022-01-01T00:00:00+00:00"^^xsd:dateTime ] ; + core:hasInformations [ + core:hasInformation "this is a dataset" + ] ; core:hasTitle "ADMIN EXPRESS COG 2022 COMMUNE"@fr ; core:hasTitle "ADMIN EXPRESS COG 2022 CITY"@en ; core:hasTitle "ADMIN EXPRESS COG 2022 GEMEINDE"@de ; diff --git a/contracts/okp4-dataverse/src/registrar/credential.rs b/contracts/okp4-dataverse/src/registrar/credential.rs index be95b4ad..c0d4e1c4 100644 --- a/contracts/okp4-dataverse/src/registrar/credential.rs +++ b/contracts/okp4-dataverse/src/registrar/credential.rs @@ -29,7 +29,7 @@ impl<'a> DataverseCredential<'a> { "credential is expected to have exactly one type".to_string(), ) }) - .map(|t| *t) + .copied() } fn extract_vc_claim(