Skip to main content

sct_ecl/
eval.rs

1// SPDX-FileCopyrightText: Cadasto B.V.
2// SPDX-License-Identifier: BUSL-1.1
3
4//! The evaluator: an expression constraint to the set of concept ordinals it
5//! selects, as set algebra over the materialized closure, the attribute
6//! graph, and the reference set tables of one edition.
7//!
8//! The semantics are the ECL specification's
9//! (<https://docs.snomed.org/snomed-ct-specifications/snomed-ct-expression-constraint-language>,
10//! the quick reference in Appendix D): `<` is the closure's descendants,
11//! `^` the active members, a refinement the sources whose attribute rows
12//! satisfy the attributes with their cardinalities per role group, and the
13//! filters and history supplements restrict or extend the set. The edition
14//! is reached through [`Model`]; nothing here walks a graph edge by edge.
15
16use 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
31/// The historical association reference set root
32/// (`900000000000522004 |Historical association reference set|`).
33pub const HISTORICAL_ASSOCIATION: u64 = 900_000_000_000_522_004;
34/// `900000000000527005 |SAME AS association reference set|`.
35pub const SAME_AS: u64 = 900_000_000_000_527_005;
36/// `900000000000526001 |REPLACED BY association reference set|`.
37pub const REPLACED_BY: u64 = 900_000_000_000_526_001;
38/// `900000000000528000 |WAS A association reference set|`.
39pub const WAS_A: u64 = 900_000_000_000_528_000;
40/// `1186924009 |PARTIALLY EQUIVALENT TO association reference set|`.
41pub const PARTIALLY_EQUIVALENT_TO: u64 = 1_186_924_009;
42/// The reference set field every member carries.
43const REFERENCED_COMPONENT: &str = "referencedComponentId";
44/// The association reference set field a history supplement follows.
45const TARGET_COMPONENT: &str = "targetComponentId";
46
47/// A failure to evaluate.
48#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
49pub enum EvalError {
50    /// The expression names a concept the edition does not have.
51    #[error("the edition has no concept {0}")]
52    UnknownConcept(Sctid),
53    /// A `^` focus is not a reference set with members in the edition.
54    #[error("{0} is not a reference set with members in the edition")]
55    NotAReferenceSet(Sctid),
56    /// An alternate identifier scheme alias resolves to no identifier scheme.
57    #[error("`{0}` is not an identifier scheme alias of the edition")]
58    UnknownScheme(String),
59    /// An alternate identifier names no concept.
60    #[error("no concept has the alternate identifier {scheme}#{code}")]
61    UnknownIdentifier {
62        /// The scheme alias.
63        scheme: String,
64        /// The code.
65        code: String,
66    },
67    /// A reference set has no field of the name.
68    #[error("reference set {refset} has no field `{field}`")]
69    UnknownField {
70        /// The reference set.
71        refset: Sctid,
72        /// The field.
73        field: String,
74    },
75    /// A construct the edition's data cannot answer.
76    #[error("{0} is not supported")]
77    Unsupported(&'static str),
78    /// A dialect alias the specification does not list.
79    #[error("`{0}` is not a dialect alias")]
80    UnknownDialect(String),
81    /// The edition's storage failed.
82    #[error("the edition could not be read: {0}")]
83    Storage(String),
84}
85
86/// One concept filter with its concept sets resolved, for the model.
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub enum ConceptPredicate {
89    /// The concept is active (or, `false`, inactive).
90    Active(bool),
91    /// The definition status is one of the allowed ones.
92    DefinitionStatus {
93        /// Sufficiently defined concepts pass.
94        defined: bool,
95        /// Primitive concepts pass.
96        primitive: bool,
97    },
98    /// The module is one of `modules` (SCTIDs), or is not when negated.
99    Module {
100        /// The module SCTIDs.
101        modules: Vec<u64>,
102        /// `!=`.
103        negated: bool,
104    },
105    /// The effective time compares to one of `values` (`YYYYMMDD`; `!=` means
106    /// to none of them).
107    EffectiveTime {
108        /// The operator.
109        operator: Comparison,
110        /// The times.
111        values: Vec<u32>,
112    },
113}
114
115/// One description filter with its concept sets and aliases resolved, for
116/// the model; every predicate of one `{{ D ... }}` must hold for the same
117/// description.
118#[derive(Debug, Clone, PartialEq, Eq)]
119pub enum DescriptionPredicate {
120    /// The term matches one of the search terms (or, `!=`, none).
121    Term {
122        /// The operator.
123        operator: Equality,
124        /// The search terms.
125        terms: Vec<TypedSearchTerm>,
126    },
127    /// The language code (the primary subtag) is one of `codes`.
128    Language {
129        /// The operator.
130        operator: Equality,
131        /// Two-letter codes.
132        codes: Vec<String>,
133    },
134    /// The description type is one of `types` (SCTIDs).
135    Type {
136        /// The operator.
137        operator: Equality,
138        /// The description type SCTIDs.
139        types: Vec<u64>,
140    },
141    /// The description is acceptable in one of the language reference sets,
142    /// with one of the listed acceptabilities when any are listed.
143    Dialect {
144        /// The operator.
145        operator: Equality,
146        /// `(language reference set SCTID, allowed acceptabilities)`.
147        dialects: Vec<(u64, Vec<Acceptability>)>,
148    },
149    /// The description is active (or, `false`, inactive).
150    Active(bool),
151    /// The description identifier is one of `ids`.
152    Id {
153        /// The operator.
154        operator: Equality,
155        /// The description identifiers.
156        ids: Vec<u64>,
157    },
158}
159
160/// `900000000000548007 |Preferred|`.
161const PREFERRED: u64 = 900_000_000_000_548_007;
162/// `900000000000549004 |Acceptable|`.
163const ACCEPTABLE: u64 = 900_000_000_000_549_004;
164/// `900000000000073002 |Defined|`.
165const DEFINED: u64 = 900_000_000_000_073_002;
166/// `900000000000074008 |Primitive|`.
167const PRIMITIVE: u64 = 900_000_000_000_074_008;
168/// `900000000000003001 |Fully specified name|`.
169const FULLY_SPECIFIED_NAME: u64 = 900_000_000_000_003_001;
170/// `900000000000013009 |Synonym|`.
171const SYNONYM: u64 = 900_000_000_000_013_009;
172/// `900000000000550004 |Definition|`.
173const DEFINITION: u64 = 900_000_000_000_550_004;
174
175/// The edition an expression is evaluated against.
176pub trait Model {
177    /// The ordinal of a concept, `None` when the edition lacks it.
178    ///
179    /// # Errors
180    ///
181    /// Returns [`EvalError::Storage`] when the edition cannot be read.
182    fn concept(&self, id: Sctid) -> Result<Option<Ordinal>, EvalError>;
183    /// The SCTID of a concept.
184    ///
185    /// # Errors
186    ///
187    /// Returns [`EvalError::Storage`] when the edition cannot be read.
188    fn sctid(&self, concept: Ordinal) -> Result<Option<Sctid>, EvalError>;
189    /// Every concept, active and inactive.
190    fn all(&self) -> RoaringBitmap;
191    /// The concepts with no parent.
192    fn roots(&self) -> RoaringBitmap;
193    /// The concepts with no child.
194    fn leaves(&self) -> RoaringBitmap;
195    /// The transitive descendants, self excluded.
196    fn descendants(&self, concept: Ordinal) -> &RoaringBitmap;
197    /// The transitive ancestors, self excluded.
198    fn ancestors(&self, concept: Ordinal) -> &RoaringBitmap;
199    /// The direct children.
200    fn children(&self, concept: Ordinal) -> RoaringBitmap;
201    /// The direct parents.
202    fn parents(&self, concept: Ordinal) -> RoaringBitmap;
203    /// The attribute relationships.
204    fn attributes(&self) -> &concept_graph::attributes::Attributes;
205    /// The reference set member tables.
206    fn members(&self) -> &concept_graph::refsets::RefsetMembers;
207    /// The alternate identifiers.
208    fn identifiers(&self) -> &concept_graph::identifiers::Identifiers;
209    /// The identifier scheme an alias names (case-insensitively).
210    ///
211    /// # Errors
212    ///
213    /// Returns [`EvalError::Storage`] when the edition cannot be read.
214    fn scheme(&self, alias: &str) -> Result<Option<u64>, EvalError>;
215    /// The concepts of `within` whose concept rows satisfy every predicate.
216    ///
217    /// # Errors
218    ///
219    /// Returns [`EvalError`] for a predicate the edition cannot answer.
220    fn filter_concepts(
221        &self,
222        within: &RoaringBitmap,
223        predicates: &[ConceptPredicate],
224    ) -> Result<RoaringBitmap, EvalError>;
225    /// The concepts of `within` that have one description satisfying every
226    /// predicate.
227    ///
228    /// # Errors
229    ///
230    /// Returns [`EvalError`] for a predicate the edition cannot answer.
231    fn filter_descriptions(
232        &self,
233        within: &RoaringBitmap,
234        predicates: &[DescriptionPredicate],
235    ) -> Result<RoaringBitmap, EvalError>;
236}
237
238/// Evaluates `constraint` against `model`.
239///
240/// # Errors
241///
242/// Returns [`EvalError`] when the expression names something the edition
243/// does not have, or asks for a construct its data cannot answer.
244pub 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/// The kinds of the attribute types a set of type concepts names, or every
262/// kind for `*`.
263#[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/// What an attribute's value must satisfy.
286#[derive(Debug)]
287enum ValueTest {
288    /// The value is a concept in (or, negated, not in) the set; `None` is `*`.
289    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            // NOTE: `= *` matches any value, a concrete one too; the specification
302            // spells the wildcard as any value and the reference servers agree.
303            (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/// Whether `pattern`, with `*` for any run of characters and `\*`, `\"`,
351/// `\\` for the literal characters, matches the whole of `text`
352/// (case-insensitively, as description matching is).
353#[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/// Whether every word of a match term is a prefix of some word of `text`,
381/// case-insensitively, or a wild pattern matches the whole text.
382#[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
400/// `\"` and `\\` to the character they escape.
401fn 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
416/// Whether `count` satisfies a cardinality; `None` is the default `[1..*]`.
417fn 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    /// `subexpressionconstraint`: the focus, the member-of, the operator, then
463    /// the filters and the history supplement.
464    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    /// The constraint operator over a set; the whole edition has the roots
520    /// and the leaves as its answers.
521    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    /// The constraint operator over `*`: the whole edition, less the roots or
550    /// the leaves where the operator excludes them.
551    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    /// `!!>`: the members of the set with no ancestor in the set.
570    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    /// `!!<`: the members of the set with no descendant in the set.
581    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    /// The concepts the hierarchy operator reaches from the set, without the
596    /// set itself.
597    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    /// `^`: the referenced components (or the selected fields) of the active
621    /// members of the reference sets in `set`, those rows passing the member
622    /// filters.
623    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    /// The concepts one reference set contributes: the selected columns of
643    /// every row that passes the member filters.
644    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    /// The concept one selected column of a row holds: the referenced
667    /// component for `None`, else the field's concept value.
668    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    /// The columns a field selection names: `None` is the referenced
679    /// component, `Some(i)` a field; `[*]` is every concept-valued column.
680    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    /// Whether row `row` of `table` satisfies every member filter.
719    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    /// The SCTIDs a concept set names: a plain reference or a reference set
799    /// as written (the metadata concepts need not be in the edition), any
800    /// other constraint by evaluation.
801    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    /// The `{{ C ... }}` filters with their concept sets resolved.
850    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    /// The `{{ D ... }}` filters with their concept sets and aliases resolved.
896    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    /// One `{{ D ... }}` filter with its concept sets and aliases resolved.
908    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    /// The language reference sets a `dialectId` filter names, each with the
973    /// acceptabilities it admits; an entry's own set wins over the shared one.
974    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    /// The language reference sets a `dialect` filter's aliases name, each
1002    /// with the acceptabilities it admits.
1003    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    /// The history supplement: the inactive concepts whose association in the
1023    /// chosen reference sets targets a member of `set`.
1024    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    /// The association reference sets a history supplement follows.
1036    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    /// The members of the association reference sets whose target lies in
1055    /// `set`.
1056    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    /// The members of one association table whose target field lies in `set`.
1071    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    /// The attribute type kinds an attribute name names.
1085    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    /// The concept values of the attributes of `kinds` on the sources in `set`.
1106    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    /// `eclrefinement` over the focus set.
1122    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    /// `eclattributegroup`: the concepts whose relationship groups satisfy the
1162    /// attribute set as often as the cardinality demands.
1163    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        // A concept whose rows satisfy the set within one group satisfies
1172        // it over all its rows, so the ungrouped answer (the inverted
1173        // index) narrows the concepts whose groups are counted, unless
1174        // zero groups may match.
1175        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    /// An ungrouped attribute set over the focus: each attribute is a set of
1195    /// sources (a fast path when the default cardinality and a concept value
1196    /// allow the inverted index, else a row scan), joined by the junction.
1197    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    /// One attribute over the focus, ungrouped.
1233    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    /// The concepts carrying an attribute of one of `kinds` with any value,
1273    /// from the inverted index.
1274    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    /// The concepts carrying an attribute of one of `kinds` whose value is in
1286    /// `values`, from the inverted index.
1287    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    /// `R attribute = X`: the values of the attribute whose sources are in the
1301    /// value set, counted per value for the cardinality, within the focus.
1302    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    /// The compiled tests of an attribute set, for per-group evaluation.
1329    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
1388/// Whether `values` (one time or a set) match `actual` under `operator`.
1389fn 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/// A compiled attribute: the type kinds, the value test, the cardinality.
1402#[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
1428/// The relationship groups of one concept, in row order.
1429fn 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        // NOTE: ECL treats each ungrouped relationship (group 0) as
1434        // its own group; the rows arrive sorted by group.
1435        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
1446/// Whether the rows of one group satisfy the set.
1447fn 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}