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 let all = self.model.all();
526 return match operator {
527 ConstraintOperator::DescendantOrSelfOf
528 | ConstraintOperator::AncestorOrSelfOf
529 | ConstraintOperator::ChildOrSelfOf
530 | ConstraintOperator::ParentOrSelfOf => all,
531 ConstraintOperator::DescendantOf | ConstraintOperator::ChildOf => {
532 all - self.model.roots()
533 }
534 ConstraintOperator::AncestorOf | ConstraintOperator::ParentOf => {
535 all - self.model.leaves()
536 }
537 ConstraintOperator::Top => self.model.roots(),
538 ConstraintOperator::Bottom => self.model.leaves(),
539 };
540 }
541 let mut out = RoaringBitmap::new();
542 match operator {
543 ConstraintOperator::Top => {
544 for concept in set {
545 if self.model.ancestors(Ordinal::new(concept)).is_disjoint(set) {
546 out.insert(concept);
547 }
548 }
549 return out;
550 }
551 ConstraintOperator::Bottom => {
552 for concept in set {
553 if self
554 .model
555 .descendants(Ordinal::new(concept))
556 .is_disjoint(set)
557 {
558 out.insert(concept);
559 }
560 }
561 return out;
562 }
563 _ => {}
564 }
565 for concept in set {
566 let ordinal = Ordinal::new(concept);
567 match operator {
568 ConstraintOperator::DescendantOf | ConstraintOperator::DescendantOrSelfOf => {
569 out |= self.model.descendants(ordinal);
570 }
571 ConstraintOperator::AncestorOf | ConstraintOperator::AncestorOrSelfOf => {
572 out |= self.model.ancestors(ordinal);
573 }
574 ConstraintOperator::ChildOf | ConstraintOperator::ChildOrSelfOf => {
575 out |= self.model.children(ordinal);
576 }
577 ConstraintOperator::ParentOf | ConstraintOperator::ParentOrSelfOf => {
578 out |= self.model.parents(ordinal);
579 }
580 ConstraintOperator::Top | ConstraintOperator::Bottom => {}
581 }
582 }
583 if matches!(
584 operator,
585 ConstraintOperator::DescendantOrSelfOf
586 | ConstraintOperator::AncestorOrSelfOf
587 | ConstraintOperator::ChildOrSelfOf
588 | ConstraintOperator::ParentOrSelfOf
589 ) {
590 out |= set;
591 }
592 out
593 }
594
595 fn member_of(
599 &self,
600 set: &RoaringBitmap,
601 fields: Option<&RefsetFields>,
602 filter_groups: &[Vec<MemberFilter>],
603 ) -> Result<RoaringBitmap, EvalError> {
604 let mut out = RoaringBitmap::new();
605 for concept in set {
606 let Some(id) = self.model.sctid(Ordinal::new(concept))? else {
607 continue;
608 };
609 let Some(table) = self.model.members().table(id.0) else {
610 return Err(EvalError::NotAReferenceSet(id));
611 };
612 let columns = Self::selected_columns(id, table, fields)?;
613 for row in 0..table.len() {
614 if !self.row_passes(table, row, filter_groups)? {
615 continue;
616 }
617 for column in &columns {
618 match column {
619 None => {
620 if let Some(member) = table.concept(row) {
621 out.insert(member.index());
622 }
623 }
624 Some(field) => {
625 if let Some(FieldRef::Concept(value)) = table.value(row, *field) {
626 out.insert(value.index());
627 }
628 }
629 }
630 }
631 }
632 }
633 Ok(out)
634 }
635
636 fn selected_columns(
639 refset: Sctid,
640 table: &Table,
641 fields: Option<&RefsetFields>,
642 ) -> Result<Vec<Option<usize>>, EvalError> {
643 Ok(match fields {
644 None => vec![None],
645 Some(RefsetFields::Any) => {
646 let mut columns = vec![None];
647 columns.extend(
648 table
649 .kinds()
650 .iter()
651 .enumerate()
652 .filter(|(_, kind)| **kind == concept_graph::refsets::FieldKind::Component)
653 .map(|(i, _)| Some(i)),
654 );
655 columns
656 }
657 Some(RefsetFields::Names(names)) => {
658 let mut columns = Vec::new();
659 for name in names {
660 if name.eq_ignore_ascii_case(REFERENCED_COMPONENT) {
661 columns.push(None);
662 } else {
663 columns.push(Some(table.field(name).ok_or_else(|| {
664 EvalError::UnknownField {
665 refset,
666 field: name.clone(),
667 }
668 })?));
669 }
670 }
671 columns
672 }
673 })
674 }
675
676 fn row_passes(
678 &self,
679 table: &Table,
680 row: usize,
681 filter_groups: &[Vec<MemberFilter>],
682 ) -> Result<bool, EvalError> {
683 for filter in filter_groups.iter().flatten() {
684 let passes = match filter {
685 MemberFilter::Active { operator, value } => {
686 if *value != (*operator == Equality::Equal) {
687 return Err(EvalError::Unsupported(
688 "a member filter on inactive members: the tables hold active members",
689 ));
690 }
691 true
692 }
693 MemberFilter::EffectiveTime { operator, values } => {
694 let actual = table.effective_time(row).unwrap_or_default();
695 times_match(*operator, values, actual)
696 }
697 MemberFilter::Module { operator, value } => {
698 let modules = self.sctids_of(value)?;
699 let inside = table.module(row).is_some_and(|m| modules.contains(&m));
700 inside == (*operator == Equality::Equal)
701 }
702 MemberFilter::Field { name, value } => {
703 let Some(column) = table.field(name) else {
704 return Ok(false);
705 };
706 self.field_passes(table.value(row, column), value)?
707 }
708 };
709 if !passes {
710 return Ok(false);
711 }
712 }
713 Ok(true)
714 }
715
716 fn field_passes(
717 &self,
718 actual: Option<FieldRef<'_>>,
719 expected: &FieldValue,
720 ) -> Result<bool, EvalError> {
721 Ok(match (expected, actual) {
722 (FieldValue::Expression { operator, value }, Some(FieldRef::Concept(concept))) => {
723 let set = self.sub(value)?;
724 set.contains(concept.index()) == (*operator == Equality::Equal)
725 }
726 (FieldValue::Expression { operator, .. }, _) => *operator == Equality::NotEqual,
727 (FieldValue::Numeric { operator, value }, Some(FieldRef::Integer(actual))) => {
728 let expected: f64 = value.0.parse().unwrap_or(f64::NAN);
729 #[expect(
730 clippy::cast_precision_loss,
731 reason = "reference set integers are small"
732 )]
733 let actual = actual as f64;
734 compare_numbers(*operator, actual, expected)
735 }
736 (FieldValue::String { operator, terms }, Some(FieldRef::String(text))) => {
737 terms.iter().any(|t| term_matches(t, text)) == (*operator == Equality::Equal)
738 }
739 (FieldValue::Boolean { operator, value }, Some(FieldRef::String(text))) => {
740 let actual = text.eq_ignore_ascii_case("true");
741 (actual == *value) == (*operator == Equality::Equal)
742 }
743 (FieldValue::Time { operator, values }, Some(FieldRef::String(text))) => {
744 let actual: u32 = text.parse().unwrap_or_default();
745 times_match(*operator, values, actual)
746 }
747 (FieldValue::Time { operator, values }, Some(FieldRef::Integer(actual))) => {
748 let actual = u32::try_from(actual).unwrap_or_default();
749 times_match(*operator, values, actual)
750 }
751 _ => false,
752 })
753 }
754
755 fn sctids_of(&self, set: &ConceptSet) -> Result<Vec<u64>, EvalError> {
759 match set {
760 ConceptSet::Set(references) => Ok(references.iter().map(|r| r.id.0).collect()),
761 ConceptSet::Expression(sub)
762 if sub.operator.is_none()
763 && sub.member_of.is_none()
764 && sub.filters.is_empty()
765 && sub.member_filters.is_empty()
766 && sub.history.is_none() =>
767 {
768 match &sub.focus {
769 FocusConcept::Reference(reference) => Ok(vec![reference.id.0]),
770 _ => self.sctids_of_set(&self.sub(sub)?),
771 }
772 }
773 ConceptSet::Expression(sub) => self.sctids_of_set(&self.sub(sub)?),
774 }
775 }
776
777 fn sctids_of_set(&self, set: &RoaringBitmap) -> Result<Vec<u64>, EvalError> {
778 let mut ids = Vec::new();
779 for concept in set {
780 if let Some(id) = self.model.sctid(Ordinal::new(concept))? {
781 ids.push(id.0);
782 }
783 }
784 Ok(ids)
785 }
786
787 fn acceptabilities(
788 set: Option<&crate::ast::AcceptabilitySet>,
789 ) -> Result<Vec<Acceptability>, EvalError> {
790 match set {
791 None => Ok(Vec::new()),
792 Some(crate::ast::AcceptabilitySet::Tokens(tokens)) => Ok(tokens.clone()),
793 Some(crate::ast::AcceptabilitySet::Concepts(references)) => references
794 .iter()
795 .map(|reference| match reference.id.0 {
796 PREFERRED => Ok(Acceptability::Preferred),
797 ACCEPTABLE => Ok(Acceptability::Acceptable),
798 _ => Err(EvalError::Unsupported(
799 "an acceptability concept other than preferred or acceptable",
800 )),
801 })
802 .collect(),
803 }
804 }
805
806 fn concept_predicates(
808 &self,
809 filters: &[ConceptFilter],
810 ) -> Result<Vec<ConceptPredicate>, EvalError> {
811 let mut predicates = Vec::new();
812 for filter in filters {
813 predicates.push(match filter {
814 ConceptFilter::Active { operator, value } => {
815 ConceptPredicate::Active(*value == (*operator == Equality::Equal))
816 }
817 ConceptFilter::DefinitionStatus { operator, tokens } => {
818 let defined = tokens.contains(&DefinitionStatus::Defined);
819 let primitive = tokens.contains(&DefinitionStatus::Primitive);
820 let equal = *operator == Equality::Equal;
821 ConceptPredicate::DefinitionStatus {
822 defined: defined == equal,
823 primitive: primitive == equal,
824 }
825 }
826 ConceptFilter::DefinitionStatusId { operator, value } => {
827 let ids = self.sctids_of(value)?;
828 let equal = *operator == Equality::Equal;
829 ConceptPredicate::DefinitionStatus {
830 defined: ids.contains(&DEFINED) == equal,
831 primitive: ids.contains(&PRIMITIVE) == equal,
832 }
833 }
834 ConceptFilter::Module { operator, value } => ConceptPredicate::Module {
835 modules: self.sctids_of(value)?,
836 negated: *operator == Equality::NotEqual,
837 },
838 ConceptFilter::EffectiveTime { operator, values } => {
839 ConceptPredicate::EffectiveTime {
840 operator: *operator,
841 values: values
842 .iter()
843 .map(|v| v.0.parse().unwrap_or_default())
844 .collect(),
845 }
846 }
847 });
848 }
849 Ok(predicates)
850 }
851
852 fn description_predicates(
854 &self,
855 filters: &[DescriptionFilter],
856 ) -> Result<Vec<DescriptionPredicate>, EvalError> {
857 let mut predicates = Vec::new();
858 for filter in filters {
859 predicates.push(match filter {
860 DescriptionFilter::Term { operator, terms } => DescriptionPredicate::Term {
861 operator: *operator,
862 terms: terms.clone(),
863 },
864 DescriptionFilter::Language { operator, codes } => DescriptionPredicate::Language {
865 operator: *operator,
866 codes: codes.clone(),
867 },
868 DescriptionFilter::TypeId { operator, value } => DescriptionPredicate::Type {
869 operator: *operator,
870 types: self.sctids_of(value)?,
871 },
872 DescriptionFilter::Type { operator, tokens } => DescriptionPredicate::Type {
873 operator: *operator,
874 types: tokens
875 .iter()
876 .map(|t| match t {
877 TypeToken::Synonym => SYNONYM,
878 TypeToken::FullySpecifiedName => FULLY_SPECIFIED_NAME,
879 TypeToken::Definition => DEFINITION,
880 })
881 .collect(),
882 },
883 DescriptionFilter::DialectId {
884 operator,
885 value,
886 acceptability,
887 } => {
888 let shared = Self::acceptabilities(acceptability.as_ref())?;
889 let dialects = match value {
890 DialectIdValue::Expression(sub) => self
891 .sctids_of(&ConceptSet::Expression(sub.clone()))?
892 .into_iter()
893 .map(|id| (id, shared.clone()))
894 .collect(),
895 DialectIdValue::Set(items) => {
896 let mut out = Vec::new();
897 for (reference, own) in items {
898 let allowed = if own.is_some() {
899 Self::acceptabilities(own.as_ref())?
900 } else {
901 shared.clone()
902 };
903 out.push((reference.id.0, allowed));
904 }
905 out
906 }
907 };
908 DescriptionPredicate::Dialect {
909 operator: *operator,
910 dialects,
911 }
912 }
913 DescriptionFilter::Dialect {
914 operator,
915 aliases,
916 acceptability,
917 } => {
918 let shared = Self::acceptabilities(acceptability.as_ref())?;
919 let mut dialects = Vec::new();
920 for alias in aliases {
921 let refset = crate::dialects::refset(&alias.alias)
922 .ok_or_else(|| EvalError::UnknownDialect(alias.alias.clone()))?;
923 let allowed = if alias.acceptability.is_some() {
924 Self::acceptabilities(alias.acceptability.as_ref())?
925 } else {
926 shared.clone()
927 };
928 dialects.push((refset, allowed));
929 }
930 DescriptionPredicate::Dialect {
931 operator: *operator,
932 dialects,
933 }
934 }
935 DescriptionFilter::Module { .. } => {
936 return Err(EvalError::Unsupported(
937 "a description module filter: the store keeps no description module",
938 ));
939 }
940 DescriptionFilter::EffectiveTime { .. } => {
941 return Err(EvalError::Unsupported(
942 "a description effective time filter: the store keeps no description time",
943 ));
944 }
945 DescriptionFilter::Active { operator, value } => {
946 DescriptionPredicate::Active(*value == (*operator == Equality::Equal))
947 }
948 DescriptionFilter::Id { operator, ids } => DescriptionPredicate::Id {
949 operator: *operator,
950 ids: ids.iter().map(|id| id.0).collect(),
951 },
952 });
953 }
954 Ok(predicates)
955 }
956
957 fn history(
960 &self,
961 mut set: RoaringBitmap,
962 history: &HistorySupplement,
963 ) -> Result<RoaringBitmap, EvalError> {
964 let refsets: Vec<u64> = match history {
965 HistorySupplement::Minimum => vec![SAME_AS],
966 HistorySupplement::Moderate => {
967 vec![SAME_AS, REPLACED_BY, WAS_A, PARTIALLY_EQUIVALENT_TO]
968 }
969 HistorySupplement::Default | HistorySupplement::Maximum => {
970 match self.model.concept(Sctid(HISTORICAL_ASSOCIATION))? {
971 Some(root) => {
972 let mut ids = Vec::new();
973 for concept in self.model.descendants(root) {
974 if let Some(id) = self.model.sctid(Ordinal::new(concept))? {
975 ids.push(id.0);
976 }
977 }
978 ids
979 }
980 None => Vec::new(),
981 }
982 }
983 HistorySupplement::Subset(constraint) => {
984 let mut ids = Vec::new();
985 for concept in self.expression(constraint)? {
986 if let Some(id) = self.model.sctid(Ordinal::new(concept))? {
987 ids.push(id.0);
988 }
989 }
990 ids
991 }
992 };
993 let mut added = RoaringBitmap::new();
994 for refset in refsets {
995 let Some(table) = self.model.members().table(refset) else {
996 continue;
997 };
998 let Some(target) = table.field(TARGET_COMPONENT) else {
999 continue;
1000 };
1001 for row in 0..table.len() {
1002 if let (Some(FieldRef::Concept(value)), Some(member)) =
1003 (table.value(row, target), table.concept(row))
1004 && set.contains(value.index())
1005 {
1006 added.insert(member.index());
1007 }
1008 }
1009 }
1010 set |= added;
1011 Ok(set)
1012 }
1013
1014 fn kinds(&self, name: &SubExpressionConstraint) -> Result<Kinds, EvalError> {
1016 if matches!(name.focus, FocusConcept::Wildcard)
1017 && name.operator.is_none()
1018 && name.filters.is_empty()
1019 {
1020 return Ok(Kinds::Any);
1021 }
1022 let types = self.sub(name)?;
1023 let attributes = self.model.attributes();
1024 let mut kinds = Vec::new();
1025 for concept in types {
1026 if let Some(id) = self.model.sctid(Ordinal::new(concept))?
1027 && let Some(kind) = attributes.kind(id.0)
1028 {
1029 kinds.push(kind);
1030 }
1031 }
1032 Ok(Kinds::These(kinds))
1033 }
1034
1035 fn values_of(&self, set: &RoaringBitmap, kinds: &Kinds) -> RoaringBitmap {
1037 let attributes = self.model.attributes();
1038 let mut out = RoaringBitmap::new();
1039 for source in set {
1040 for row in attributes.rows(Ordinal::new(source)) {
1041 if let ValueRef::Concept(target) = row.value
1042 && kinds.contains(row.kind)
1043 {
1044 out.insert(target.index());
1045 }
1046 }
1047 }
1048 out
1049 }
1050
1051 fn refinement(
1053 &self,
1054 focus: &RoaringBitmap,
1055 refinement: &Refinement,
1056 ) -> Result<RoaringBitmap, EvalError> {
1057 match refinement {
1058 Refinement::Single(one) => self.sub_refinement(focus, one),
1059 Refinement::Conjunction(items) => {
1060 let mut result = focus.clone();
1061 for item in items {
1062 result &= self.sub_refinement(&result, item)?;
1063 }
1064 Ok(result)
1065 }
1066 Refinement::Disjunction(items) => {
1067 let mut result = RoaringBitmap::new();
1068 for item in items {
1069 result |= self.sub_refinement(focus, item)?;
1070 }
1071 Ok(result)
1072 }
1073 }
1074 }
1075
1076 fn sub_refinement(
1077 &self,
1078 focus: &RoaringBitmap,
1079 item: &SubRefinement,
1080 ) -> Result<RoaringBitmap, EvalError> {
1081 match item {
1082 SubRefinement::AttributeSet(set) => self.attribute_set(focus, set),
1083 SubRefinement::Nested(inner) => self.refinement(focus, inner),
1084 SubRefinement::Group {
1085 cardinality,
1086 attributes,
1087 } => {
1088 let tests = self.compile_set(attributes)?;
1089 let graph = self.model.attributes();
1090 let candidates = if cardinality.is_none_or(|c| c.min >= 1) {
1095 self.attribute_set(focus, attributes)?
1096 } else {
1097 focus.clone()
1098 };
1099 let mut out = RoaringBitmap::new();
1100 for concept in &candidates {
1101 let rows: Vec<Row<'_>> = graph.rows(Ordinal::new(concept)).collect();
1102 let mut groups: Vec<Vec<&Row<'_>>> = Vec::new();
1103 let mut current: Option<u32> = None;
1104 for row in &rows {
1105 if row.group == 0 || current != Some(row.group) {
1108 groups.push(Vec::new());
1109 current = Some(row.group);
1110 }
1111 if let Some(last) = groups.last_mut() {
1112 last.push(row);
1113 }
1114 }
1115 let matching = groups
1116 .iter()
1117 .filter(|group| set_holds(&tests, group))
1118 .count();
1119 if within(*cardinality, u32::try_from(matching).unwrap_or(u32::MAX)) {
1120 out.insert(concept);
1121 }
1122 }
1123 Ok(out)
1124 }
1125 }
1126 }
1127
1128 fn attribute_set(
1132 &self,
1133 focus: &RoaringBitmap,
1134 set: &AttributeSet,
1135 ) -> Result<RoaringBitmap, EvalError> {
1136 match set {
1137 AttributeSet::Single(one) => self.sub_attribute_set(focus, one),
1138 AttributeSet::Conjunction(items) => {
1139 let mut result = focus.clone();
1140 for item in items {
1141 result &= self.sub_attribute_set(&result, item)?;
1142 }
1143 Ok(result)
1144 }
1145 AttributeSet::Disjunction(items) => {
1146 let mut result = RoaringBitmap::new();
1147 for item in items {
1148 result |= self.sub_attribute_set(focus, item)?;
1149 }
1150 Ok(result)
1151 }
1152 }
1153 }
1154
1155 fn sub_attribute_set(
1156 &self,
1157 focus: &RoaringBitmap,
1158 item: &SubAttributeSet,
1159 ) -> Result<RoaringBitmap, EvalError> {
1160 match item {
1161 SubAttributeSet::Attribute(attribute) => self.attribute(focus, attribute),
1162 SubAttributeSet::Nested(inner) => self.attribute_set(focus, inner),
1163 }
1164 }
1165
1166 fn attribute(
1168 &self,
1169 focus: &RoaringBitmap,
1170 attribute: &Attribute,
1171 ) -> Result<RoaringBitmap, EvalError> {
1172 let test = self.compile(attribute)?;
1173 let graph = self.model.attributes();
1174 if attribute.reverse {
1175 return Ok(self.reverse(focus, &test));
1176 }
1177 let default = attribute.cardinality.is_none();
1178 if default
1179 && let ValueTest::Concept {
1180 set,
1181 negated: false,
1182 } = &test.value
1183 {
1184 let mut out = RoaringBitmap::new();
1185 for kind in test.kinds.iter(graph.types().len()) {
1186 match set {
1187 None => {
1188 if let Some(sources) = graph.sources_of_kind(kind) {
1189 out |= sources;
1190 }
1191 }
1192 Some(values) => {
1193 for target in graph.targets_of_kind(kind) {
1194 if values.contains(*target) {
1195 out.extend(
1196 graph.sources(kind, Ordinal::new(*target)).iter().copied(),
1197 );
1198 }
1199 }
1200 }
1201 }
1202 }
1203 return Ok(out & focus);
1204 }
1205 let mut out = RoaringBitmap::new();
1206 for concept in focus {
1207 let count = graph
1208 .rows(Ordinal::new(concept))
1209 .filter(|row| test.matches(row))
1210 .count();
1211 if within(
1212 attribute.cardinality,
1213 u32::try_from(count).unwrap_or(u32::MAX),
1214 ) {
1215 out.insert(concept);
1216 }
1217 }
1218 Ok(out)
1219 }
1220
1221 fn reverse(&self, focus: &RoaringBitmap, test: &AttributeTest) -> RoaringBitmap {
1224 let graph = self.model.attributes();
1225 let sources = match &test.value {
1226 ValueTest::Concept {
1227 set: Some(set),
1228 negated: false,
1229 } => Some(set),
1230 _ => None,
1231 };
1232 let mut out = RoaringBitmap::new();
1233 for target in focus {
1234 let mut count = 0_u32;
1235 for kind in test.kinds.iter(graph.types().len()) {
1236 for source in graph.sources(kind, Ordinal::new(target)) {
1237 if sources.is_none_or(|set| set.contains(*source)) {
1238 count = count.saturating_add(1);
1239 }
1240 }
1241 }
1242 if within(test.cardinality, count) {
1243 out.insert(target);
1244 }
1245 }
1246 out
1247 }
1248
1249 fn compile_set(&self, set: &AttributeSet) -> Result<CompiledSet, EvalError> {
1251 Ok(match set {
1252 AttributeSet::Single(one) => CompiledSet::Single(Box::new(self.compile_item(one)?)),
1253 AttributeSet::Conjunction(items) => CompiledSet::Conjunction(
1254 items
1255 .iter()
1256 .map(|i| self.compile_item(i))
1257 .collect::<Result<_, _>>()?,
1258 ),
1259 AttributeSet::Disjunction(items) => CompiledSet::Disjunction(
1260 items
1261 .iter()
1262 .map(|i| self.compile_item(i))
1263 .collect::<Result<_, _>>()?,
1264 ),
1265 })
1266 }
1267
1268 fn compile_item(&self, item: &SubAttributeSet) -> Result<CompiledItem, EvalError> {
1269 Ok(match item {
1270 SubAttributeSet::Attribute(attribute) => {
1271 CompiledItem::Attribute(self.compile(attribute)?)
1272 }
1273 SubAttributeSet::Nested(inner) => CompiledItem::Nested(self.compile_set(inner)?),
1274 })
1275 }
1276
1277 fn compile(&self, attribute: &Attribute) -> Result<AttributeTest, EvalError> {
1278 let kinds = self.kinds(&attribute.name)?;
1279 let value = match &attribute.value {
1280 AttributeValue::Expression { operator, value } => {
1281 let set = if matches!(value.focus, FocusConcept::Wildcard)
1282 && value.operator.is_none()
1283 && value.filters.is_empty()
1284 && value.member_of.is_none()
1285 {
1286 None
1287 } else {
1288 Some(self.sub(value)?)
1289 };
1290 ValueTest::Concept {
1291 set,
1292 negated: *operator == Equality::NotEqual,
1293 }
1294 }
1295 AttributeValue::Numeric { operator, value } => {
1296 ValueTest::Number(*operator, value.0.parse().unwrap_or(f64::NAN))
1297 }
1298 AttributeValue::String { operator, terms } => ValueTest::Text(*operator, terms.clone()),
1299 AttributeValue::Boolean { operator, value } => ValueTest::Boolean(*operator, *value),
1300 };
1301 Ok(AttributeTest {
1302 kinds,
1303 value,
1304 cardinality: attribute.cardinality,
1305 })
1306 }
1307}
1308
1309fn times_match(operator: Comparison, values: &[TimeValue], actual: u32) -> bool {
1311 let expected = values
1312 .iter()
1313 .map(|v| v.0.parse::<u32>().unwrap_or_default());
1314 match operator {
1315 Comparison::NotEqual => expected.clone().all(|e| actual != e),
1316 _ => expected
1317 .into_iter()
1318 .any(|e| compare_times(operator, actual, e)),
1319 }
1320}
1321
1322#[derive(Debug)]
1324struct AttributeTest {
1325 kinds: Kinds,
1326 value: ValueTest,
1327 cardinality: Option<Cardinality>,
1328}
1329
1330impl AttributeTest {
1331 fn matches(&self, row: &Row<'_>) -> bool {
1332 self.kinds.contains(row.kind) && self.value.matches(row.value)
1333 }
1334}
1335
1336#[derive(Debug)]
1337enum CompiledSet {
1338 Single(Box<CompiledItem>),
1339 Conjunction(Vec<CompiledItem>),
1340 Disjunction(Vec<CompiledItem>),
1341}
1342
1343#[derive(Debug)]
1344enum CompiledItem {
1345 Attribute(AttributeTest),
1346 Nested(CompiledSet),
1347}
1348
1349fn set_holds(set: &CompiledSet, rows: &[&Row<'_>]) -> bool {
1351 match set {
1352 CompiledSet::Single(item) => item_holds(item, rows),
1353 CompiledSet::Conjunction(items) => items.iter().all(|i| item_holds(i, rows)),
1354 CompiledSet::Disjunction(items) => items.iter().any(|i| item_holds(i, rows)),
1355 }
1356}
1357
1358fn item_holds(item: &CompiledItem, rows: &[&Row<'_>]) -> bool {
1359 match item {
1360 CompiledItem::Nested(set) => set_holds(set, rows),
1361 CompiledItem::Attribute(test) => {
1362 let count = rows.iter().filter(|row| test.matches(row)).count();
1363 within(test.cardinality, u32::try_from(count).unwrap_or(u32::MAX))
1364 }
1365 }
1366}
1367
1368#[cfg(test)]
1369mod tests {
1370 use super::{Cardinality, compare_numbers, term_matches, wild_matches, within};
1371 use crate::ast::{Comparison, TypedSearchTerm};
1372
1373 #[test]
1374 fn cardinalities_patterns_and_words_match_as_the_specification_says() {
1375 assert!(within(None, 1) && within(None, 5) && !within(None, 0));
1376 assert!(within(
1377 Some(Cardinality {
1378 min: 0,
1379 max: Some(0)
1380 }),
1381 0
1382 ));
1383 assert!(!within(
1384 Some(Cardinality {
1385 min: 0,
1386 max: Some(0)
1387 }),
1388 1
1389 ));
1390 assert!(within(Some(Cardinality { min: 2, max: None }), 2));
1391 assert!(wild_matches("cardi*opathy", "Cardiomyopathy"));
1392 assert!(!wild_matches("cardi*opathy", "Cardiomyopathy X"));
1393 assert!(wild_matches("*itis", "Bronchitis"));
1394 assert!(wild_matches("a\\*b", "A*B"));
1395 assert!(!wild_matches("a\\*b", "AXB"));
1396 let term = TypedSearchTerm::Match(vec![String::from("hea"), String::from("att")]);
1397 assert!(term_matches(&term, "Heart attack"));
1398 assert!(!term_matches(&term, "Heart"));
1399 assert!(compare_numbers(Comparison::GreaterOrEqual, 500.0, 500.0));
1400 assert!(!compare_numbers(Comparison::Less, 500.0, 500.0));
1401 }
1402}