1use std::borrow::Cow;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
23#[non_exhaustive]
24pub enum OperatorFamily {
25 Core,
27 Datetime,
30 ExtString,
33 ExtArray,
35 ExtMath,
37 ExtControl,
39 ExtObject,
41 ErrorHandling,
43 Tensor,
46 Secret,
48 Custom,
50}
51
52impl OperatorFamily {
53 fn of(upstream: datalogic_rs::Family) -> Option<Self> {
56 use datalogic_rs::Family as F;
57 Some(match upstream {
58 F::Core => Self::Core,
59 F::DateTime => Self::Datetime,
60 F::ExtString => Self::ExtString,
61 F::ExtArray => Self::ExtArray,
62 F::ExtMath => Self::ExtMath,
63 F::ExtControl => Self::ExtControl,
64 F::ExtObject => Self::ExtObject,
65 F::ErrorHandling => Self::ErrorHandling,
66 F::Tensor => Self::Tensor,
67 _ => return None,
68 })
69 }
70
71 pub(crate) fn to_upstream(self) -> Option<datalogic_rs::Family> {
74 use datalogic_rs::Family as F;
75 Some(match self {
76 Self::Core => F::Core,
77 Self::Datetime => F::DateTime,
78 Self::ExtString => F::ExtString,
79 Self::ExtArray => F::ExtArray,
80 Self::ExtMath => F::ExtMath,
81 Self::ExtControl => F::ExtControl,
82 Self::ExtObject => F::ExtObject,
83 Self::ErrorHandling => F::ErrorHandling,
84 Self::Tensor => F::Tensor,
85 Self::Secret | Self::Custom => return None,
86 })
87 }
88
89 fn named(name: &str) -> Option<Self> {
91 datalogic_rs::Family::ALL
92 .iter()
93 .find(|f| f.name() == name)
94 .and_then(|f| Self::of(*f))
95 }
96}
97
98pub(crate) const ALL_OPERATORS: &[OperatorFamily] = &[
104 #[cfg(feature = "datetime")]
105 OperatorFamily::Datetime,
106 OperatorFamily::ExtString,
107 OperatorFamily::ExtArray,
108 OperatorFamily::ExtMath,
109 OperatorFamily::ExtControl,
110 OperatorFamily::ExtObject,
111 OperatorFamily::ErrorHandling,
112];
113
114#[derive(Clone, Debug, PartialEq, Eq)]
129#[non_exhaustive]
130pub struct Operator {
131 pub name: Cow<'static, str>,
133 pub family: OperatorFamily,
135 pub scoping: bool,
137 pub deterministic: bool,
139}
140
141impl Operator {
142 pub fn custom(name: impl Into<Cow<'static, str>>) -> Self {
144 Self {
145 name: name.into(),
146 family: OperatorFamily::Custom,
147 scoping: false,
148 deterministic: false,
149 }
150 }
151
152 #[must_use]
154 pub fn scoping(mut self, yes: bool) -> Self {
155 self.scoping = yes;
156 self
157 }
158
159 #[must_use]
161 pub fn deterministic(mut self, yes: bool) -> Self {
162 self.deterministic = yes;
163 self
164 }
165
166 pub(crate) fn secret() -> Self {
167 Self {
168 name: Cow::Borrowed(super::secrets::SECRET_OPERATOR),
169 family: OperatorFamily::Secret,
170 scoping: false,
171 deterministic: true,
172 }
173 }
174
175 pub(crate) fn from_logic(op: datalogic_rs::OperatorInfo) -> impl Iterator<Item = Self> {
180 let family = OperatorFamily::named(op.family);
181 debug_assert!(
182 family.is_some(),
183 "`{}` is in family `{}`, which no feature turns on",
184 op.name,
185 op.family
186 );
187 family.into_iter().flat_map(move |family| {
188 std::iter::once(op.name)
189 .chain(op.aliases.iter().copied())
190 .map(move |name| Self {
191 name: Cow::Borrowed(name),
192 family,
193 scoping: op.scoped_arg.is_some(),
194 deterministic: op.effect != "clock",
195 })
196 })
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use crate::expr::ExprEngine;
204 use serde_json::{Value, json};
205
206 fn probe(engine: &ExprEngine, logic: Value) -> Value {
207 engine.eval_json(&logic)
208 }
209
210 fn rows() -> Value {
211 json!([{"a": 1}, {"a": 2}])
212 }
213
214 fn listed() -> Vec<Operator> {
216 ExprEngine::builder()
217 .all_operators()
218 .build()
219 .unwrap()
220 .operators()
221 .to_vec()
222 }
223
224 #[test]
225 fn the_expanded_names_are_datalogics_in_its_order_with_no_duplicate() {
226 let logic = datalogic_rs::Engine::builder()
227 .with_families(ALL_OPERATORS.iter().filter_map(|f| f.to_upstream()))
228 .build();
229 let ours: Vec<String> = listed()
230 .into_iter()
231 .filter(|o| o.family != OperatorFamily::Secret)
232 .map(|o| o.name.into_owned())
233 .collect();
234 let mut theirs: Vec<&str> = logic.builtin_operator_names().collect();
235 let mut sorted = ours.clone();
236 sorted.sort_unstable();
237 sorted.dedup();
238 assert_eq!(sorted.len(), ours.len(), "a name twice");
239 theirs.sort_unstable();
240 assert_eq!(
241 sorted, theirs,
242 "every name datalogic answers to, aliases included"
243 );
244 let at = |name: &str| ours.iter().position(|n| n == name).unwrap();
246 assert_eq!(at("var"), at("val") + 1);
247 assert_eq!(at("?:"), at("if") + 1);
248 }
249
250 #[test]
251 fn the_scoping_and_the_clock_are_datalogics_facts() {
252 let ops = listed();
253 let scoping: Vec<&str> = ops.iter().filter(|o| o.scoping).map(|o| &*o.name).collect();
254 let non: Vec<&str> = ops
255 .iter()
256 .filter(|o| !o.deterministic)
257 .map(|o| &*o.name)
258 .collect();
259 let mut want_scoping = vec![
260 "filter", "map", "reduce", "all", "some", "none", "sort", "group_by", "distinct", "try",
261 ];
262 let mut want_non = Vec::new();
263 if cfg!(feature = "datetime") {
264 want_non.push("now");
265 }
266 let mut scoping = scoping;
267 scoping.sort_unstable();
268 want_scoping.sort_unstable();
269 assert_eq!(scoping, want_scoping);
270 assert_eq!(non, want_non);
271 }
272
273 #[test]
274 fn every_family_datalogic_has_but_flagd_is_one_of_ours() {
275 for family in datalogic_rs::Family::ALL {
278 let mapped = OperatorFamily::of(*family);
279 assert_eq!(
280 mapped.is_none(),
281 family.name() == "Flagd",
282 "{}",
283 family.name()
284 );
285 if let Some(ours) = mapped {
286 assert_eq!(ours.to_upstream(), Some(*family));
287 }
288 }
289 }
290
291 #[test]
292 fn the_effects_are_the_ones_this_crate_reads() {
293 let logic = datalogic_rs::Engine::builder()
296 .with_families(ALL_OPERATORS.iter().filter_map(|f| f.to_upstream()))
297 .build();
298 let mut effects: Vec<&str> = logic.operators().map(|o| o.effect).collect();
299 effects.sort_unstable();
300 effects.dedup();
301 for effect in effects {
302 assert!(
303 ["pure", "clock", "throws", "catches"].contains(&effect),
304 "an effect this crate does not read: {effect}"
305 );
306 }
307 }
308
309 #[test]
310 fn core_scoping_operators_read_the_element() {
311 let e = ExprEngine::default();
312 let a = rows();
313 assert_eq!(probe(&e, json!({"map": [a, {"var": "a"}]})), json!([1, 2]));
314 assert_eq!(
315 probe(&e, json!({"filter": [a, {">": [{"var": "a"}, 1]}]})),
316 json!([{"a": 2}])
317 );
318 assert_eq!(
319 probe(&e, json!({"all": [a, {">": [{"var": "a"}, 0]}]})),
320 json!(true)
321 );
322 assert_eq!(
323 probe(&e, json!({"some": [a, {">": [{"var": "a"}, 1]}]})),
324 json!(true)
325 );
326 assert_eq!(
327 probe(&e, json!({"none": [a, {">": [{"var": "a"}, 5]}]})),
328 json!(true)
329 );
330 assert_eq!(
331 probe(
332 &e,
333 json!({"reduce": [a, {"+": [{"var": "current.a"}, {"var": "accumulator"}]}, 0]})
334 ),
335 json!(3)
336 );
337 }
338
339 #[test]
340 fn ext_array_scoping_operators_read_the_element() {
341 let e = ExprEngine::builder()
342 .family(OperatorFamily::ExtArray)
343 .build()
344 .unwrap();
345 let a = rows();
346 assert_eq!(
347 probe(&e, json!({"sort": [a, false, {"var": "a"}]})),
348 json!([{"a": 2}, {"a": 1}])
349 );
350 assert_eq!(
351 probe(&e, json!({"group_by": [a, {"var": "a"}]})),
352 json!([
353 {"key": 1, "items": [{"a": 1}]},
354 {"key": 2, "items": [{"a": 2}]}
355 ])
356 );
357 }
358}