Skip to main content

snomed_ecl_engine/ecl/
members.rs

1use super::*;
2use crate::decimal::Decimal;
3
4#[derive(Clone, Debug, PartialEq, Eq)]
5pub enum MemberPredicate {
6    Concepts(Box<Expr>),
7    Number(Decimal),
8    Text(Vec<SearchTerm>),
9    Boolean(Option<bool>),
10    Dates(Vec<Option<u32>>),
11    /// Quoted dates also satisfy the untyped string grammar; the column decides their meaning.
12    DatesOrText {
13        dates: Vec<Option<u32>>,
14        terms: Vec<SearchTerm>,
15    },
16}
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct MemberFilter {
19    pub field: String,
20    pub comparison: Comparison,
21    pub value: MemberPredicate,
22}
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct MemberQuery {
25    pub source: Box<Expr>,
26    pub reverse: bool,
27    /// None selects referencedComponentId; an empty list selects all non-metadata fields.
28    pub fields: Option<Vec<String>>,
29    pub filters: Vec<MemberFilter>,
30}
31impl Parser<'_> {
32    pub(super) fn member_fields(&mut self) -> Result<Vec<String>> {
33        self.take("[");
34        self.ws()?;
35        let mut fields = Vec::new();
36        if self.take("*") || self.keyword("any") {
37            self.ws()?;
38        } else {
39            loop {
40                let field = self.word().to_ascii_lowercase();
41                if field.is_empty() || !field.bytes().all(|b| b.is_ascii_alphabetic()) {
42                    return Err(self.unexpected());
43                }
44                if fields.contains(&field) {
45                    self.refuse(self.pos, "A field is projected twice");
46                }
47                self.pos += field.len();
48                fields.push(field);
49                self.ws()?;
50                if !self.take(",") {
51                    break;
52                }
53                self.ws()?;
54            }
55        }
56        if !self.take("]") {
57            return Err(self.unexpected());
58        }
59        self.ws()?;
60        Ok(fields)
61    }
62    pub(super) fn starts_member_filter(&mut self) -> Result<bool> {
63        let saved = self.pos;
64        let result = if self.take("{{") {
65            self.ws()?;
66            self.rest().starts_with(['m', 'M']) && self.filter_name() != "moduleid"
67        } else {
68            false
69        };
70        self.pos = saved;
71        Ok(result)
72    }
73    pub(super) fn member_filters(&mut self, depth: usize) -> Result<Vec<MemberFilter>> {
74        if depth > MAX_DEPTH {
75            return Err(self.error(ParseErrorKind::Limit, "Filter nesting exceeds 64"));
76        }
77        self.take("{{");
78        self.ws()?;
79        if !(self.take("M") || self.take("m")) {
80            return Err(self.unexpected());
81        }
82        self.ws()?;
83        let mut result = Vec::new();
84        loop {
85            let field = self.filter_name();
86            if field.is_empty() || !field.bytes().all(|b| b.is_ascii_alphabetic()) {
87                return Err(self.unexpected());
88            }
89            self.pos += field.len();
90            let comparison = self.comparison()?;
91            let saved = self.pos;
92            if self.take("(") {
93                self.ws()?;
94            }
95            let quoted = !self.starts_alternate()
96                && (self.rest().starts_with('"')
97                    || self.word().eq_ignore_ascii_case("match")
98                    || self.word().eq_ignore_ascii_case("wild"));
99            self.pos = saved;
100            // The activeFilter and effectiveTimeFilter forms come first; the generic
101            // memberFieldFilter still parses other value lexemes, which evaluation types.
102            let dated = field == "effectivetime" && self.rest().starts_with('"');
103            let active_literal = self.rest().starts_with(['*', '"'])
104                || self.word().eq_ignore_ascii_case("any")
105                || matches!(self.rest().as_bytes(), [b'0' | b'1', rest @ ..]
106                    if !rest.first().is_some_and(u8::is_ascii_digit));
107            let value = if field == "active" && active_literal {
108                MemberPredicate::Boolean(self.active_value()?)
109            } else if dated
110                || quoted && !matches!(comparison, Comparison::Eq | Comparison::Ne)
111                || self.rest().starts_with("\"\"")
112            {
113                let mark = self.mark();
114                let list = self.take("(");
115                self.ws()?;
116                let first = self.filter_date();
117                if first.is_err() && dated && matches!(comparison, Comparison::Eq | Comparison::Ne)
118                {
119                    // Not a date, so the grammar reads it as a string compared with a
120                    // field that happens to be named effectiveTime; evaluation types it.
121                    self.reset(mark);
122                    let terms = self.search_terms()?;
123                    result.push(MemberFilter {
124                        field,
125                        comparison,
126                        value: MemberPredicate::Text(terms),
127                    });
128                    self.ws()?;
129                    if self.take("}}") {
130                        return Ok(result);
131                    }
132                    if !self.take(",") {
133                        return Err(self.unexpected());
134                    }
135                    self.ws()?;
136                    continue;
137                }
138                let mut dates = vec![first?];
139                loop {
140                    let spaced = self.ws()?;
141                    if !list || self.take(")") {
142                        break;
143                    }
144                    if !spaced {
145                        return Err(self.unexpected());
146                    }
147                    dates.push(self.filter_date()?);
148                }
149                MemberPredicate::Dates(dates)
150            } else if self.take("#") {
151                let start = self.pos;
152                if !self.take("-") {
153                    self.take("+");
154                }
155                let integer = self.pos;
156                while self.rest().starts_with(|c: char| c.is_ascii_digit()) {
157                    self.pos += 1;
158                }
159                if self.pos == integer
160                    || self.pos - integer > 1 && self.text[integer..].starts_with('0')
161                {
162                    return Err(self.unexpected());
163                }
164                if self.take(".") {
165                    while self.rest().starts_with(|c: char| c.is_ascii_digit()) {
166                        self.pos += 1;
167                    }
168                }
169                MemberPredicate::Number(
170                    Decimal::parse(&self.text[start..self.pos]).ok_or_else(|| self.unexpected())?,
171                )
172            } else if quoted && !self.starts_alternate() {
173                let start = self.pos;
174                if let Ok(dates) = self.member_dates() {
175                    let terms = dates
176                        .iter()
177                        .flatten()
178                        .map(|d| SearchTerm::Match(vec![format!("{d:08}")]))
179                        .collect();
180                    MemberPredicate::DatesOrText { dates, terms }
181                } else {
182                    self.pos = start;
183                    MemberPredicate::Text(self.search_terms()?)
184                }
185            } else if !self.starts_alternate() && self.value_keyword("true") {
186                MemberPredicate::Boolean(Some(true))
187            } else if !self.starts_alternate() && self.value_keyword("false") {
188                MemberPredicate::Boolean(Some(false))
189            } else if field == "moduleid" {
190                // Only moduleFilter admits a bare set of concepts, `(a b)`.
191                MemberPredicate::Concepts(Box::new(self.filter_concepts(depth + 1)?))
192            } else {
193                MemberPredicate::Concepts(Box::new(self.subexpression(depth + 1)?))
194            };
195            if !matches!(
196                value,
197                MemberPredicate::Number(_) | MemberPredicate::Dates(_)
198            ) && !matches!(comparison, Comparison::Eq | Comparison::Ne)
199            {
200                return Err(self.unexpected());
201            }
202            result.push(MemberFilter {
203                field,
204                comparison,
205                value,
206            });
207            self.nodes += 1;
208            if self.nodes > MAX_NODES {
209                return Err(self.error(ParseErrorKind::Limit, "Too many member predicates"));
210            }
211            self.ws()?;
212            if self.take("}}") {
213                return Ok(result);
214            }
215            if !self.take(",") {
216                return Err(self.unexpected());
217            }
218            self.ws()?;
219        }
220    }
221
222    fn member_dates(&mut self) -> Result<Vec<Option<u32>>> {
223        let list = self.take("(");
224        self.ws()?;
225        let mut result = vec![self.filter_date()?];
226        loop {
227            let separated = self.ws()?;
228            if !list || self.take(")") {
229                return Ok(result);
230            }
231            if !separated {
232                return Err(self.unexpected());
233            }
234            result.push(self.filter_date()?);
235        }
236    }
237}