mod descriptions;
mod members;
use std::fmt;
use concept_graph::attributes::{Row, ValueRef};
use concept_graph::ordinal::Ordinal;
use roaring::RoaringBitmap;
use self::matching::{
AttributeTest, CompiledItem, CompiledSet, Kinds, ValueTest, matching_rows, number,
relationship_groups, set_holds, within,
};
use crate::ast::{
Acceptability, Attribute, AttributeSet, AttributeValue, Cardinality, Comparison,
ConstraintOperator, Equality, ExpressionConstraint, FilterConstraint, FocusConcept, Refinement,
RefsetOperator, Sctid, SubAttributeSet, SubExpressionConstraint, SubRefinement,
TypedSearchTerm,
};
mod matching;
pub const HISTORICAL_ASSOCIATION: u64 = 900_000_000_000_522_004;
pub const SAME_AS: u64 = 900_000_000_000_527_005;
pub const REPLACED_BY: u64 = 900_000_000_000_526_001;
pub const WAS_A: u64 = 900_000_000_000_528_000;
pub const PARTIALLY_EQUIVALENT_TO: u64 = 1_186_924_009;
const REFERENCED_COMPONENT: &str = "referencedComponentId";
const TARGET_COMPONENT: &str = "targetComponentId";
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum EvalError {
#[error("the edition has no concept {0}")]
UnknownConcept(Sctid),
#[error("{0} is not a reference set with members in the edition")]
NotAReferenceSet(Sctid),
#[error("`{0}` is not an identifier scheme alias of the edition")]
UnknownScheme(String),
#[error("no concept has the alternate identifier {scheme}#{code}")]
UnknownIdentifier {
scheme: String,
code: String,
},
#[error("reference set {refset} has no field `{field}`")]
UnknownField {
refset: Sctid,
field: String,
},
#[error("{0} is not supported")]
Unsupported(&'static str),
#[error("`{0}` is not a dialect alias")]
UnknownDialect(String),
#[error("the edition could not be read: {0}")]
Storage(String),
#[error("the numeric value `{value}` does not convert")]
InvalidNumber {
value: String,
#[source]
source: std::num::ParseFloatError,
},
#[error("the time `{value}` is no YYYYMMDD")]
InvalidTime {
value: String,
#[source]
source: std::num::ParseIntError,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConceptPredicate {
Active(bool),
DefinitionStatus {
defined: bool,
primitive: bool,
},
Module {
modules: Vec<u64>,
negated: bool,
},
EffectiveTime {
operator: Comparison,
values: Vec<u32>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DescriptionPredicate {
Term {
operator: Equality,
terms: Vec<TypedSearchTerm>,
},
Language {
operator: Equality,
codes: Vec<String>,
},
Type {
operator: Equality,
types: Vec<u64>,
},
Dialect {
operator: Equality,
dialects: Vec<(u64, Vec<Acceptability>)>,
},
Active(Option<bool>),
Id {
operator: Equality,
ids: Vec<u64>,
},
}
const PREFERRED: u64 = 900_000_000_000_548_007;
const ACCEPTABLE: u64 = 900_000_000_000_549_004;
const DEFINED: u64 = 900_000_000_000_073_002;
const PRIMITIVE: u64 = 900_000_000_000_074_008;
const FULLY_SPECIFIED_NAME: u64 = 900_000_000_000_003_001;
const SYNONYM: u64 = 900_000_000_000_013_009;
const DEFINITION: u64 = 900_000_000_000_550_004;
pub trait Model {
fn concept(&self, id: Sctid) -> Result<Option<Ordinal>, EvalError>;
fn sctid(&self, concept: Ordinal) -> Result<Option<Sctid>, EvalError>;
fn all(&self) -> RoaringBitmap;
fn roots(&self) -> RoaringBitmap;
fn leaves(&self) -> RoaringBitmap;
fn descendants(&self, concept: Ordinal) -> &RoaringBitmap;
fn ancestors(&self, concept: Ordinal) -> &RoaringBitmap;
fn children(&self, concept: Ordinal) -> RoaringBitmap;
fn parents(&self, concept: Ordinal) -> RoaringBitmap;
fn attributes(&self) -> &concept_graph::attributes::Attributes;
fn members(&self) -> &concept_graph::refsets::RefsetMembers;
fn identifiers(&self) -> &concept_graph::identifiers::Identifiers;
fn scheme(&self, alias: &str) -> Result<Option<u64>, EvalError>;
fn filter_concepts(
&self,
within: &RoaringBitmap,
predicates: &[ConceptPredicate],
) -> Result<RoaringBitmap, EvalError>;
fn filter_descriptions(
&self,
within: &RoaringBitmap,
predicates: &[DescriptionPredicate],
) -> Result<RoaringBitmap, EvalError>;
}
pub fn evaluate<M: Model>(
model: &M,
constraint: &ExpressionConstraint,
) -> Result<RoaringBitmap, EvalError> {
Evaluator { model }.expression(constraint)
}
struct Evaluator<'m, M: Model> {
model: &'m M,
}
impl<M: Model> fmt::Debug for Evaluator<'_, M> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Evaluator")
}
}
#[must_use]
pub fn wild_matches(pattern: &str, text: &str) -> bool {
let pattern: Vec<char> = pattern.to_lowercase().chars().collect();
let text: Vec<char> = text.to_lowercase().chars().collect();
wild_at(&pattern, &text)
}
fn wild_at(pattern: &[char], text: &[char]) -> bool {
match pattern.split_first() {
None => text.is_empty(),
Some(('*', rest)) => (0..=text.len()).any(|skip| {
text.get(skip..)
.is_some_and(|remaining| wild_at(rest, remaining))
}),
Some(('\\', rest)) => {
let Some((escaped, after)) = rest.split_first() else {
return false;
};
text.split_first()
.is_some_and(|(first, remaining)| first == escaped && wild_at(after, remaining))
}
Some((expected, rest)) => text
.split_first()
.is_some_and(|(first, remaining)| first == expected && wild_at(rest, remaining)),
}
}
#[must_use]
pub fn term_matches(term: &TypedSearchTerm, text: &str) -> bool {
match term {
TypedSearchTerm::Match(words) => {
let lower = text.to_lowercase();
let text_words: Vec<&str> = lower
.split(|c: char| !c.is_alphanumeric())
.filter(|w| !w.is_empty())
.collect();
words.iter().all(|word| {
let word = unescape(word).to_lowercase();
text_words.iter().any(|t| t.starts_with(word.as_str()))
})
}
TypedSearchTerm::Wild(pattern) => wild_matches(pattern, text),
}
}
fn unescape(word: &str) -> String {
let mut out = String::with_capacity(word.len());
let mut chars = word.chars();
while let Some(c) = chars.next() {
if c == '\\' {
if let Some(next) = chars.next() {
out.push(next);
}
} else {
out.push(c);
}
}
out
}
impl<M: Model> Evaluator<'_, M> {
fn expression(&self, constraint: &ExpressionConstraint) -> Result<RoaringBitmap, EvalError> {
match constraint {
ExpressionConstraint::Sub(sub) => self.sub(sub),
ExpressionConstraint::Refined { focus, refinement } => {
let focus = self.sub(focus)?;
self.refinement(&focus, refinement)
}
ExpressionConstraint::Conjunction(operands) => {
let mut result: Option<RoaringBitmap> = None;
for operand in operands {
let set = self.sub(operand)?;
result = Some(match result {
None => set,
Some(current) => current & set,
});
}
Ok(result.unwrap_or_default())
}
ExpressionConstraint::Disjunction(operands) => {
let mut result = RoaringBitmap::new();
for operand in operands {
result |= self.sub(operand)?;
}
Ok(result)
}
ExpressionConstraint::Exclusion { left, right } => {
Ok(self.sub(left)? - self.sub(right)?)
}
ExpressionConstraint::Dotted { focus, attributes } => {
let mut set = self.sub(focus)?;
for attribute in attributes {
let kinds = self.kinds(attribute)?;
set = self.values_of(&set, &kinds);
}
Ok(set)
}
}
}
fn sub(&self, sub: &SubExpressionConstraint) -> Result<RoaringBitmap, EvalError> {
let mut set = self.focus(&sub.focus)?;
match &sub.refset_operator {
Some(RefsetOperator::MemberOf(member_of)) => {
set = self.member_of(&set, member_of.fields.as_ref(), &sub.member_filters)?;
}
Some(RefsetOperator::ContainingAny) => {
set = self.containing_any(&set, &sub.member_filters)?;
}
None => {}
}
if let Some(operator) = sub.operator {
set = self.operate(operator, &set, matches!(sub.focus, FocusConcept::Wildcard));
}
for filter in &sub.filters {
set = match filter {
FilterConstraint::Concept(filters) => match self.concept_predicates(filters)? {
Some(predicates) => self.model.filter_concepts(&set, &predicates)?,
None => RoaringBitmap::new(),
},
FilterConstraint::Description(filters) => {
match self.description_predicates(filters)? {
Some(predicates) => self.model.filter_descriptions(&set, &predicates)?,
None => RoaringBitmap::new(),
}
}
};
}
if let Some(history) = &sub.history {
set = self.history(set, history)?;
}
Ok(set)
}
fn focus(&self, focus: &FocusConcept) -> Result<RoaringBitmap, EvalError> {
match focus {
FocusConcept::Wildcard => Ok(self.model.all()),
FocusConcept::Reference(reference) => {
let ordinal = self
.model
.concept(reference.id)?
.ok_or(EvalError::UnknownConcept(reference.id))?;
Ok(RoaringBitmap::from_iter([ordinal.index()]))
}
FocusConcept::AltIdentifier(alt) => {
let scheme = self
.model
.scheme(&alt.scheme)?
.ok_or_else(|| EvalError::UnknownScheme(alt.scheme.clone()))?;
let ordinal = self
.model
.identifiers()
.lookup(scheme, &alt.code)
.ok_or_else(|| EvalError::UnknownIdentifier {
scheme: alt.scheme.clone(),
code: alt.code.clone(),
})?;
Ok(RoaringBitmap::from_iter([ordinal.index()]))
}
FocusConcept::Nested(inner) => self.expression(inner),
}
}
fn operate(
&self,
operator: ConstraintOperator,
set: &RoaringBitmap,
whole: bool,
) -> RoaringBitmap {
if whole {
return self.operate_over_edition(operator);
}
match operator {
ConstraintOperator::Top => self.top_of_set(set),
ConstraintOperator::Bottom => self.bottom_of_set(set),
_ => {
let mut out = self.related(operator, set);
if matches!(
operator,
ConstraintOperator::DescendantOrSelfOf
| ConstraintOperator::AncestorOrSelfOf
| ConstraintOperator::ChildOrSelfOf
| ConstraintOperator::ParentOrSelfOf
) {
out |= set;
}
out
}
}
}
fn operate_over_edition(&self, operator: ConstraintOperator) -> RoaringBitmap {
let all = self.model.all();
match operator {
ConstraintOperator::DescendantOrSelfOf
| ConstraintOperator::AncestorOrSelfOf
| ConstraintOperator::ChildOrSelfOf
| ConstraintOperator::ParentOrSelfOf => all,
ConstraintOperator::DescendantOf | ConstraintOperator::ChildOf => {
all - self.model.roots()
}
ConstraintOperator::AncestorOf | ConstraintOperator::ParentOf => {
all - self.model.leaves()
}
ConstraintOperator::Top => self.model.roots(),
ConstraintOperator::Bottom => self.model.leaves(),
}
}
fn top_of_set(&self, set: &RoaringBitmap) -> RoaringBitmap {
let mut out = RoaringBitmap::new();
for concept in set {
if self.model.ancestors(Ordinal::new(concept)).is_disjoint(set) {
out.insert(concept);
}
}
out
}
fn bottom_of_set(&self, set: &RoaringBitmap) -> RoaringBitmap {
let mut out = RoaringBitmap::new();
for concept in set {
if self
.model
.descendants(Ordinal::new(concept))
.is_disjoint(set)
{
out.insert(concept);
}
}
out
}
fn related(&self, operator: ConstraintOperator, set: &RoaringBitmap) -> RoaringBitmap {
let mut out = RoaringBitmap::new();
for concept in set {
let ordinal = Ordinal::new(concept);
match operator {
ConstraintOperator::DescendantOf | ConstraintOperator::DescendantOrSelfOf => {
out |= self.model.descendants(ordinal);
}
ConstraintOperator::AncestorOf | ConstraintOperator::AncestorOrSelfOf => {
out |= self.model.ancestors(ordinal);
}
ConstraintOperator::ChildOf | ConstraintOperator::ChildOrSelfOf => {
out |= self.model.children(ordinal);
}
ConstraintOperator::ParentOf | ConstraintOperator::ParentOrSelfOf => {
out |= self.model.parents(ordinal);
}
ConstraintOperator::Top | ConstraintOperator::Bottom => {}
}
}
out
}
fn kinds(&self, name: &SubExpressionConstraint) -> Result<Kinds, EvalError> {
if matches!(name.focus, FocusConcept::Wildcard)
&& name.operator.is_none()
&& name.filters.is_empty()
{
return Ok(Kinds::Any);
}
let types = self.sub(name)?;
let attributes = self.model.attributes();
let mut kinds = Vec::new();
for concept in types {
if let Some(id) = self.model.sctid(Ordinal::new(concept))?
&& let Some(kind) = attributes.kind(id.0)
{
kinds.push(kind);
}
}
Ok(Kinds::These(kinds))
}
fn values_of(&self, set: &RoaringBitmap, kinds: &Kinds) -> RoaringBitmap {
let attributes = self.model.attributes();
let mut out = RoaringBitmap::new();
for source in set {
for row in attributes.rows(Ordinal::new(source)) {
if let ValueRef::Concept(target) = row.value
&& kinds.contains(row.kind)
{
out.insert(target.index());
}
}
}
out
}
fn refinement(
&self,
focus: &RoaringBitmap,
refinement: &Refinement,
) -> Result<RoaringBitmap, EvalError> {
match refinement {
Refinement::Single(one) => self.sub_refinement(focus, one),
Refinement::Conjunction(items) => {
let mut result = focus.clone();
for item in items {
result &= self.sub_refinement(&result, item)?;
}
Ok(result)
}
Refinement::Disjunction(items) => {
let mut result = RoaringBitmap::new();
for item in items {
result |= self.sub_refinement(focus, item)?;
}
Ok(result)
}
}
}
fn sub_refinement(
&self,
focus: &RoaringBitmap,
item: &SubRefinement,
) -> Result<RoaringBitmap, EvalError> {
match item {
SubRefinement::AttributeSet(set) => self.attribute_set(focus, set),
SubRefinement::Nested(inner) => self.refinement(focus, inner),
SubRefinement::Group {
cardinality,
attributes,
} => self.attribute_group(focus, *cardinality, attributes),
}
}
fn attribute_group(
&self,
focus: &RoaringBitmap,
cardinality: Option<Cardinality>,
attributes: &AttributeSet,
) -> Result<RoaringBitmap, EvalError> {
let tests = self.compile_set(attributes)?;
let graph = self.model.attributes();
let candidates = if cardinality.is_none_or(|c| c.min >= 1) {
self.attribute_set(focus, attributes)?
} else {
focus.clone()
};
let mut out = RoaringBitmap::new();
for concept in &candidates {
let rows: Vec<Row<'_>> = graph.rows(Ordinal::new(concept)).collect();
let mut matching = 0_u32;
for group in &relationship_groups(&rows) {
if set_holds(&tests, group)? {
matching = matching.saturating_add(1);
}
}
if within(cardinality, matching) {
out.insert(concept);
}
}
Ok(out)
}
fn attribute_set(
&self,
focus: &RoaringBitmap,
set: &AttributeSet,
) -> Result<RoaringBitmap, EvalError> {
match set {
AttributeSet::Single(one) => self.sub_attribute_set(focus, one),
AttributeSet::Conjunction(items) => {
let mut result = focus.clone();
for item in items {
result &= self.sub_attribute_set(&result, item)?;
}
Ok(result)
}
AttributeSet::Disjunction(items) => {
let mut result = RoaringBitmap::new();
for item in items {
result |= self.sub_attribute_set(focus, item)?;
}
Ok(result)
}
}
}
fn sub_attribute_set(
&self,
focus: &RoaringBitmap,
item: &SubAttributeSet,
) -> Result<RoaringBitmap, EvalError> {
match item {
SubAttributeSet::Attribute(attribute) => self.attribute(focus, attribute),
SubAttributeSet::Nested(inner) => self.attribute_set(focus, inner),
}
}
fn attribute(
&self,
focus: &RoaringBitmap,
attribute: &Attribute,
) -> Result<RoaringBitmap, EvalError> {
let test = self.compile(attribute)?;
if attribute.reverse {
return Ok(self.reverse(focus, &test));
}
let default = attribute.cardinality.is_none();
if default
&& let ValueTest::Concept {
set,
negated: false,
} = &test.value
{
let sources = match set {
None => self.sources_of_kinds(&test.kinds),
Some(values) => self.sources_of_values(&test.kinds, values),
};
return Ok(sources & focus);
}
let graph = self.model.attributes();
let mut out = RoaringBitmap::new();
for concept in focus {
let rows: Vec<Row<'_>> = graph.rows(Ordinal::new(concept)).collect();
if within(attribute.cardinality, matching_rows(&test, rows.iter())?) {
out.insert(concept);
}
}
Ok(out)
}
fn sources_of_kinds(&self, kinds: &Kinds) -> RoaringBitmap {
let graph = self.model.attributes();
let mut out = RoaringBitmap::new();
for kind in kinds.iter(graph.types().len()) {
if let Some(sources) = graph.sources_of_kind(kind) {
out |= sources;
}
}
out
}
fn sources_of_values(&self, kinds: &Kinds, values: &RoaringBitmap) -> RoaringBitmap {
let graph = self.model.attributes();
let mut out = RoaringBitmap::new();
for kind in kinds.iter(graph.types().len()) {
for target in graph.targets_of_kind(kind) {
if values.contains(*target) {
out.extend(graph.sources(kind, Ordinal::new(*target)).iter().copied());
}
}
}
out
}
fn reverse(&self, focus: &RoaringBitmap, test: &AttributeTest) -> RoaringBitmap {
let graph = self.model.attributes();
let sources = match &test.value {
ValueTest::Concept {
set: Some(set),
negated: false,
} => Some(set),
_ => None,
};
let mut out = RoaringBitmap::new();
for target in focus {
let mut count = 0_u32;
for kind in test.kinds.iter(graph.types().len()) {
for source in graph.sources(kind, Ordinal::new(target)) {
if sources.is_none_or(|set| set.contains(*source)) {
count = count.saturating_add(1);
}
}
}
if within(test.cardinality, count) {
out.insert(target);
}
}
out
}
fn compile_set(&self, set: &AttributeSet) -> Result<CompiledSet, EvalError> {
Ok(match set {
AttributeSet::Single(one) => CompiledSet::Single(Box::new(self.compile_item(one)?)),
AttributeSet::Conjunction(items) => CompiledSet::Conjunction(
items
.iter()
.map(|i| self.compile_item(i))
.collect::<Result<_, _>>()?,
),
AttributeSet::Disjunction(items) => CompiledSet::Disjunction(
items
.iter()
.map(|i| self.compile_item(i))
.collect::<Result<_, _>>()?,
),
})
}
fn compile_item(&self, item: &SubAttributeSet) -> Result<CompiledItem, EvalError> {
Ok(match item {
SubAttributeSet::Attribute(attribute) => {
CompiledItem::Attribute(self.compile(attribute)?)
}
SubAttributeSet::Nested(inner) => CompiledItem::Nested(self.compile_set(inner)?),
})
}
fn compile(&self, attribute: &Attribute) -> Result<AttributeTest, EvalError> {
let kinds = self.kinds(&attribute.name)?;
let value = match &attribute.value {
AttributeValue::Expression { operator, value } => {
let set = if matches!(value.focus, FocusConcept::Wildcard)
&& value.operator.is_none()
&& value.filters.is_empty()
&& value.refset_operator.is_none()
{
None
} else {
Some(self.sub(value)?)
};
ValueTest::Concept {
set,
negated: *operator == Equality::NotEqual,
}
}
AttributeValue::Numeric { operator, value } => {
ValueTest::Number(*operator, number(&value.0)?)
}
AttributeValue::String { operator, values } => {
ValueTest::Text(*operator, values.clone())
}
AttributeValue::Boolean { operator, value } => ValueTest::Boolean(*operator, *value),
};
Ok(AttributeTest {
kinds,
value,
cardinality: attribute.cardinality,
})
}
}