snomed_ecl_engine/ecl/
members.rs1use 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 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 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 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 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 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}