This commit is contained in:
2026-08-16 15:50:10 -04:00
parent 8dcaa5b61f
commit 72a39e5baa
17 changed files with 290 additions and 182 deletions
+1
View File
@@ -9,5 +9,6 @@ gl-search.workspace = true
oxigraph.workspace = true
oxilangtag.workspace = true
rayon.workspace = true
thiserror.workspace = true
tracing.workspace = true
-1
View File
@@ -1,4 +1,3 @@
use oxigraph::model::{GraphNameRef, NamedNodeRef};
use std::collections::BTreeMap;
use std::sync::LazyLock;
+6
View File
@@ -16,6 +16,12 @@ pub enum Error {
#[error(transparent)]
QueryEvaluation(#[from] oxigraph::sparql::QueryEvaluationError),
#[error(transparent)]
QueryResultsSyntax(#[from] oxigraph::sparql::results::QueryResultsSyntaxError),
#[error(transparent)]
UpdateEvaluation(#[from] oxigraph::sparql::UpdateEvaluationError),
#[error(transparent)]
TonicStatus(#[from] gl_inference::tonic::Status),
}
-8
View File
@@ -10,14 +10,6 @@ pub(crate) fn term_to_named_node(term: &Term) -> Option<&NamedNode> {
}
}
pub(crate) fn term_ref_as_named_node(term: TermRef<'_>) -> Option<NamedNodeRef<'_>> {
if let TermRef::NamedNode(node) = term {
Some(node)
} else {
None
}
}
pub(crate) fn term_as_str(term: &Term) -> Option<&str> {
if let Term::Literal(literal) = term {
match literal.datatype() {
-95
View File
@@ -1,95 +0,0 @@
use std::collections::HashMap;
use oxigraph::model::NamedNode;
use oxigraph::sparql::results::{QueryResultsFormat, QueryResultsParser, SliceQueryResultsParserOutput};
use gl_inference::tonic::transport::Channel;
use gl_inference::proto::ontology_client::OntologyClient;
use gl_inference::proto::OntologyQueryRequest;
use gl_search::{Field, OwnedValue, Schema};
use crate::class::Class;
use crate::{curie, CurieHelper};
use crate::helpers::{term_as_str, term_to_named_node};
use crate::language::LanguageCondition;
pub async fn index_ontology(
client: &mut OntologyClient<Channel>,
language: &LanguageCondition,
) -> crate::Result<Vec<HashMap<Field, OwnedValue>>> {
let curie_helper = CurieHelper::new((&*curie::PREFIXES).clone());
let label_filter = language.to_filter_expression("label");
let description_filter = language.to_filter_expression("description");
let mut request = OntologyQueryRequest::default();
request.sparql_query = Some(format!(r#"PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?class ?subject ?label ?description WHERE {{
VALUES ?class {{ rdf:Property rdfs:Class skos:Concept }}
?subject a ?class ;
rdfs:label ?label .
{label_filter}
OPTIONAL {{ ?subject rdfs:comment ?comment }}
OPTIONAL {{ ?subject skos:definition ?definition }}
BIND(COALESCE(?definition, ?comment) AS ?description)
{description_filter}
}}"#));
let response = client.query(request).await.unwrap();
let parser_output = QueryResultsParser::from_format(QueryResultsFormat::Json)
.for_slice(&response.get_ref().results)
.unwrap();
let mut results = Vec::new();
if let SliceQueryResultsParserOutput::Solutions(solutions) = parser_output {
let primary_language = language.primary_language();
let label_field = Schema::field("label", primary_language);
let definition_field = Schema::field("definition", primary_language);
for solution in solutions.filter_map(Result::ok) {
let mut document = HashMap::with_capacity(4);
let discriminant = solution
.get("class")
.and_then(term_to_named_node)
.and_then(Class::try_from_named_node)
.map(|doc_type| doc_type as u64)
.map(OwnedValue::U64)
.unwrap_or(OwnedValue::Null);
document.insert(Schema::discriminant_field(), discriminant);
let subject_str = solution
.get("subject")
.and_then(term_to_named_node)
.map(NamedNode::as_str);
let curie = subject_str
.and_then(|subject| curie_helper.abbreviate(None, subject))
.map(OwnedValue::Str)
.unwrap_or(OwnedValue::Null);
document.insert(Schema::curie_field(), curie);
let subject = subject_str
.map(OwnedValue::from)
.unwrap_or(OwnedValue::Null);
document.insert(Schema::iri_field(), subject);
let label = solution
.get("label")
.and_then(term_as_str)
.map(OwnedValue::from)
.unwrap_or(OwnedValue::Null);
document.insert(label_field, label);
let definition = solution
.get("description")
.and_then(term_as_str)
.map(OwnedValue::from)
.unwrap_or(OwnedValue::Null);
document.insert(definition_field, definition);
results.push(document);
}
}
Ok(results)
}
+175
View File
@@ -0,0 +1,175 @@
use rayon::iter::ParallelIterator;
use std::collections::{HashMap, HashSet};
use oxigraph::model::{Graph, NamedNode, TermRef, NamedOrBlankNodeRef, NamedNodeRef};
use oxigraph::sparql::results::{QueryResultsFormat, QueryResultsParser, SliceQueryResultsParserOutput};
use rayon::iter::IntoParallelRefIterator;
use gl_inference::tonic::transport::Channel;
use gl_inference::proto::ontology_client::OntologyClient;
use gl_inference::proto::OntologyQueryRequest;
use gl_search::{Field, OwnedValue, Schema};
use crate::class::Class;
use crate::{helpers, vocab, CurieHelper};
use crate::helpers::{term_as_str, term_to_named_node};
use crate::language::LanguageCondition;
pub struct Indexer<'a> {
language: LanguageCondition,
curie_helper: &'a CurieHelper,
}
impl<'a> Indexer<'a> {
pub fn new(language: LanguageCondition, curie_helper: &'a CurieHelper) -> Self {
Self {
language,
curie_helper,
}
}
fn extract_string(&self, graph: &Graph, subject: NamedNodeRef, predicate: NamedNodeRef) -> OwnedValue {
graph.object_for_subject_predicate(subject, predicate)
.filter(|term| self.language.primary_matches_term(*term))
.and_then(helpers::term_ref_as_str)
.map(String::from)
.map(OwnedValue::Str)
.unwrap_or_else(|| OwnedValue::Null)
}
fn person(&self, graph: &Graph, subject: NamedNodeRef) -> HashMap<Field, OwnedValue> {
HashMap::from_iter([
(
Schema::field("given_name", self.language.primary_language()),
self.extract_string(graph, subject, vocab::rdaad::GIVEN_NAME),
),
(
Schema::field("surname", self.language.primary_language()),
self.extract_string(graph, subject, vocab::rdaad::SURNAME),
),
])
}
fn corporate_body(&self, graph: &Graph, subject: NamedNodeRef) -> HashMap<Field, OwnedValue> {
HashMap::from_iter([
(
Schema::field("corporate_name", self.language.primary_language()),
self.extract_string(graph, subject, vocab::rdaad::NAME_OF_CORPORATE_BODY),
),
])
}
pub fn graph(&self, graph: &Graph) -> crate::Result<Vec<HashMap<Field, OwnedValue>>> {
let mut entities_and_types = HashSet::new();
let triples = graph.triples_for_predicate(vocab::rdf::TYPE);
for triple in triples {
if let NamedOrBlankNodeRef::NamedNode(subject) = triple.subject &&
let TermRef::NamedNode(class) = triple.object {
entities_and_types.insert((subject, class));
}
}
let results = entities_and_types.par_iter()
.filter_map(|(subject, class)| {
Class::try_from_named_node(*class).and_then(|class| {
match class {
Class::RdfProperty => None,
Class::RdfsClass => None,
Class::SkosConcept => None,
Class::Work => Some(self.person(graph, *subject)),
Class::Person => Some(self.person(graph, *subject)),
Class::CorporateBody => Some(self.corporate_body(graph, *subject)),
Class::Expression => Some(self.person(graph, *subject)),
Class::Manifestation => Some(self.person(graph, *subject)),
}.map(|mut document| {
document.insert(Schema::discriminant_field(), OwnedValue::from(class as u64));
document.insert(Schema::iri_field(), OwnedValue::Str(subject.as_str().to_string()));
let curie = self.curie_helper.abbreviate(None, subject.as_str())
.map(OwnedValue::Str)
.unwrap_or(OwnedValue::Null);
document.insert(Schema::curie_field(), curie);
document
})
})
}).collect();
Ok(results)
}
pub async fn ontology(&self, client: &mut OntologyClient<Channel>) -> crate::Result<Vec<HashMap<Field, OwnedValue>>> {
let label_filter = self.language.to_filter_expression("label");
let description_filter = self.language.to_filter_expression("description");
let mut request = OntologyQueryRequest::default();
request.sparql_query = Some(format!(r#"PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?class ?subject ?label ?description WHERE {{
VALUES ?class {{ rdf:Property rdfs:Class skos:Concept }}
?subject a ?class ;
rdfs:label ?label .
{label_filter}
OPTIONAL {{ ?subject rdfs:comment ?comment }}
OPTIONAL {{ ?subject skos:definition ?definition }}
BIND(COALESCE(?definition, ?comment) AS ?description)
{description_filter}
}}"#));
let response = client.query(request).await?;
let parser_output = QueryResultsParser::from_format(QueryResultsFormat::Json)
.for_slice(&response.get_ref().results)?;
let mut results = Vec::new();
if let SliceQueryResultsParserOutput::Solutions(solutions) = parser_output {
let primary_language = self.language.primary_language();
let label_field = Schema::field("label", primary_language);
let definition_field = Schema::field("definition", primary_language);
for solution in solutions.filter_map(Result::ok) {
let mut document = HashMap::with_capacity(4);
let discriminant = solution
.get("class")
.and_then(term_to_named_node)
.and_then(Class::try_from_named_node)
.map(|doc_type| doc_type as u64)
.map(OwnedValue::U64)
.unwrap_or(OwnedValue::Null);
document.insert(Schema::discriminant_field(), discriminant);
let subject_str = solution
.get("subject")
.and_then(term_to_named_node)
.map(NamedNode::as_str);
let curie = subject_str
.and_then(|subject| self.curie_helper.abbreviate(None, subject))
.map(OwnedValue::Str)
.unwrap_or(OwnedValue::Null);
document.insert(Schema::curie_field(), curie);
let subject = subject_str
.map(OwnedValue::from)
.unwrap_or(OwnedValue::Null);
document.insert(Schema::iri_field(), subject);
let label = solution
.get("label")
.and_then(term_as_str)
.map(OwnedValue::from)
.unwrap_or(OwnedValue::Null);
document.insert(label_field, label);
let definition = solution
.get("description")
.and_then(term_as_str)
.map(OwnedValue::from)
.unwrap_or(OwnedValue::Null);
document.insert(definition_field, definition);
results.push(document);
}
}
Ok(results)
}
}
+1
View File
@@ -10,6 +10,7 @@ pub static ENGLISH_OR_UNTAGGED: LazyLock<LanguageCondition> = LazyLock::new(|| {
)
});
#[derive(Clone)]
pub enum LanguageCondition {
ExactMatchOnly(LanguageTag<String>),
ExactMatchOrUntagged(LanguageTag<String>),
+2 -3
View File
@@ -1,5 +1,4 @@
mod curie;
//mod materialize;
mod error;
pub mod vocab;
@@ -7,7 +6,7 @@ pub mod class;
pub mod category;
pub mod language;
mod helpers;
pub mod index;
pub mod indexer;
pub use curie::{CurieHelper, PREFIXES};
pub use error::{Error, Result};
pub use error::{Error, Result};