1use std::fmt;
14
15use concept_graph::attributes::{Row, ValueRef};
16use concept_graph::ordinal::Ordinal;
17use concept_graph::refsets::{Table, ValueRef as FieldRef};
18use roaring::RoaringBitmap;
19
20use crate::ast::{
21 Acceptability, Attribute, AttributeSet, AttributeValue, Cardinality, Comparison, ConceptFilter,
22 ConceptSet, ConstraintOperator, DefinitionStatus, DescriptionFilter, DialectIdValue, Equality,
23 ExpressionConstraint, FieldValue, FilterConstraint, FocusConcept, HistorySupplement,
24 MemberFilter, Refinement, RefsetFields, Sctid, SubAttributeSet, SubExpressionConstraint,
25 SubRefinement, TimeValue, TypeToken, TypedSearchTerm,
26};
27
28pub const HISTORICAL_ASSOCIATION: u64 = 900_000_000_000_522_004;
31pub const SAME_AS: u64 = 900_000_000_000_527_005;
33pub const REPLACED_BY: u64 = 900_000_000_000_526_001;
35pub const WAS_A: u64 = 900_000_000_000_528_000;
37pub const PARTIALLY_EQUIVALENT_TO: u64 = 1_186_924_009;
39const REFERENCED_COMPONENT: &str = "referencedComponentId";
41const TARGET_COMPONENT: &str = "targetComponentId";
43
44#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
46pub enum EvalError {
47 #[error("the edition has no concept {0}")]
49 UnknownConcept(Sctid),
50 #[error("{0} is not a reference set with members in the edition")]
52 NotAReferenceSet(Sctid),
53 #[error("`{0}` is not an identifier scheme alias of the edition")]
55 UnknownScheme(String),
56 #[error("no concept has the alternate identifier {scheme}#{code}")]
58 UnknownIdentifier {
59 scheme: String,
61 code: String,
63 },
64 #[error("reference set {refset} has no field `{field}`")]
66 UnknownField {
67 refset: Sctid,
69 field: String,
71 },
72 #[error("{0} is not supported")]
74 Unsupported(&'static str),
75 #[error("`{0}` is not a dialect alias")]
77 UnknownDialect(String),
78 #[error("the edition could not be read: {0}")]
80 Storage(String),
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
85pub enum ConceptPredicate {
86 Active(bool),
88 DefinitionStatus {
90 defined: bool,
92 primitive: bool,
94 },
95 Module {
97 modules: Vec<u64>,
99 negated: bool,
101 },
102 EffectiveTime {
105 operator: Comparison,
107 values: Vec<u32>,
109 },
110}
111
112#[derive(Debug, Clone, PartialEq, Eq)]
116pub enum DescriptionPredicate {
117 Term {
119 operator: Equality,
121 terms: Vec<TypedSearchTerm>,
123 },
124 Language {
126 operator: Equality,
128 codes: Vec<String>,
130 },
131 Type {
133 operator: Equality,
135 types: Vec<u64>,
137 },
138 Dialect {
141 operator: Equality,
143 dialects: Vec<(u64, Vec<Acceptability>)>,
145 },
146 Active(bool),
148 Id {
150 operator: Equality,
152 ids: Vec<u64>,
154 },
155}
156
157const PREFERRED: u64 = 900_000_000_000_548_007;
159const ACCEPTABLE: u64 = 900_000_000_000_549_004;
161const DEFINED: u64 = 900_000_000_000_073_002;
163const PRIMITIVE: u64 = 900_000_000_000_074_008;
165const FULLY_SPECIFIED_NAME: u64 = 900_000_000_000_003_001;
167const SYNONYM: u64 = 900_000_000_000_013_009;
169const DEFINITION: u64 = 900_000_000_000_550_004;
171
172pub trait Model {
174 fn concept(&self, id: Sctid) -> Result<Option<Ordinal>, EvalError>;
180 fn sctid(&self, concept: Ordinal) -> Result<Option<Sctid>, EvalError>;
186 fn all(&self) -> RoaringBitmap;
188 fn roots(&self) -> RoaringBitmap;
190 fn leaves(&self) -> RoaringBitmap;
192 fn descendants(&self, concept: Ordinal) -> &RoaringBitmap;
194 fn ancestors(&self, concept: Ordinal) -> &RoaringBitmap;
196 fn children(&self, concept: Ordinal) -> RoaringBitmap;
198 fn parents(&self, concept: Ordinal) -> RoaringBitmap;
200 fn attributes(&self) -> &concept_graph::attributes::Attributes;
202 fn members(&self) -> &concept_graph::refsets::RefsetMembers;
204 fn identifiers(&self) -> &concept_graph::identifiers::Identifiers;
206 fn scheme(&self, alias: &str) -> Result<Option<u64>, EvalError>;
212 fn filter_concepts(
218 &self,
219 within: &RoaringBitmap,
220 predicates: &[ConceptPredicate],
221 ) -> Result<RoaringBitmap, EvalError>;
222 fn filter_descriptions(
229 &self,
230 within: &RoaringBitmap,
231 predicates: &[DescriptionPredicate],
232 ) -> Result<RoaringBitmap, EvalError>;
233}
234
235pub fn evaluate<M: Model>(
242 model: &M,
243 constraint: &ExpressionConstraint,
244) -> Result<RoaringBitmap, EvalError> {
245 Evaluator { model }.expression(constraint)
246}
247
248struct Evaluator<'m, M: Model> {
249 model: &'m M,
250}
251
252impl<M: Model> fmt::Debug for Evaluator<'_, M> {
253 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254 f.write_str("Evaluator")
255 }
256}
257
258#[derive(Debug, Clone, PartialEq, Eq)]
261enum Kinds {
262 Any,
263 These(Vec<u32>),
264}
265
266impl Kinds {
267 fn contains(&self, kind: u32) -> bool {
268 match self {
269 Self::Any => true,
270 Self::These(kinds) => kinds.contains(&kind),
271 }
272 }
273
274 fn iter(&self, total: usize) -> Box<dyn Iterator<Item = u32> + '_> {
275 match self {
276 Self::Any => Box::new(0..u32::try_from(total).unwrap_or(u32::MAX)),
277 Self::These(kinds) => Box::new(kinds.iter().copied()),
278 }
279 }
280}
281
282#[derive(Debug)]
284enum ValueTest {
285 Concept {
287 set: Option<RoaringBitmap>,
288 negated: bool,
289 },
290 Number(Comparison, f64),
291 Text(Equality, Vec<TypedSearchTerm>),
292 Boolean(Equality, bool),
293}
294
295impl ValueTest {
296 fn matches(&self, value: ValueRef<'_>) -> bool {
297 match (self, value) {
298 (Self::Concept { set: None, negated }, _) => !*negated,
301 (
302 Self::Concept {
303 set: Some(set),
304 negated,
305 },
306 ValueRef::Concept(target),
307 ) => set.contains(target.index()) != *negated,
308 (Self::Number(operator, expected), ValueRef::Number(text)) => text
309 .parse::<f64>()
310 .is_ok_and(|actual| compare_numbers(*operator, actual, *expected)),
311 (Self::Text(operator, terms), ValueRef::String(text)) => {
312 let hit = terms.iter().any(|term| term_matches(term, text));
313 hit == (*operator == Equality::Equal)
314 }
315 (Self::Boolean(operator, expected), ValueRef::String(text)) => {
316 let actual = text.eq_ignore_ascii_case("true");
317 let boolean = actual || text.eq_ignore_ascii_case("false");
318 boolean && ((actual == *expected) == (*operator == Equality::Equal))
319 }
320 _ => false,
321 }
322 }
323}
324
325fn compare_numbers(operator: Comparison, actual: f64, expected: f64) -> bool {
326 match operator {
327 Comparison::Equal => (actual - expected).abs() < f64::EPSILON,
328 Comparison::NotEqual => (actual - expected).abs() >= f64::EPSILON,
329 Comparison::Less => actual < expected,
330 Comparison::LessOrEqual => actual <= expected,
331 Comparison::Greater => actual > expected,
332 Comparison::GreaterOrEqual => actual >= expected,
333 }
334}
335
336fn compare_times(operator: Comparison, actual: u32, expected: u32) -> bool {
337 match operator {
338 Comparison::Equal => actual == expected,
339 Comparison::NotEqual => actual != expected,
340 Comparison::Less => actual < expected,
341 Comparison::LessOrEqual => actual <= expected,
342 Comparison::Greater => actual > expected,
343 Comparison::GreaterOrEqual => actual >= expected,
344 }
345}
346
347#[must_use]
351pub fn wild_matches(pattern: &str, text: &str) -> bool {
352 let pattern: Vec<char> = pattern.to_lowercase().chars().collect();
353 let text: Vec<char> = text.to_lowercase().chars().collect();
354 wild_at(&pattern, &text)
355}
356
357fn wild_at(pattern: &[char], text: &[char]) -> bool {
358 match pattern.split_first() {
359 None => text.is_empty(),
360 Some(('*', rest)) => (0..=text.len()).any(|skip| {
361 text.get(skip..)
362 .is_some_and(|remaining| wild_at(rest, remaining))
363 }),
364 Some(('\\', rest)) => {
365 let Some((escaped, after)) = rest.split_first() else {
366 return false;
367 };
368 text.split_first()
369 .is_some_and(|(first, remaining)| first == escaped && wild_at(after, remaining))
370 }
371 Some((expected, rest)) => text
372 .split_first()
373 .is_some_and(|(first, remaining)| first == expected && wild_at(rest, remaining)),
374 }
375}
376
377#[must_use]
380pub fn term_matches(term: &TypedSearchTerm, text: &str) -> bool {
381 match term {
382 TypedSearchTerm::Match(words) => {
383 let lower = text.to_lowercase();
384 let text_words: Vec<&str> = lower
385 .split(|c: char| !c.is_alphanumeric())
386 .filter(|w| !w.is_empty())
387 .collect();
388 words.iter().all(|word| {
389 let word = unescape(word).to_lowercase();
390 text_words.iter().any(|t| t.starts_with(word.as_str()))
391 })
392 }
393 TypedSearchTerm::Wild(pattern) => wild_matches(pattern, text),
394 }
395}
396
397fn unescape(word: &str) -> String {
399 let mut out = String::with_capacity(word.len());
400 let mut chars = word.chars();
401 while let Some(c) = chars.next() {
402 if c == '\\' {
403 if let Some(next) = chars.next() {
404 out.push(next);
405 }
406 } else {
407 out.push(c);
408 }
409 }
410 out
411}
412
413fn within(cardinality: Option<Cardinality>, count: u32) -> bool {
415 let Cardinality { min, max } = cardinality.unwrap_or(Cardinality { min: 1, max: None });
416 count >= min && max.is_none_or(|max| count <= max)
417}
418
419impl<M: Model> Evaluator<'_, M> {
420 fn expression(&self, constraint: &ExpressionConstraint) -> Result<RoaringBitmap, EvalError> {
421 match constraint {
422 ExpressionConstraint::Sub(sub) => self.sub(sub),
423 ExpressionConstraint::Refined { focus, refinement } => {
424 let focus = self.sub(focus)?;
425 self.refinement(&focus, refinement)
426 }
427 ExpressionConstraint::Conjunction(operands) => {
428 let mut result: Option<RoaringBitmap> = None;
429 for operand in operands {
430 let set = self.sub(operand)?;
431 result = Some(match result {
432 None => set,
433 Some(current) => current & set,
434 });
435 }
436 Ok(result.unwrap_or_default())
437 }
438 ExpressionConstraint::Disjunction(operands) => {
439 let mut result = RoaringBitmap::new();
440 for operand in operands {
441 result |= self.sub(operand)?;
442 }
443 Ok(result)
444 }
445 ExpressionConstraint::Exclusion { left, right } => {
446 Ok(self.sub(left)? - self.sub(right)?)
447 }
448 ExpressionConstraint::Dotted { focus, attributes } => {
449 let mut set = self.sub(focus)?;
450 for attribute in attributes {
451 let kinds = self.kinds(attribute)?;
452 set = self.values_of(&set, &kinds);
453 }
454 Ok(set)
455 }
456 }
457 }
458
459 fn sub(&self, sub: &SubExpressionConstraint) -> Result<RoaringBitmap, EvalError> {
462 let mut set = self.focus(&sub.focus)?;
463 if let Some(member_of) = &sub.member_of {
464 set = self.member_of(&set, member_of.fields.as_ref(), &sub.member_filters)?;
465 }
466 if let Some(operator) = sub.operator {
467 set = self.operate(operator, &set, matches!(sub.focus, FocusConcept::Wildcard));
468 }
469 for filter in &sub.filters {
470 set = match filter {
471 FilterConstraint::Concept(filters) => {
472 let predicates = self.concept_predicates(filters)?;
473 self.model.filter_concepts(&set, &predicates)?
474 }
475 FilterConstraint::Description(filters) => {
476 let predicates = self.description_predicates(filters)?;
477 self.model.filter_descriptions(&set, &predicates)?
478 }
479 };
480 }
481 if let Some(history) = &sub.history {
482 set = self.history(set, history)?;
483 }
484 Ok(set)
485 }
486
487 fn focus(&self, focus: &FocusConcept) -> Result<RoaringBitmap, EvalError> {
488 match focus {
489 FocusConcept::Wildcard => Ok(self.model.all()),
490 FocusConcept::Reference(reference) => {
491 let ordinal = self
492 .model
493 .concept(reference.id)?
494 .ok_or(EvalError::UnknownConcept(reference.id))?;
495 Ok(RoaringBitmap::from_iter([ordinal.index()]))
496 }
497 FocusConcept::AltIdentifier(alt) => {
498 let scheme = self
499 .model
500 .scheme(&alt.scheme)?
501 .ok_or_else(|| EvalError::UnknownScheme(alt.scheme.clone()))?;
502 let ordinal = self
503 .model
504 .identifiers()
505 .lookup(scheme, &alt.code)
506 .ok_or_else(|| EvalError::UnknownIdentifier {
507 scheme: alt.scheme.clone(),
508 code: alt.code.clone(),
509 })?;
510 Ok(RoaringBitmap::from_iter([ordinal.index()]))
511 }
512 FocusConcept::Nested(inner) => self.expression(inner),
513 }
514 }
515
516 fn operate(
519 &self,
520 operator: ConstraintOperator,
521 set: &RoaringBitmap,
522 whole: bool,
523 ) -> RoaringBitmap {
524 if whole {
525 return self.operate_over_edition(operator);
526 }
527 match operator {
528 ConstraintOperator::Top => self.top_of_set(set),
529 ConstraintOperator::Bottom => self.bottom_of_set(set),
530 _ => {
531 let mut out = self.related(operator, set);
532 if matches!(
533 operator,
534 ConstraintOperator::DescendantOrSelfOf
535 | ConstraintOperator::AncestorOrSelfOf
536 | ConstraintOperator::ChildOrSelfOf
537 | ConstraintOperator::ParentOrSelfOf
538 ) {
539 out |= set;
540 }
541 out
542 }
543 }
544 }
545
546 fn operate_over_edition(&self, operator: ConstraintOperator) -> RoaringBitmap {
549 let all = self.model.all();
550 match operator {
551 ConstraintOperator::DescendantOrSelfOf
552 | ConstraintOperator::AncestorOrSelfOf
553 | ConstraintOperator::ChildOrSelfOf
554 | ConstraintOperator::ParentOrSelfOf => all,
555 ConstraintOperator::DescendantOf | ConstraintOperator::ChildOf => {
556 all - self.model.roots()
557 }
558 ConstraintOperator::AncestorOf | ConstraintOperator::ParentOf => {
559 all - self.model.leaves()
560 }
561 ConstraintOperator::Top => self.model.roots(),
562 ConstraintOperator::Bottom => self.model.leaves(),
563 }
564 }
565
566 fn top_of_set(&self, set: &RoaringBitmap) -> RoaringBitmap {
568 let mut out = RoaringBitmap::new();
569 for concept in set {
570 if self.model.ancestors(Ordinal::new(concept)).is_disjoint(set) {
571 out.insert(concept);
572 }
573 }
574 out
575 }
576
577 fn bottom_of_set(&self, set: &RoaringBitmap) -> RoaringBitmap {
579 let mut out = RoaringBitmap::new();
580 for concept in set {
581 if self
582 .model
583 .descendants(Ordinal::new(concept))
584 .is_disjoint(set)
585 {
586 out.insert(concept);
587 }
588 }
589 out
590 }
591
592 fn related(&self, operator: ConstraintOperator, set: &RoaringBitmap) -> RoaringBitmap {
595 let mut out = RoaringBitmap::new();
596 for concept in set {
597 let ordinal = Ordinal::new(concept);
598 match operator {
599 ConstraintOperator::DescendantOf | ConstraintOperator::DescendantOrSelfOf => {
600 out |= self.model.descendants(ordinal);
601 }
602 ConstraintOperator::AncestorOf | ConstraintOperator::AncestorOrSelfOf => {
603 out |= self.model.ancestors(ordinal);
604 }
605 ConstraintOperator::ChildOf | ConstraintOperator::ChildOrSelfOf => {
606 out |= self.model.children(ordinal);
607 }
608 ConstraintOperator::ParentOf | ConstraintOperator::ParentOrSelfOf => {
609 out |= self.model.parents(ordinal);
610 }
611 ConstraintOperator::Top | ConstraintOperator::Bottom => {}
612 }
613 }
614 out
615 }
616
617 fn member_of(
621 &self,
622 set: &RoaringBitmap,
623 fields: Option<&RefsetFields>,
624 filter_groups: &[Vec<MemberFilter>],
625 ) -> Result<RoaringBitmap, EvalError> {
626 let mut out = RoaringBitmap::new();
627 for concept in set {
628 let Some(id) = self.model.sctid(Ordinal::new(concept))? else {
629 continue;
630 };
631 let Some(table) = self.model.members().table(id.0) else {
632 return Err(EvalError::NotAReferenceSet(id));
633 };
634 out |= self.table_members(id, table, fields, filter_groups)?;
635 }
636 Ok(out)
637 }
638
639 fn table_members(
642 &self,
643 refset: Sctid,
644 table: &Table,
645 fields: Option<&RefsetFields>,
646 filter_groups: &[Vec<MemberFilter>],
647 ) -> Result<RoaringBitmap, EvalError> {
648 let columns = Self::selected_columns(refset, table, fields)?;
649 let mut out = RoaringBitmap::new();
650 for row in 0..table.len() {
651 if !self.row_passes(table, row, filter_groups)? {
652 continue;
653 }
654 for column in &columns {
655 if let Some(value) = Self::column_concept(table, row, *column) {
656 out.insert(value.index());
657 }
658 }
659 }
660 Ok(out)
661 }
662
663 fn column_concept(table: &Table, row: usize, column: Option<usize>) -> Option<Ordinal> {
666 match column {
667 None => table.concept(row),
668 Some(field) => match table.value(row, field) {
669 Some(FieldRef::Concept(value)) => Some(value),
670 _ => None,
671 },
672 }
673 }
674
675 fn selected_columns(
678 refset: Sctid,
679 table: &Table,
680 fields: Option<&RefsetFields>,
681 ) -> Result<Vec<Option<usize>>, EvalError> {
682 Ok(match fields {
683 None => vec![None],
684 Some(RefsetFields::Any) => {
685 let mut columns = vec![None];
686 columns.extend(
687 table
688 .kinds()
689 .iter()
690 .enumerate()
691 .filter(|(_, kind)| **kind == concept_graph::refsets::FieldKind::Component)
692 .map(|(i, _)| Some(i)),
693 );
694 columns
695 }
696 Some(RefsetFields::Names(names)) => {
697 let mut columns = Vec::new();
698 for name in names {
699 if name.eq_ignore_ascii_case(REFERENCED_COMPONENT) {
700 columns.push(None);
701 } else {
702 columns.push(Some(table.field(name).ok_or_else(|| {
703 EvalError::UnknownField {
704 refset,
705 field: name.clone(),
706 }
707 })?));
708 }
709 }
710 columns
711 }
712 })
713 }
714
715 fn row_passes(
717 &self,
718 table: &Table,
719 row: usize,
720 filter_groups: &[Vec<MemberFilter>],
721 ) -> Result<bool, EvalError> {
722 for filter in filter_groups.iter().flatten() {
723 let passes = match filter {
724 MemberFilter::Active { operator, value } => {
725 if *value != (*operator == Equality::Equal) {
726 return Err(EvalError::Unsupported(
727 "a member filter on inactive members: the tables hold active members",
728 ));
729 }
730 true
731 }
732 MemberFilter::EffectiveTime { operator, values } => {
733 let actual = table.effective_time(row).unwrap_or_default();
734 times_match(*operator, values, actual)
735 }
736 MemberFilter::Module { operator, value } => {
737 let modules = self.sctids_of(value)?;
738 let inside = table.module(row).is_some_and(|m| modules.contains(&m));
739 inside == (*operator == Equality::Equal)
740 }
741 MemberFilter::Field { name, value } => {
742 let Some(column) = table.field(name) else {
743 return Ok(false);
744 };
745 self.field_passes(table.value(row, column), value)?
746 }
747 };
748 if !passes {
749 return Ok(false);
750 }
751 }
752 Ok(true)
753 }
754
755 fn field_passes(
756 &self,
757 actual: Option<FieldRef<'_>>,
758 expected: &FieldValue,
759 ) -> Result<bool, EvalError> {
760 Ok(match (expected, actual) {
761 (FieldValue::Expression { operator, value }, Some(FieldRef::Concept(concept))) => {
762 let set = self.sub(value)?;
763 set.contains(concept.index()) == (*operator == Equality::Equal)
764 }
765 (FieldValue::Expression { operator, .. }, _) => *operator == Equality::NotEqual,
766 (FieldValue::Numeric { operator, value }, Some(FieldRef::Integer(actual))) => {
767 let expected: f64 = value.0.parse().unwrap_or(f64::NAN);
768 #[expect(
769 clippy::as_conversions,
770 clippy::cast_precision_loss,
771 reason = "reference set integers are small"
772 )]
773 let actual = actual as f64;
774 compare_numbers(*operator, actual, expected)
775 }
776 (FieldValue::String { operator, terms }, Some(FieldRef::String(text))) => {
777 terms.iter().any(|t| term_matches(t, text)) == (*operator == Equality::Equal)
778 }
779 (FieldValue::Boolean { operator, value }, Some(FieldRef::String(text))) => {
780 let actual = text.eq_ignore_ascii_case("true");
781 (actual == *value) == (*operator == Equality::Equal)
782 }
783 (FieldValue::Time { operator, values }, Some(FieldRef::String(text))) => {
784 let actual: u32 = text.parse().unwrap_or_default();
785 times_match(*operator, values, actual)
786 }
787 (FieldValue::Time { operator, values }, Some(FieldRef::Integer(actual))) => {
788 let actual = u32::try_from(actual).unwrap_or_default();
789 times_match(*operator, values, actual)
790 }
791 _ => false,
792 })
793 }
794
795 fn sctids_of(&self, set: &ConceptSet) -> Result<Vec<u64>, EvalError> {
799 match set {
800 ConceptSet::Set(references) => Ok(references.iter().map(|r| r.id.0).collect()),
801 ConceptSet::Expression(sub)
802 if sub.operator.is_none()
803 && sub.member_of.is_none()
804 && sub.filters.is_empty()
805 && sub.member_filters.is_empty()
806 && sub.history.is_none() =>
807 {
808 match &sub.focus {
809 FocusConcept::Reference(reference) => Ok(vec![reference.id.0]),
810 _ => self.sctids_of_set(&self.sub(sub)?),
811 }
812 }
813 ConceptSet::Expression(sub) => self.sctids_of_set(&self.sub(sub)?),
814 }
815 }
816
817 fn sctids_of_set(&self, set: &RoaringBitmap) -> Result<Vec<u64>, EvalError> {
818 let mut ids = Vec::new();
819 for concept in set {
820 if let Some(id) = self.model.sctid(Ordinal::new(concept))? {
821 ids.push(id.0);
822 }
823 }
824 Ok(ids)
825 }
826
827 fn acceptabilities(
828 set: Option<&crate::ast::AcceptabilitySet>,
829 ) -> Result<Vec<Acceptability>, EvalError> {
830 match set {
831 None => Ok(Vec::new()),
832 Some(crate::ast::AcceptabilitySet::Tokens(tokens)) => Ok(tokens.clone()),
833 Some(crate::ast::AcceptabilitySet::Concepts(references)) => references
834 .iter()
835 .map(|reference| match reference.id.0 {
836 PREFERRED => Ok(Acceptability::Preferred),
837 ACCEPTABLE => Ok(Acceptability::Acceptable),
838 _ => Err(EvalError::Unsupported(
839 "an acceptability concept other than preferred or acceptable",
840 )),
841 })
842 .collect(),
843 }
844 }
845
846 fn concept_predicates(
848 &self,
849 filters: &[ConceptFilter],
850 ) -> Result<Vec<ConceptPredicate>, EvalError> {
851 let mut predicates = Vec::new();
852 for filter in filters {
853 predicates.push(match filter {
854 ConceptFilter::Active { operator, value } => {
855 ConceptPredicate::Active(*value == (*operator == Equality::Equal))
856 }
857 ConceptFilter::DefinitionStatus { operator, tokens } => {
858 let defined = tokens.contains(&DefinitionStatus::Defined);
859 let primitive = tokens.contains(&DefinitionStatus::Primitive);
860 let equal = *operator == Equality::Equal;
861 ConceptPredicate::DefinitionStatus {
862 defined: defined == equal,
863 primitive: primitive == equal,
864 }
865 }
866 ConceptFilter::DefinitionStatusId { operator, value } => {
867 let ids = self.sctids_of(value)?;
868 let equal = *operator == Equality::Equal;
869 ConceptPredicate::DefinitionStatus {
870 defined: ids.contains(&DEFINED) == equal,
871 primitive: ids.contains(&PRIMITIVE) == equal,
872 }
873 }
874 ConceptFilter::Module { operator, value } => ConceptPredicate::Module {
875 modules: self.sctids_of(value)?,
876 negated: *operator == Equality::NotEqual,
877 },
878 ConceptFilter::EffectiveTime { operator, values } => {
879 ConceptPredicate::EffectiveTime {
880 operator: *operator,
881 values: values
882 .iter()
883 .map(|v| v.0.parse().unwrap_or_default())
884 .collect(),
885 }
886 }
887 });
888 }
889 Ok(predicates)
890 }
891
892 fn description_predicates(
894 &self,
895 filters: &[DescriptionFilter],
896 ) -> Result<Vec<DescriptionPredicate>, EvalError> {
897 let mut predicates = Vec::new();
898 for filter in filters {
899 predicates.push(self.description_predicate(filter)?);
900 }
901 Ok(predicates)
902 }
903
904 fn description_predicate(
906 &self,
907 filter: &DescriptionFilter,
908 ) -> Result<DescriptionPredicate, EvalError> {
909 Ok(match filter {
910 DescriptionFilter::Term { operator, terms } => DescriptionPredicate::Term {
911 operator: *operator,
912 terms: terms.clone(),
913 },
914 DescriptionFilter::Language { operator, codes } => DescriptionPredicate::Language {
915 operator: *operator,
916 codes: codes.clone(),
917 },
918 DescriptionFilter::TypeId { operator, value } => DescriptionPredicate::Type {
919 operator: *operator,
920 types: self.sctids_of(value)?,
921 },
922 DescriptionFilter::Type { operator, tokens } => DescriptionPredicate::Type {
923 operator: *operator,
924 types: tokens
925 .iter()
926 .map(|t| match t {
927 TypeToken::Synonym => SYNONYM,
928 TypeToken::FullySpecifiedName => FULLY_SPECIFIED_NAME,
929 TypeToken::Definition => DEFINITION,
930 })
931 .collect(),
932 },
933 DescriptionFilter::DialectId {
934 operator,
935 value,
936 acceptability,
937 } => DescriptionPredicate::Dialect {
938 operator: *operator,
939 dialects: self.dialects_by_id(value, acceptability.as_ref())?,
940 },
941 DescriptionFilter::Dialect {
942 operator,
943 aliases,
944 acceptability,
945 } => DescriptionPredicate::Dialect {
946 operator: *operator,
947 dialects: Self::dialects_by_alias(aliases, acceptability.as_ref())?,
948 },
949 DescriptionFilter::Module { .. } => {
950 return Err(EvalError::Unsupported(
951 "a description module filter: the store keeps no description module",
952 ));
953 }
954 DescriptionFilter::EffectiveTime { .. } => {
955 return Err(EvalError::Unsupported(
956 "a description effective time filter: the store keeps no description time",
957 ));
958 }
959 DescriptionFilter::Active { operator, value } => {
960 DescriptionPredicate::Active(*value == (*operator == Equality::Equal))
961 }
962 DescriptionFilter::Id { operator, ids } => DescriptionPredicate::Id {
963 operator: *operator,
964 ids: ids.iter().map(|id| id.0).collect(),
965 },
966 })
967 }
968
969 fn dialects_by_id(
972 &self,
973 value: &DialectIdValue,
974 acceptability: Option<&crate::ast::AcceptabilitySet>,
975 ) -> Result<Vec<(u64, Vec<Acceptability>)>, EvalError> {
976 let shared = Self::acceptabilities(acceptability)?;
977 match value {
978 DialectIdValue::Expression(sub) => Ok(self
979 .sctids_of(&ConceptSet::Expression(sub.clone()))?
980 .into_iter()
981 .map(|id| (id, shared.clone()))
982 .collect()),
983 DialectIdValue::Set(items) => {
984 let mut out = Vec::new();
985 for (reference, own) in items {
986 let allowed = if own.is_some() {
987 Self::acceptabilities(own.as_ref())?
988 } else {
989 shared.clone()
990 };
991 out.push((reference.id.0, allowed));
992 }
993 Ok(out)
994 }
995 }
996 }
997
998 fn dialects_by_alias(
1001 aliases: &[crate::ast::DialectAlias],
1002 acceptability: Option<&crate::ast::AcceptabilitySet>,
1003 ) -> Result<Vec<(u64, Vec<Acceptability>)>, EvalError> {
1004 let shared = Self::acceptabilities(acceptability)?;
1005 let mut dialects = Vec::new();
1006 for alias in aliases {
1007 let refset = crate::dialects::refset(&alias.alias)
1008 .ok_or_else(|| EvalError::UnknownDialect(alias.alias.clone()))?;
1009 let allowed = if alias.acceptability.is_some() {
1010 Self::acceptabilities(alias.acceptability.as_ref())?
1011 } else {
1012 shared.clone()
1013 };
1014 dialects.push((refset, allowed));
1015 }
1016 Ok(dialects)
1017 }
1018
1019 fn history(
1022 &self,
1023 mut set: RoaringBitmap,
1024 history: &HistorySupplement,
1025 ) -> Result<RoaringBitmap, EvalError> {
1026 let refsets = self.history_refsets(history)?;
1027 let added = self.associated_members(&set, &refsets);
1028 set |= added;
1029 Ok(set)
1030 }
1031
1032 fn history_refsets(&self, history: &HistorySupplement) -> Result<Vec<u64>, EvalError> {
1034 match history {
1035 HistorySupplement::Minimum => Ok(vec![SAME_AS]),
1036 HistorySupplement::Moderate => {
1037 Ok(vec![SAME_AS, REPLACED_BY, WAS_A, PARTIALLY_EQUIVALENT_TO])
1038 }
1039 HistorySupplement::Default | HistorySupplement::Maximum => {
1040 match self.model.concept(Sctid(HISTORICAL_ASSOCIATION))? {
1041 Some(root) => self.sctids_of_set(self.model.descendants(root)),
1042 None => Ok(Vec::new()),
1043 }
1044 }
1045 HistorySupplement::Subset(constraint) => {
1046 self.sctids_of_set(&self.expression(constraint)?)
1047 }
1048 }
1049 }
1050
1051 fn associated_members(&self, set: &RoaringBitmap, refsets: &[u64]) -> RoaringBitmap {
1054 let mut added = RoaringBitmap::new();
1055 for refset in refsets {
1056 let Some(table) = self.model.members().table(*refset) else {
1057 continue;
1058 };
1059 let Some(target) = table.field(TARGET_COMPONENT) else {
1060 continue;
1061 };
1062 added |= Self::members_targeting(table, target, set);
1063 }
1064 added
1065 }
1066
1067 fn members_targeting(table: &Table, target: usize, set: &RoaringBitmap) -> RoaringBitmap {
1069 let mut out = RoaringBitmap::new();
1070 for row in 0..table.len() {
1071 if let (Some(FieldRef::Concept(value)), Some(member)) =
1072 (table.value(row, target), table.concept(row))
1073 && set.contains(value.index())
1074 {
1075 out.insert(member.index());
1076 }
1077 }
1078 out
1079 }
1080
1081 fn kinds(&self, name: &SubExpressionConstraint) -> Result<Kinds, EvalError> {
1083 if matches!(name.focus, FocusConcept::Wildcard)
1084 && name.operator.is_none()
1085 && name.filters.is_empty()
1086 {
1087 return Ok(Kinds::Any);
1088 }
1089 let types = self.sub(name)?;
1090 let attributes = self.model.attributes();
1091 let mut kinds = Vec::new();
1092 for concept in types {
1093 if let Some(id) = self.model.sctid(Ordinal::new(concept))?
1094 && let Some(kind) = attributes.kind(id.0)
1095 {
1096 kinds.push(kind);
1097 }
1098 }
1099 Ok(Kinds::These(kinds))
1100 }
1101
1102 fn values_of(&self, set: &RoaringBitmap, kinds: &Kinds) -> RoaringBitmap {
1104 let attributes = self.model.attributes();
1105 let mut out = RoaringBitmap::new();
1106 for source in set {
1107 for row in attributes.rows(Ordinal::new(source)) {
1108 if let ValueRef::Concept(target) = row.value
1109 && kinds.contains(row.kind)
1110 {
1111 out.insert(target.index());
1112 }
1113 }
1114 }
1115 out
1116 }
1117
1118 fn refinement(
1120 &self,
1121 focus: &RoaringBitmap,
1122 refinement: &Refinement,
1123 ) -> Result<RoaringBitmap, EvalError> {
1124 match refinement {
1125 Refinement::Single(one) => self.sub_refinement(focus, one),
1126 Refinement::Conjunction(items) => {
1127 let mut result = focus.clone();
1128 for item in items {
1129 result &= self.sub_refinement(&result, item)?;
1130 }
1131 Ok(result)
1132 }
1133 Refinement::Disjunction(items) => {
1134 let mut result = RoaringBitmap::new();
1135 for item in items {
1136 result |= self.sub_refinement(focus, item)?;
1137 }
1138 Ok(result)
1139 }
1140 }
1141 }
1142
1143 fn sub_refinement(
1144 &self,
1145 focus: &RoaringBitmap,
1146 item: &SubRefinement,
1147 ) -> Result<RoaringBitmap, EvalError> {
1148 match item {
1149 SubRefinement::AttributeSet(set) => self.attribute_set(focus, set),
1150 SubRefinement::Nested(inner) => self.refinement(focus, inner),
1151 SubRefinement::Group {
1152 cardinality,
1153 attributes,
1154 } => self.attribute_group(focus, *cardinality, attributes),
1155 }
1156 }
1157
1158 fn attribute_group(
1161 &self,
1162 focus: &RoaringBitmap,
1163 cardinality: Option<Cardinality>,
1164 attributes: &AttributeSet,
1165 ) -> Result<RoaringBitmap, EvalError> {
1166 let tests = self.compile_set(attributes)?;
1167 let graph = self.model.attributes();
1168 let candidates = if cardinality.is_none_or(|c| c.min >= 1) {
1173 self.attribute_set(focus, attributes)?
1174 } else {
1175 focus.clone()
1176 };
1177 let mut out = RoaringBitmap::new();
1178 for concept in &candidates {
1179 let rows: Vec<Row<'_>> = graph.rows(Ordinal::new(concept)).collect();
1180 let matching = relationship_groups(&rows)
1181 .iter()
1182 .filter(|group| set_holds(&tests, group))
1183 .count();
1184 if within(cardinality, u32::try_from(matching).unwrap_or(u32::MAX)) {
1185 out.insert(concept);
1186 }
1187 }
1188 Ok(out)
1189 }
1190
1191 fn attribute_set(
1195 &self,
1196 focus: &RoaringBitmap,
1197 set: &AttributeSet,
1198 ) -> Result<RoaringBitmap, EvalError> {
1199 match set {
1200 AttributeSet::Single(one) => self.sub_attribute_set(focus, one),
1201 AttributeSet::Conjunction(items) => {
1202 let mut result = focus.clone();
1203 for item in items {
1204 result &= self.sub_attribute_set(&result, item)?;
1205 }
1206 Ok(result)
1207 }
1208 AttributeSet::Disjunction(items) => {
1209 let mut result = RoaringBitmap::new();
1210 for item in items {
1211 result |= self.sub_attribute_set(focus, item)?;
1212 }
1213 Ok(result)
1214 }
1215 }
1216 }
1217
1218 fn sub_attribute_set(
1219 &self,
1220 focus: &RoaringBitmap,
1221 item: &SubAttributeSet,
1222 ) -> Result<RoaringBitmap, EvalError> {
1223 match item {
1224 SubAttributeSet::Attribute(attribute) => self.attribute(focus, attribute),
1225 SubAttributeSet::Nested(inner) => self.attribute_set(focus, inner),
1226 }
1227 }
1228
1229 fn attribute(
1231 &self,
1232 focus: &RoaringBitmap,
1233 attribute: &Attribute,
1234 ) -> Result<RoaringBitmap, EvalError> {
1235 let test = self.compile(attribute)?;
1236 if attribute.reverse {
1237 return Ok(self.reverse(focus, &test));
1238 }
1239 let default = attribute.cardinality.is_none();
1240 if default
1241 && let ValueTest::Concept {
1242 set,
1243 negated: false,
1244 } = &test.value
1245 {
1246 let sources = match set {
1247 None => self.sources_of_kinds(&test.kinds),
1248 Some(values) => self.sources_of_values(&test.kinds, values),
1249 };
1250 return Ok(sources & focus);
1251 }
1252 let graph = self.model.attributes();
1253 let mut out = RoaringBitmap::new();
1254 for concept in focus {
1255 let count = graph
1256 .rows(Ordinal::new(concept))
1257 .filter(|row| test.matches(row))
1258 .count();
1259 if within(
1260 attribute.cardinality,
1261 u32::try_from(count).unwrap_or(u32::MAX),
1262 ) {
1263 out.insert(concept);
1264 }
1265 }
1266 Ok(out)
1267 }
1268
1269 fn sources_of_kinds(&self, kinds: &Kinds) -> RoaringBitmap {
1272 let graph = self.model.attributes();
1273 let mut out = RoaringBitmap::new();
1274 for kind in kinds.iter(graph.types().len()) {
1275 if let Some(sources) = graph.sources_of_kind(kind) {
1276 out |= sources;
1277 }
1278 }
1279 out
1280 }
1281
1282 fn sources_of_values(&self, kinds: &Kinds, values: &RoaringBitmap) -> RoaringBitmap {
1285 let graph = self.model.attributes();
1286 let mut out = RoaringBitmap::new();
1287 for kind in kinds.iter(graph.types().len()) {
1288 for target in graph.targets_of_kind(kind) {
1289 if values.contains(*target) {
1290 out.extend(graph.sources(kind, Ordinal::new(*target)).iter().copied());
1291 }
1292 }
1293 }
1294 out
1295 }
1296
1297 fn reverse(&self, focus: &RoaringBitmap, test: &AttributeTest) -> RoaringBitmap {
1300 let graph = self.model.attributes();
1301 let sources = match &test.value {
1302 ValueTest::Concept {
1303 set: Some(set),
1304 negated: false,
1305 } => Some(set),
1306 _ => None,
1307 };
1308 let mut out = RoaringBitmap::new();
1309 for target in focus {
1310 let mut count = 0_u32;
1311 for kind in test.kinds.iter(graph.types().len()) {
1312 for source in graph.sources(kind, Ordinal::new(target)) {
1313 if sources.is_none_or(|set| set.contains(*source)) {
1314 count = count.saturating_add(1);
1315 }
1316 }
1317 }
1318 if within(test.cardinality, count) {
1319 out.insert(target);
1320 }
1321 }
1322 out
1323 }
1324
1325 fn compile_set(&self, set: &AttributeSet) -> Result<CompiledSet, EvalError> {
1327 Ok(match set {
1328 AttributeSet::Single(one) => CompiledSet::Single(Box::new(self.compile_item(one)?)),
1329 AttributeSet::Conjunction(items) => CompiledSet::Conjunction(
1330 items
1331 .iter()
1332 .map(|i| self.compile_item(i))
1333 .collect::<Result<_, _>>()?,
1334 ),
1335 AttributeSet::Disjunction(items) => CompiledSet::Disjunction(
1336 items
1337 .iter()
1338 .map(|i| self.compile_item(i))
1339 .collect::<Result<_, _>>()?,
1340 ),
1341 })
1342 }
1343
1344 fn compile_item(&self, item: &SubAttributeSet) -> Result<CompiledItem, EvalError> {
1345 Ok(match item {
1346 SubAttributeSet::Attribute(attribute) => {
1347 CompiledItem::Attribute(self.compile(attribute)?)
1348 }
1349 SubAttributeSet::Nested(inner) => CompiledItem::Nested(self.compile_set(inner)?),
1350 })
1351 }
1352
1353 fn compile(&self, attribute: &Attribute) -> Result<AttributeTest, EvalError> {
1354 let kinds = self.kinds(&attribute.name)?;
1355 let value = match &attribute.value {
1356 AttributeValue::Expression { operator, value } => {
1357 let set = if matches!(value.focus, FocusConcept::Wildcard)
1358 && value.operator.is_none()
1359 && value.filters.is_empty()
1360 && value.member_of.is_none()
1361 {
1362 None
1363 } else {
1364 Some(self.sub(value)?)
1365 };
1366 ValueTest::Concept {
1367 set,
1368 negated: *operator == Equality::NotEqual,
1369 }
1370 }
1371 AttributeValue::Numeric { operator, value } => {
1372 ValueTest::Number(*operator, value.0.parse().unwrap_or(f64::NAN))
1373 }
1374 AttributeValue::String { operator, terms } => ValueTest::Text(*operator, terms.clone()),
1375 AttributeValue::Boolean { operator, value } => ValueTest::Boolean(*operator, *value),
1376 };
1377 Ok(AttributeTest {
1378 kinds,
1379 value,
1380 cardinality: attribute.cardinality,
1381 })
1382 }
1383}
1384
1385fn times_match(operator: Comparison, values: &[TimeValue], actual: u32) -> bool {
1387 let expected = values
1388 .iter()
1389 .map(|v| v.0.parse::<u32>().unwrap_or_default());
1390 match operator {
1391 Comparison::NotEqual => expected.clone().all(|e| actual != e),
1392 _ => expected
1393 .into_iter()
1394 .any(|e| compare_times(operator, actual, e)),
1395 }
1396}
1397
1398#[derive(Debug)]
1400struct AttributeTest {
1401 kinds: Kinds,
1402 value: ValueTest,
1403 cardinality: Option<Cardinality>,
1404}
1405
1406impl AttributeTest {
1407 fn matches(&self, row: &Row<'_>) -> bool {
1408 self.kinds.contains(row.kind) && self.value.matches(row.value)
1409 }
1410}
1411
1412#[derive(Debug)]
1413enum CompiledSet {
1414 Single(Box<CompiledItem>),
1415 Conjunction(Vec<CompiledItem>),
1416 Disjunction(Vec<CompiledItem>),
1417}
1418
1419#[derive(Debug)]
1420enum CompiledItem {
1421 Attribute(AttributeTest),
1422 Nested(CompiledSet),
1423}
1424
1425fn relationship_groups<'row, 'graph>(rows: &'row [Row<'graph>]) -> Vec<Vec<&'row Row<'graph>>> {
1427 let mut groups: Vec<Vec<&Row<'_>>> = Vec::new();
1428 let mut current: Option<u32> = None;
1429 for row in rows {
1430 if row.group == 0 || current != Some(row.group) {
1433 groups.push(Vec::new());
1434 current = Some(row.group);
1435 }
1436 if let Some(last) = groups.last_mut() {
1437 last.push(row);
1438 }
1439 }
1440 groups
1441}
1442
1443fn set_holds(set: &CompiledSet, rows: &[&Row<'_>]) -> bool {
1445 match set {
1446 CompiledSet::Single(item) => item_holds(item, rows),
1447 CompiledSet::Conjunction(items) => items.iter().all(|i| item_holds(i, rows)),
1448 CompiledSet::Disjunction(items) => items.iter().any(|i| item_holds(i, rows)),
1449 }
1450}
1451
1452fn item_holds(item: &CompiledItem, rows: &[&Row<'_>]) -> bool {
1453 match item {
1454 CompiledItem::Nested(set) => set_holds(set, rows),
1455 CompiledItem::Attribute(test) => {
1456 let count = rows.iter().filter(|row| test.matches(row)).count();
1457 within(test.cardinality, u32::try_from(count).unwrap_or(u32::MAX))
1458 }
1459 }
1460}
1461
1462#[cfg(test)]
1463mod tests {
1464 use super::{Cardinality, compare_numbers, term_matches, wild_matches, within};
1465 use crate::ast::{Comparison, TypedSearchTerm};
1466
1467 #[test]
1468 fn cardinalities_patterns_and_words_match_as_the_specification_says() {
1469 assert!(within(None, 1) && within(None, 5) && !within(None, 0));
1470 assert!(within(
1471 Some(Cardinality {
1472 min: 0,
1473 max: Some(0)
1474 }),
1475 0
1476 ));
1477 assert!(!within(
1478 Some(Cardinality {
1479 min: 0,
1480 max: Some(0)
1481 }),
1482 1
1483 ));
1484 assert!(within(Some(Cardinality { min: 2, max: None }), 2));
1485 assert!(wild_matches("cardi*opathy", "Cardiomyopathy"));
1486 assert!(!wild_matches("cardi*opathy", "Cardiomyopathy X"));
1487 assert!(wild_matches("*itis", "Bronchitis"));
1488 assert!(wild_matches("a\\*b", "A*B"));
1489 assert!(!wild_matches("a\\*b", "AXB"));
1490 let term = TypedSearchTerm::Match(vec![String::from("hea"), String::from("att")]);
1491 assert!(term_matches(&term, "Heart attack"));
1492 assert!(!term_matches(&term, "Heart"));
1493 assert!(compare_numbers(Comparison::GreaterOrEqual, 500.0, 500.0));
1494 assert!(!compare_numbers(Comparison::Less, 500.0, 500.0));
1495 }
1496}