1#![allow(dead_code)]
2#![allow(clippy::upper_case_acronyms)]
3
4use std::{convert::TryInto, str::FromStr, sync::Arc};
5
6use super::{
7 non_empty::NonEmptyVec, AggrOp, Arr, FuncCall, Ind, Index, Num, Obj, Operation, Query, SimpleExpr, TagAtom, TagExpr,
8};
9use crate::{language::SortKey, tags::Tag, Timestamp};
10use anyhow::{bail, ensure, Result};
11use chrono::{TimeZone, Utc};
12use once_cell::sync::Lazy;
13use pest::{prec_climber::PrecClimber, Parser};
14use unicode_normalization::UnicodeNormalization;
15use utils::*;
16
17#[derive(Debug, Clone)]
18pub struct NoVal(&'static str);
19impl std::fmt::Display for NoVal {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 write!(f, "no value was present for {}", self.0)
22 }
23}
24impl std::error::Error for NoVal {}
25
26#[derive(pest_derive::Parser)]
27#[grammar = "language/aql.pest"]
28struct Aql;
29
30mod utils;
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub(crate) enum Context {
34 Simple,
35 Aggregate,
36}
37
38#[derive(Debug, Clone, derive_more::Display, derive_more::Error)]
39pub enum ContextError {
40 #[display(fmt = "aggregators are only valid in AGGREGATE clauses")]
41 AggregatorOutsideAggregate,
42 #[display(fmt = "current value _ not available in AGGREGATE clauses")]
43 CurrentValueInAggregate,
44}
45
46fn r_tag(p: P) -> Result<Tag> {
47 let quoted = p.single()?.single()?;
48 let s = quoted.as_str();
49 let s = &s[1..s.len() - 1];
50 match quoted.as_rule() {
51 Rule::single_quoted => Ok(Tag::from_str(s.replace("''", "'").as_ref())?),
52 Rule::double_quoted => Ok(Tag::from_str(s.replace("\"\"", "\"").as_ref())?),
53 x => bail!("unexpected token: {:?}", x),
54 }
55}
56
57enum FromTo {
58 From,
59 To,
60}
61fn r_tag_from_to(p: P, f: FromTo) -> Result<TagExpr> {
62 use TagAtom::*;
63 use TagExpr::Atom;
64 let mut p = p.inner()?;
65 let mut first = p.next().ok_or(NoVal("r_tag_from_to first"))?;
66 Ok(match first.as_rule() {
67 Rule::natural => {
68 let lamport = first.natural()?.into();
69 let stream = p.parse_or_default()?;
71 match f {
72 FromTo::From => Atom(FromLamport(SortKey { lamport, stream })),
73 FromTo::To => Atom(ToLamport(SortKey { lamport, stream })),
74 }
75 }
76 Rule::isodate => match f {
77 FromTo::From => Atom(FromTime(r_timestamp(first)?)),
78 FromTo::To => Atom(ToTime(r_timestamp(first)?)),
79 },
80 x => bail!("unexpected token: {:?}", x),
81 })
82}
83
84fn r_tag_expr(p: P) -> Result<TagExpr> {
85 use TagAtom::*;
86 use TagExpr::Atom;
87
88 static CLIMBER: Lazy<PrecClimber<Rule>> = Lazy::new(|| {
89 use pest::prec_climber::{Assoc::*, Operator};
90
91 PrecClimber::new(vec![Operator::new(Rule::or, Left), Operator::new(Rule::and, Left)])
92 });
93
94 CLIMBER.climb(
95 p.inner()?,
96 |p| {
97 Ok(match p.as_rule() {
98 Rule::tag => Atom(Tag(r_tag(p)?)),
99 Rule::tag_expr => r_tag_expr(p)?,
100 Rule::all_events => Atom(AllEvents),
101 Rule::is_local => Atom(IsLocal),
102 Rule::tag_from => r_tag_from_to(p, FromTo::From)?,
103 Rule::tag_to => r_tag_from_to(p, FromTo::To)?,
104 Rule::tag_app => Atom(AppId(p.single()?.as_str().parse()?)),
105 x => bail!("unexpected token: {:?}", x),
106 })
107 },
108 |lhs, op, rhs| {
109 Ok(match op.as_rule() {
110 Rule::and => lhs?.and(rhs?),
111 Rule::or => lhs?.or(rhs?),
112 x => bail!("unexpected token: {:?}", x),
113 })
114 },
115 )
116}
117
118fn r_string(p: P) -> Result<String> {
119 let p = p.single()?;
120 Ok(match p.as_rule() {
121 Rule::nonempty_string => {
122 let p = p.single()?;
123 let s = p.as_str();
124 let s = &s[1..s.len() - 1];
125 match p.as_rule() {
126 Rule::single_quoted => s.replace("''", "'"),
127 Rule::double_quoted => s.replace("\"\"", "\""),
128 x => bail!("unexpected token: {:?}", x),
129 }
130 }
131 Rule::empty_string => String::new(),
132 x => bail!("unexpected token: {:?}", x),
133 })
134}
135
136fn r_var(p: P, ctx: Context) -> Result<SimpleExpr> {
137 let mut p = p.inner()?;
138 let s = p.next().ok_or(NoVal("no var"))?.as_str();
139 if s == "_" {
140 ensure!(ctx != Context::Aggregate, ContextError::CurrentValueInAggregate);
141 }
142 let head = SimpleExpr::Variable(super::var::Var(s.nfc().collect()));
143 let mut tail = vec![];
144 for mut i in p {
145 match i.as_rule() {
146 Rule::ident => tail.push(Index::String(i.string()?)),
147 Rule::natural => tail.push(Index::Number(i.natural()?)),
148 Rule::string => tail.push(Index::String(r_string(i)?)),
149 Rule::simple_expr => tail.push(Index::Expr(r_simple_expr(i, ctx)?)),
150 x => bail!("unexpected token: {:?}", x),
151 }
152 }
153 Ok(if tail.is_empty() {
154 head
155 } else {
156 SimpleExpr::Indexing(Ind {
157 head: Arc::new(head),
158 tail: tail.try_into()?,
159 })
160 })
161}
162
163fn r_expr_index(p: P, ctx: Context) -> Result<SimpleExpr> {
164 let mut p = p.inner()?;
165 let head = r_simple_expr(p.next().ok_or(NoVal("r_expr_index head"))?, ctx)?;
166 let mut tail = vec![];
167 for mut i in p {
168 match i.as_rule() {
169 Rule::ident => tail.push(Index::String(i.string()?)),
170 Rule::natural => tail.push(Index::Number(i.natural()?)),
171 Rule::string => tail.push(Index::String(r_string(i)?)),
172 Rule::simple_expr => tail.push(Index::Expr(r_simple_expr(i, ctx)?)),
173 x => bail!("unexpected token: {:?}", x),
174 }
175 }
176 Ok(if tail.is_empty() {
177 head
178 } else {
179 SimpleExpr::Indexing(Ind {
180 head: Arc::new(head),
181 tail: tail.try_into()?,
182 })
183 })
184}
185
186fn r_number(mut p: P) -> Result<Num> {
187 p.natural().map(Num::Natural).or_else(|_| p.decimal().map(Num::Decimal))
188}
189
190fn r_timestamp(p: P) -> Result<Timestamp> {
191 let mut p = p.inner()?;
192 let year: i32 = p.string()?.parse()?;
193 let month: u32 = p.string()?.parse()?;
194 let day: u32 = p.string()?.parse()?;
195 let hour: u32 = p.parse_or_default()?;
196 let min: u32 = p.parse_or_default()?;
197 let sec: u32 = p.parse_or_default()?;
198 let nano: u32 = if let Some(p) = p.next() {
199 match p.as_rule() {
200 Rule::millisecond => p.as_str().parse::<u32>()? * 1_000_000,
201 Rule::microsecond => p.as_str().parse::<u32>()? * 1_000,
202 Rule::nanosecond => p.as_str().parse::<u32>()?,
203 x => bail!("unexpected token: {:?}", x),
204 }
205 } else {
206 0
207 };
208 Ok(Utc.ymd(year, month, day).and_hms_nano(hour, min, sec, nano).into())
209}
210
211fn r_object(p: P, ctx: Context) -> Result<Obj> {
212 let mut props = vec![];
213 let mut p = p.inner()?;
214 while p.peek().is_some() {
215 let key = {
216 let mut i = p.next().ok_or(NoVal("key"))?;
217 match i.as_rule() {
218 Rule::ident => Index::String(i.string()?),
219 Rule::natural => Index::Number(i.natural()?),
220 Rule::string => Index::String(r_string(i)?),
221 Rule::simple_expr => Index::Expr(r_simple_expr(i, ctx)?),
222 x => bail!("unexpected token: {:?}", x),
223 }
224 };
225 let value = r_simple_expr(p.next().ok_or(NoVal("value"))?, ctx)?;
226 props.push((key, value));
227 }
228 Ok(Obj { props: props.into() })
229}
230
231fn r_array(p: P, ctx: Context) -> Result<Arr> {
232 Ok(Arr {
233 items: p.inner()?.map(|p| r_simple_expr(p, ctx)).collect::<Result<_>>()?,
234 })
235}
236
237fn r_bool(p: P) -> bool {
238 p.as_str() == "TRUE"
239}
240
241fn r_cases(p: P, ctx: Context) -> Result<NonEmptyVec<(SimpleExpr, SimpleExpr)>> {
242 let mut p = p.inner()?;
243 let mut ret = Vec::new();
244 while let Some(pred) = p.next() {
245 let pred = r_simple_expr(pred, ctx)?;
246 let expr = r_simple_expr(p.next().ok_or(NoVal("case expression"))?, ctx)?;
247 ret.push((pred, expr));
248 }
249 Ok(ret.try_into()?)
250}
251
252fn r_not(p: P) -> Result<P> {
253 p.single()
254}
255
256fn r_aggr(p: P, ctx: Context) -> Result<SimpleExpr> {
257 ensure!(ctx == Context::Aggregate, ContextError::AggregatorOutsideAggregate);
258 let p = p.single()?;
259 Ok(match p.as_rule() {
260 Rule::aggr_sum => SimpleExpr::AggrOp(Arc::new((AggrOp::Sum, r_simple_expr(p.single()?, Context::Simple)?))),
261 Rule::aggr_prod => SimpleExpr::AggrOp(Arc::new((AggrOp::Prod, r_simple_expr(p.single()?, Context::Simple)?))),
262 Rule::aggr_min => SimpleExpr::AggrOp(Arc::new((AggrOp::Min, r_simple_expr(p.single()?, Context::Simple)?))),
263 Rule::aggr_max => SimpleExpr::AggrOp(Arc::new((AggrOp::Max, r_simple_expr(p.single()?, Context::Simple)?))),
264 Rule::aggr_first => SimpleExpr::AggrOp(Arc::new((AggrOp::First, r_simple_expr(p.single()?, Context::Simple)?))),
265 Rule::aggr_last => SimpleExpr::AggrOp(Arc::new((AggrOp::Last, r_simple_expr(p.single()?, Context::Simple)?))),
266 x => bail!("unexpected token: {:?}", x),
267 })
268}
269
270fn r_func_call(p: P, ctx: Context) -> Result<FuncCall> {
271 let mut p = p.inner()?;
272 let name = p.string()?;
273 let mut args = vec![];
274 for p in p {
275 args.push(r_simple_expr(p, ctx)?);
276 }
277 Ok(FuncCall {
278 name,
279 args: args.into(),
280 })
281}
282
283fn r_simple_expr(p: P, ctx: Context) -> Result<SimpleExpr> {
284 static CLIMBER: Lazy<PrecClimber<Rule>> = Lazy::new(|| {
285 use pest::prec_climber::{Assoc::*, Operator};
286 let op = Operator::new;
287
288 PrecClimber::new(vec![
289 op(Rule::alternative, Left),
290 op(Rule::or, Left),
291 op(Rule::xor, Left),
292 op(Rule::and, Left),
293 op(Rule::eq, Left) | op(Rule::ne, Left),
294 op(Rule::lt, Left) | op(Rule::le, Left) | op(Rule::gt, Left) | op(Rule::ge, Left),
295 op(Rule::add, Left) | op(Rule::sub, Left),
296 op(Rule::mul, Left) | op(Rule::div, Left) | op(Rule::modulo, Left),
297 op(Rule::pow, Left),
298 ])
299 });
300
301 fn primary(p: P, ctx: Context) -> Result<SimpleExpr> {
302 Ok(match p.as_rule() {
303 Rule::decimal => SimpleExpr::Number(r_number(p)?),
304 Rule::var_index => r_var(p, ctx)?,
305 Rule::expr_index => r_expr_index(p, ctx)?,
306 Rule::simple_expr => r_simple_expr(p, ctx)?,
307 Rule::simple_not => SimpleExpr::Not(primary(r_not(p)?, ctx)?.into()),
308 Rule::string => SimpleExpr::String(r_string(p)?),
309 Rule::object => SimpleExpr::Object(r_object(p, ctx)?),
310 Rule::array => SimpleExpr::Array(r_array(p, ctx)?),
311 Rule::null => SimpleExpr::Null,
312 Rule::bool => SimpleExpr::Bool(r_bool(p)),
313 Rule::simple_cases => SimpleExpr::Cases(r_cases(p, ctx)?),
314 Rule::aggr_op => r_aggr(p, ctx)?,
315 Rule::func_call => SimpleExpr::FuncCall(r_func_call(p, ctx)?),
316 x => bail!("unexpected token: {:?}", x),
317 })
318 }
319
320 CLIMBER.climb(
321 p.inner()?,
322 |p| primary(p, ctx),
323 |lhs, op, rhs| {
324 Ok(match op.as_rule() {
325 Rule::add => lhs?.add(rhs?),
326 Rule::sub => lhs?.sub(rhs?),
327 Rule::mul => lhs?.mul(rhs?),
328 Rule::div => lhs?.div(rhs?),
329 Rule::modulo => lhs?.modulo(rhs?),
330 Rule::pow => lhs?.pow(rhs?),
331 Rule::and => lhs?.and(rhs?),
332 Rule::or => lhs?.or(rhs?),
333 Rule::xor => lhs?.xor(rhs?),
334 Rule::lt => lhs?.lt(rhs?),
335 Rule::le => lhs?.le(rhs?),
336 Rule::gt => lhs?.gt(rhs?),
337 Rule::ge => lhs?.ge(rhs?),
338 Rule::eq => lhs?.eq(rhs?),
339 Rule::ne => lhs?.ne(rhs?),
340 Rule::alternative => lhs?.alt(rhs?),
341 x => bail!("unexpected token: {:?}", x),
342 })
343 },
344 )
345}
346
347fn r_query(features: Vec<String>, p: P) -> Result<Query> {
348 let mut p = p.inner()?;
349 let mut q = Query {
350 features,
351 from: r_tag_expr(p.next().ok_or(NoVal("tag expression"))?)?,
352 ops: vec![],
353 };
354 for o in p {
355 match o.as_rule() {
356 Rule::filter => q
357 .ops
358 .push(Operation::Filter(r_simple_expr(o.single()?, Context::Simple)?)),
359 Rule::select => {
360 let v = o
361 .inner()?
362 .map(|p| r_simple_expr(p, Context::Simple))
363 .collect::<Result<Vec<_>>>()?;
364 q.ops.push(Operation::Select(v.try_into()?))
365 }
366 Rule::aggregate => q
367 .ops
368 .push(Operation::Aggregate(r_simple_expr(o.single()?, Context::Aggregate)?)),
369 x => bail!("unexpected token: {:?}", x),
370 }
371 }
372 Ok(q)
373}
374
375impl FromStr for Query {
376 type Err = anyhow::Error;
377
378 fn from_str(s: &str) -> Result<Self, Self::Err> {
379 let mut p = Aql::parse(Rule::main_query, s)?.single()?.inner()?;
380 let mut f = p.next().ok_or(NoVal("main query"))?;
381 let features = if f.as_rule() == Rule::features {
382 let features = f.inner()?.map(|mut ff| ff.string()).collect::<Result<_>>()?;
383 f = p.next().ok_or(NoVal("FROM"))?;
384 features
385 } else {
386 vec![]
387 };
388 r_query(features, f)
389 }
390}
391
392impl FromStr for TagExpr {
393 type Err = anyhow::Error;
394
395 fn from_str(s: &str) -> Result<Self, Self::Err> {
396 let p = Aql::parse(Rule::main_tag_expr, s)?.single()?.single()?;
397 r_tag_expr(p)
398 }
399}
400
401impl FromStr for SimpleExpr {
402 type Err = anyhow::Error;
403
404 fn from_str(s: &str) -> Result<Self, Self::Err> {
405 let p = Aql::parse(Rule::main_simple_expr, s)?.single()?.single()?;
406 r_simple_expr(p, Context::Simple)
407 }
408}
409
410impl FromStr for super::var::Var {
411 type Err = anyhow::Error;
412
413 fn from_str(s: &str) -> Result<Self, Self::Err> {
414 let p = Aql::parse(Rule::main_ident, s)?.single()?;
415 Ok(Self(p.as_str().nfc().collect()))
416 }
417}
418
419pub fn is_ident(s: &str) -> bool {
420 Aql::parse(Rule::main_ident, s).is_ok()
421}
422
423#[cfg(test)]
424mod tests {
425 use super::*;
426 use crate::{language::var::Var, tag, NodeId, StreamId};
427 use pest::{fails_with, Parser};
428 use std::convert::TryFrom;
429
430 #[test]
431 fn tag() -> Result<()> {
432 let p = Aql::parse(Rule::tag, "'hello''s revenge'")?;
433 assert_eq!(r_tag(p.single()?)?, tag!("hello's revenge"));
434 let p = Aql::parse(Rule::tag, "\"hello\"\"s revenge\"")?;
435 assert_eq!(r_tag(p.single()?)?, tag!("hello\"s revenge"));
436 Ok(())
437 }
438
439 #[test]
440 fn tag_expr() -> Result<()> {
441 use TagAtom::Tag;
442 use TagExpr::*;
443 assert_eq!(
444 "'x' |\t'y'\n&'z'".parse::<TagExpr>()?,
445 Or((
446 Atom(Tag(tag!("x"))),
447 And((Atom(Tag(tag!("y"))), Atom(Tag(tag!("z")))).into())
448 )
449 .into())
450 );
451 Ok(())
452 }
453
454 #[test]
455 fn simple_expr() -> Result<()> {
456 use super::{Num::*, SimpleExpr::*};
457 assert_eq!(
458 "(x - 5.2 * 1234)^2 / 7 % 5".parse::<SimpleExpr>()?,
459 Variable("x".try_into()?)
460 .sub(Number(Decimal(5.2)).mul(Number(Natural(1234))))
461 .pow(Number(Natural(2)))
462 .div(Number(Natural(7)))
463 .modulo(Number(Natural(5)))
464 );
465
466 fails_with! {
467 parser: Aql,
468 input: "5+3!",
469 rule: Rule::main_simple_expr,
470 positives: vec![Rule::EOI, Rule::add, Rule::sub, Rule::mul, Rule::div, Rule::modulo, Rule::pow, Rule::and,
471 Rule::or, Rule::xor, Rule::lt, Rule::le, Rule::gt, Rule::ge, Rule::eq, Rule::ne, Rule::alternative],
472 negatives: vec![],
473 pos: 3
474 };
475
476 Ok(())
477 }
478
479 #[test]
480 fn query() -> Result<()> {
481 use super::Num::*;
482 use super::{Arr, Ind, Obj};
483 use crate::app_id;
484 use SimpleExpr::*;
485 use TagAtom::*;
486 use TagExpr::Atom;
487
488 assert_eq!(
489 "FROM 'machine' | 'user' END".parse::<Query>()?,
490 Query::new(Tag(tag!("machine")).or(Tag(tag!("user"))))
491 );
492 assert_eq!(
493 "FROM 'machine' |
494 -- or the other
495 'user' & isLocal & from(2012-12-31Z) & to(12345678901234567) & \
496 from(10/1234567890123456789012345678901234567890122-4312) & appId(hello-5.-x-) & allEvents
497 FILTER _.x[42] > 5 SELECT { x: ! 'hello' y: 42 z: [1.3,_.x] } END --"
498 .parse::<Query>()?,
499 Query::new(
500 Atom(Tag(tag!("machine"))).or(Tag(tag!("user"))
501 .and(IsLocal)
502 .and(Atom(FromTime(1356912000000000.into())))
503 .and(Atom(ToLamport(SortKey {
504 lamport: 12345678901234567.into(),
505 stream: StreamId::min(),
506 })))
507 .and(Atom(FromLamport(SortKey {
508 lamport: 10.into(),
509 stream: NodeId([
510 12, 65, 70, 28, 130, 74, 44, 32, 196, 20, 97, 200, 36, 162, 194, 12, 65, 70, 28, 130, 74,
511 44, 32, 196, 20, 97, 200, 36, 162, 194, 12, 65
512 ])
513 .stream(4312.into())
514 })))
515 .and(Atom(AppId(app_id!("hello-5.-x-"))))
516 .and(Atom(AllEvents)))
517 )
518 .with_op(Operation::Filter(Ind::with("_", &[&"x", &42]).gt(Number(Natural(5)))))
519 .with_op(Operation::Select(
520 vec![Obj::with(&[
521 ("x", Not(String("hello".to_owned()).into())),
522 ("y", Number(Natural(42))),
523 ("z", Arr::with(&[Number(Decimal(1.3)), Ind::with("_", &[&"x"])]))
524 ])]
525 .try_into()?
526 ))
527 );
528 Ok(())
529 }
530
531 #[test]
532 fn positive() {
533 let p = |str: &'static str| str.parse::<Query>().unwrap();
534 p("FROM 'machine' | 'user' & isLocal & from(2012-12-31Z) & to(12345678901234567) & appId(hello-5.-x-) & allEvents FILTER _.x[42] > 5 SELECT { x: !'hello', y: 42, z: [1.3, _.x] } END");
535 p("FROM from(2012-12-31T09:30:32.007Z) END");
536 p("FROM from(2012-12-31T09:30:32Z) END");
537 p("FROM from(2012-12-31T09:30:32.007008Z) END");
538 p("FROM 'hello''s revenge' END");
539 p("FROM 'hell''o' FILTER _.x = 'worl''d' END");
540 p("FROM 'a' & 'b' | 'c' END");
541 p("FROM 'a' | 'b' & 'c' END");
542 p("FROM 'a' & ('b' | 'c') END");
543 p("FROM 'a' & 'b' | 'c' & 'd' END");
544 p("FROM ('a' | 'b') & ('c' | 'd') END");
545 }
546
547 #[test]
548 fn negative() {
549 fails_with! {
550 parser: Aql,
551 input: "FROM x",
552 rule: Rule::main_query,
553 positives: vec![Rule::tag_expr],
554 negatives: vec![],
555 pos: 5
556 };
557 fails_with! {
558 parser: Aql,
559 input: "FROM 'x' ELECT 'x'",
560 rule: Rule::main_query,
561 positives: vec![Rule::EOI, Rule::filter, Rule::select, Rule::aggregate, Rule::and, Rule::or],
562 negatives: vec![],
563 pos: 9
564 };
565 fails_with! {
566 parser: Aql,
567 input: "FROM 'x' FITTER 'x'",
568 rule: Rule::main_query,
569 positives: vec![Rule::EOI, Rule::filter, Rule::select, Rule::aggregate, Rule::and, Rule::or],
570 negatives: vec![],
571 pos: 9
572 };
573 }
574
575 #[test]
576 fn expr() {
577 use super::Num::*;
578 use SimpleExpr::*;
579 let p = |s: &'static str| s.parse::<SimpleExpr>().unwrap();
580 assert_eq!(p("NULL"), Null);
581 assert_eq!(p("FALSE"), Bool(false));
582 assert_eq!(p("1"), Number(Natural(1)));
583 assert_eq!(p("1.0"), Number(Natural(1)));
584 assert_eq!(p("'s'"), String("s".into()));
585 assert_eq!(
586 p("[1,TRUE]"),
587 Array(Arr {
588 items: vec![Number(Natural(1)), Bool(true)].into()
589 })
590 );
591 assert_eq!(
592 p("{one:1 ['two']:2 [('three')]:3 [4]:4}"),
593 Object(Obj {
594 props: vec![
595 (Index::String("one".into()), Number(Natural(1))),
596 (Index::String("two".into()), Number(Natural(2))),
597 (Index::Expr(String("three".into())), Number(Natural(3))),
598 (Index::Number(4), Number(Natural(4))),
599 ]
600 .into()
601 })
602 );
603 }
604
605 #[test]
606 fn ident() {
607 let p = |s: &str, e: Option<&str>| {
608 let q = s.parse::<Query>();
609 if let Some(err) = e {
610 let e = q.unwrap_err().to_string();
611 assert!(e.contains(err), "received: {}", e);
612 None
613 } else {
614 match q.unwrap().ops[0].clone() {
615 Operation::Select(v) => Some(v.to_vec()),
616 _ => None,
617 }
618 }
619 };
620 let ind = |s: &str| {
621 SimpleExpr::Indexing(Ind {
622 head: Arc::new(SimpleExpr::Variable(Var::try_from("_").unwrap())),
623 tail: NonEmptyVec::try_from(vec![Index::String(s.to_owned())]).unwrap(),
624 })
625 };
626 let s = |s: &str| Index::String(s.to_owned());
627 let n = |n: u64| SimpleExpr::Number(Num::Natural(n));
628 let v = |s: &str| SimpleExpr::Variable(Var::try_from(s).unwrap());
629
630 p("FROM 'x' SELECT _.H", Some("expected ident"));
631 p("FROM 'x' SELECT _.HE", Some("expected ident"));
632 assert_eq!(
633 p("FROM 'x' SELECT i, iIö, PσΔ", None),
634 Some(vec![v("i"), v("iIö"), v("PσΔ")])
635 );
636 assert_eq!(
637 p("FROM 'x' SELECT _.i, _.iIö, _.PσΔ", None),
638 Some(vec![ind("i"), ind("iIö"), ind("PσΔ")])
639 );
640 assert_eq!(
641 p("FROM 'x' SELECT { i: 1 iIö: 2 PσΔ: 3 }", None),
642 Some(vec![SimpleExpr::Object(Obj {
643 props: Arc::from(vec![(s("i"), n(1)), (s("iIö"), n(2)), (s("PσΔ"), n(3))].as_slice())
644 })])
645 )
646 }
647
648 #[test]
649 fn index() {
650 let p = |s: &str| s.parse::<SimpleExpr>().unwrap();
651 assert_eq!(
652 p("a['ª']"),
653 SimpleExpr::Indexing(Ind {
654 head: Arc::new(SimpleExpr::Variable(Var::try_from("a").unwrap())),
655 tail: vec![Index::String("ª".to_owned())].try_into().unwrap()
656 })
657 );
658 assert_eq!(
659 p("a.ª"),
660 SimpleExpr::Indexing(Ind {
661 head: Arc::new(SimpleExpr::Variable(Var::try_from("a").unwrap())),
662 tail: vec![Index::String("ª".to_owned())].try_into().unwrap()
663 })
664 );
665 assert_eq!(p("a['ª']").to_string(), "a.ª");
666
667 assert_eq!(
668 p("{ⓐ:1}"),
669 SimpleExpr::Object(Obj {
670 props: vec![(Index::String("ⓐ".to_owned()), SimpleExpr::Number(Num::Natural(1)))].into()
671 })
672 );
673 assert_eq!(p("{ⓐ:1}").to_string(), "{ ⓐ: 1 }");
674 }
675
676 #[test]
677 fn aggregate() {
678 let p = |s: &str, e: Option<&str>| {
679 let q = s.parse::<Query>();
680 if let Some(err) = e {
681 let e = q.unwrap_err().to_string();
682 assert!(e.contains(err), "received: {}", e);
683 } else {
684 q.unwrap();
685 }
686 };
687
688 p(
689 "FROM 'x' FILTER SUM(1)",
690 Some("aggregators are only valid in AGGREGATE clauses"),
691 );
692 p(
693 "FROM 'x' SELECT 1 + SUM(1)",
694 Some("aggregators are only valid in AGGREGATE clauses"),
695 );
696 p("FROM 'x' FILTER _", None);
697 p(
698 "FROM 'x' AGGREGATE 1 + _",
699 Some("current value _ not available in AGGREGATE clauses"),
700 );
701 p("FROM 'x' AGGREGATE 1 + 2", None);
702 p(
703 "FROM 'x' AGGREGATE { a: LAST(_ + 1) b: FIRST(_.a.b) c: MIN(1 / _) d: MAX([_]) e: SUM(1.3 * _) }",
704 None,
705 );
706 }
707
708 #[test]
709 fn func_call() {
710 let p = |s: &str, e: Option<&str>| {
711 let q = s.parse::<Query>();
712 if let Some(err) = e {
713 let e = q.unwrap_err().to_string();
714 assert!(e.contains(err), "received: {}", e);
715 None
716 } else {
717 match q.unwrap().ops[0].clone() {
718 Operation::Select(v) => Some(v.to_vec()),
719 _ => None,
720 }
721 }
722 };
723
724 assert_eq!(
725 p("FROM 'x' SELECT Func()", None),
726 Some(vec![SimpleExpr::FuncCall(FuncCall {
727 name: "Func".to_owned(),
728 args: vec![].into()
729 })])
730 );
731 assert_eq!(
732 p("FROM 'x' SELECT Fÿnc('x')", None),
733 Some(vec![SimpleExpr::FuncCall(FuncCall {
734 name: "Fÿnc".to_owned(),
735 args: vec![SimpleExpr::String("x".to_owned())].into()
736 })])
737 );
738 assert_eq!(
739 p("FROM 'x' SELECT Func(x, 'x')", None),
740 Some(vec![SimpleExpr::FuncCall(FuncCall {
741 name: "Func".to_owned(),
742 args: vec![
743 SimpleExpr::Variable(Var::try_from("x").unwrap()),
744 SimpleExpr::String("x".to_owned())
745 ]
746 .into()
747 })])
748 );
749 }
750}