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