1use tree_sitter::Node;
19
20use super::{node_range, partial};
21use crate::ast::{
22 BinaryOp, Block, Call, Closure, Expr, GraphDir, GraphStep, Idiom, IdiomPart, Literal, PrefixOp,
23 Spanned, TypeExpr,
24};
25
26pub fn lower_expr(node: Node<'_>, text: &str) -> Spanned<Expr> {
28 Lowerer { text }.expr(node)
29}
30
31pub fn lower_type_expr(node: Node<'_>, text: &str) -> Spanned<TypeExpr> {
35 Lowerer { text }.type_expr(node)
36}
37
38pub fn lower_block_node(node: Node<'_>, text: &str) -> Block {
41 Lowerer { text }.block(node)
42}
43
44pub fn lower_idiom_node(node: Node<'_>, text: &str) -> Idiom {
47 let lowerer = Lowerer { text };
48 match node.kind() {
49 "Ident" => Idiom {
50 parts: vec![lowerer.spanned(node, IdiomPart::Field(text[node.byte_range()].into()))],
51 },
52 _ => lowerer.idiom(node),
53 }
54}
55
56struct Lowerer<'a> {
57 text: &'a str,
58}
59
60impl Lowerer<'_> {
61 fn node_text(&self, node: Node<'_>) -> &str {
62 &self.text[node.byte_range()]
63 }
64
65 fn spanned<T>(&self, node: Node<'_>, value: T) -> Spanned<T> {
66 Spanned::new(value, node_range(node))
67 }
68
69 fn expr(&self, node: Node<'_>) -> Spanned<Expr> {
70 if node.is_error() || node.is_missing() {
71 return self.spanned(node, Expr::Partial(partial(node)));
72 }
73
74 let expr = match node.kind() {
75 "Predicate" | "Fields" => match single_named_child(node) {
77 Some(child) => return self.expr(child),
78 None => Expr::Partial(partial(node)),
79 },
80 "Number" => self.number_literal(node),
81 "String" => self.string_literal(node),
82 "Bool" => Expr::Literal(Literal::Bool(
83 self.node_text(node).eq_ignore_ascii_case("true"),
84 )),
85 "None" => {
86 if self.node_text(node).eq_ignore_ascii_case("null") {
87 Expr::Literal(Literal::Null)
88 } else {
89 Expr::Literal(Literal::None)
90 }
91 }
92 "Duration" => Expr::Literal(Literal::Duration(self.node_text(node).to_string())),
93 "Regex" => Expr::Literal(Literal::Regex(
94 self.node_text(node).trim_matches('/').to_string(),
95 )),
96 "VariableName" => Expr::Param(self.param_name(node)),
97 "RecordId" => self.record_id(node),
98 "Array" => Expr::Array(
99 named_children(node)
100 .into_iter()
101 .map(|child| self.expr(child))
102 .collect(),
103 ),
104 "Object" => self.object(node),
105 "BinaryExpression" => self.binary(node),
106 "PrefixExpression" => self.prefix(node),
107 "FunctionCall" => Expr::Call(self.call(node)),
108 "TypeCast" => self.cast(node),
109 "SubQuery" => self.subquery(node),
110 "SelectStatement" | "CreateStatement" | "UpdateStatement" | "UpsertStatement"
116 | "DeleteStatement" | "InsertStatement" | "RelateStatement" | "IfElseStatement" => {
117 Expr::Subquery(Box::new(super::statement::lower_statement(node, self.text)))
118 }
119 "Block" => Expr::Block(self.block(node)),
120 "Closure" => self.closure(node),
121 "Path" | "Idiom" => Expr::Idiom(self.idiom(node)),
122 "Ident" => Expr::Idiom(Idiom {
123 parts: vec![self.spanned(node, IdiomPart::Field(self.node_text(node).into()))],
124 }),
125 _ => Expr::Partial(partial(node)),
126 };
127 self.spanned(node, expr)
128 }
129
130 fn number_literal(&self, node: Node<'_>) -> Expr {
131 let text = self.node_text(node);
134 let literal = match named_children(node).first().map(tree_sitter::Node::kind) {
135 Some("Float") => text
136 .parse::<f64>()
137 .map_or(Literal::Float(0.0), Literal::Float),
138 Some("Decimal") => Literal::Decimal,
139 _ => text.parse::<i64>().map_or(Literal::Int(0), Literal::Int),
140 };
141 Expr::Literal(literal)
142 }
143
144 fn string_literal(&self, node: Node<'_>) -> Expr {
145 let text = self.node_text(node);
146 let bytes = text.as_bytes();
147 let prefixed = bytes.len() > 2 && matches!(bytes.get(1), Some(b'\'' | b'"'));
148 let inner = || text[1..].trim_matches(['\'', '"']).to_string();
149 let literal = match bytes.first().map(u8::to_ascii_lowercase) {
150 Some(b'd') if prefixed => Literal::Datetime(inner()),
151 Some(b'u') if prefixed => Literal::Uuid(inner()),
152 Some(b'r') if prefixed => Literal::Regex(inner()),
153 _ => {
154 let content = text
155 .trim_start_matches(['d', 'u', 'r'])
156 .trim_matches(['\'', '"']);
157 Literal::String(content.to_string())
158 }
159 };
160 Expr::Literal(literal)
161 }
162
163 fn record_id(&self, node: Node<'_>) -> Expr {
164 let table = named_children(node)
165 .into_iter()
166 .find(|c| c.kind() == "RecordTbIdent");
167 let id = named_children(node)
168 .into_iter()
169 .find(|c| !matches!(c.kind(), "RecordTbIdent" | "Colon"));
170 match (table, id) {
171 (Some(table), Some(id)) => Expr::RecordId {
172 table: self.spanned(table, self.node_text(table).to_string()),
173 id: node_range(id),
174 range: id.kind() == "RecordIdRange",
175 },
176 _ => Expr::Partial(partial(node)),
177 }
178 }
179
180 fn param_name(&self, node: Node<'_>) -> String {
181 self.node_text(node).trim_start_matches('$').to_string()
182 }
183
184 fn object(&self, node: Node<'_>) -> Expr {
185 let mut fields = Vec::new();
186 collect_object_properties(node, &mut |property| {
187 let Some(key_node) = first_descendant_of_kind(property, "ObjectKey") else {
188 return;
189 };
190 let key_leaf = single_named_child(key_node).unwrap_or(key_node);
191 let key = self.spanned(
192 key_leaf,
193 self.node_text(key_leaf)
194 .trim_matches(['`', '"', '\''])
195 .to_string(),
196 );
197 let value = named_children(property)
198 .into_iter()
199 .rfind(|child| child.kind() != "ObjectKey");
200 let value = match value {
201 Some(value_node) => self.expr(value_node),
202 None => self.spanned(property, Expr::Partial(partial(property))),
203 };
204 fields.push((key, value));
205 });
206 Expr::Object(fields)
207 }
208
209 fn binary(&self, node: Node<'_>) -> Expr {
210 let children = named_children(node);
211 let Some(op_index) = children.iter().position(|c| c.kind() == "Operator") else {
212 return Expr::Partial(partial(node));
213 };
214 let lhs = children[..op_index]
215 .iter()
216 .rev()
217 .find(|c| c.kind() != "Operator");
218 let rhs = children[op_index + 1..]
219 .iter()
220 .find(|c| c.kind() != "Operator");
221 let (Some(&lhs), Some(&rhs)) = (lhs, rhs) else {
222 return Expr::Partial(partial(node));
223 };
224 let op_node = children[op_index];
225
226 Expr::Binary {
227 lhs: Box::new(self.expr(lhs)),
228 op: self.spanned(op_node, binary_op(self.node_text(op_node))),
229 rhs: Box::new(self.expr(rhs)),
230 }
231 }
232
233 fn prefix(&self, node: Node<'_>) -> Expr {
234 let children = named_children(node);
235 let op_node = children.iter().find(|c| c.kind() == "Operator");
236 let operand = children.iter().find(|c| c.kind() != "Operator");
237 let (Some(&op_node), Some(&operand)) = (op_node, operand) else {
238 return Expr::Partial(partial(node));
239 };
240
241 Expr::Prefix {
242 op: self.spanned(op_node, prefix_op(self.node_text(op_node))),
243 expr: Box::new(self.expr(operand)),
244 }
245 }
246
247 fn call(&self, node: Node<'_>) -> Call {
248 let name = first_child_of_kind(node, "FunctionName");
249 let path = match name {
250 Some(name) => self.spanned(name, normalize_function_path(self.node_text(name))),
251 None => Spanned::new(String::new(), node_range(node)),
252 };
253 let args = first_child_of_kind(node, "ArgumentList")
254 .map(|list| {
255 named_children(list)
256 .into_iter()
257 .map(|arg| self.expr(arg))
258 .collect()
259 })
260 .unwrap_or_default();
261 Call { path, args }
262 }
263
264 fn cast(&self, node: Node<'_>) -> Expr {
265 let children = named_children(node);
266 let ty = children
267 .iter()
268 .find(|c| matches!(c.kind(), "TypeName" | "Type"));
269 let value = children
270 .iter()
271 .find(|c| !matches!(c.kind(), "TypeName" | "Type"));
272 let (Some(&ty), Some(&value)) = (ty, value) else {
273 return Expr::Partial(partial(node));
274 };
275
276 Expr::Cast {
277 ty: self.type_expr(ty),
278 expr: Box::new(self.expr(value)),
279 }
280 }
281
282 fn type_expr(&self, node: Node<'_>) -> Spanned<TypeExpr> {
285 let ty = match node.kind() {
286 "TypeName" => TypeExpr::Name(self.spanned(node, self.node_text(node).to_string())),
287 "Type" => match single_named_child(node) {
288 Some(child) => return self.type_expr(child),
289 None => TypeExpr::Partial(partial(node)),
290 },
291 "ParameterizedType" => {
292 let children = named_children(node);
293 let Some((name_node, args)) = children.split_first() else {
294 return self.spanned(node, TypeExpr::Partial(partial(node)));
295 };
296 let name = self.spanned(*name_node, self.node_text(*name_node).to_string());
297 let args: Vec<_> = args.iter().map(|arg| self.type_expr(*arg)).collect();
298 if name.node.eq_ignore_ascii_case("option") && args.len() == 1 {
300 TypeExpr::Optional(Box::new(
301 args.into_iter().next().expect("one option argument"),
302 ))
303 } else {
304 TypeExpr::Parameterized { name, args }
305 }
306 }
307 "UnionType" => {
308 let variants: Vec<_> = named_children(node)
309 .into_iter()
310 .filter(|child| child.kind() != "Pipe")
311 .map(|child| self.type_expr(child))
312 .collect();
313 TypeExpr::Union(variants)
314 }
315 "ObjectType" => self.object_type(node),
319 "LiteralType" => match single_named_child(node) {
320 Some(value) if value.kind() == "ObjectType" => return self.type_expr(value),
321 Some(value) => match self.expr(value).node {
322 Expr::Literal(literal) => TypeExpr::Literal(literal),
323 _ => TypeExpr::Partial(partial(node)),
324 },
325 None => TypeExpr::Partial(partial(node)),
326 },
327 _ => TypeExpr::Partial(partial(node)),
328 };
329 self.spanned(node, ty)
330 }
331
332 fn object_type(&self, node: Node<'_>) -> TypeExpr {
335 let mut properties = Vec::new();
336 collect_object_type_properties(node, &mut |property| {
337 let Some(key_node) = first_descendant_of_kind(property, "ObjectKey") else {
338 return;
339 };
340 let key_leaf = single_named_child(key_node).unwrap_or(key_node);
341 let key = self.spanned(
342 key_leaf,
343 self.node_text(key_leaf)
344 .trim_matches(['`', '"', '\''])
345 .to_string(),
346 );
347 let value = named_children(property)
348 .into_iter()
349 .find(|child| !matches!(child.kind(), "ObjectKey" | "Colon"));
350 let value = match value {
351 Some(ty_node) => self.type_expr(ty_node),
352 None => self.spanned(property, TypeExpr::Partial(partial(property))),
353 };
354 properties.push((key, value));
355 });
356 TypeExpr::Object(properties)
357 }
358
359 fn subquery(&self, node: Node<'_>) -> Expr {
375 let Some(inner) = subquery_content(node) else {
376 return Expr::Partial(partial(node));
377 };
378 if !is_statement_kind(inner.kind()) {
379 return self.expr(inner).node;
380 }
381 Expr::Subquery(Box::new(super::statement::lower_statement(
382 inner, self.text,
383 )))
384 }
385
386 fn block(&self, node: Node<'_>) -> Block {
387 let mut statements = Vec::new();
388 for child in named_children(node) {
389 if matches!(child.kind(), "BraceOpen" | "BraceClose") {
390 continue;
391 }
392 super::statement::recover_statement(child, self.text, &mut statements);
396 }
397 Block { statements }
398 }
399
400 fn closure(&self, node: Node<'_>) -> Expr {
401 let mut params = Vec::new();
402 let mut return_ty = None;
403 let mut body = None;
404 let mut saw_arrow = false;
405
406 for child in named_children(node) {
407 match child.kind() {
408 "Pipe" => {}
409 "LookupRight" => saw_arrow = true,
410 "ParamDefinition" => {
411 let mut name = None;
412 let mut ty = None;
413 for part in named_children(child) {
414 match part.kind() {
415 "VariableName" => {
416 name = Some(self.spanned(
417 part,
418 self.node_text(part).trim_start_matches('$').to_string(),
419 ));
420 }
421 "Type" | "TypeName" | "ParameterizedType" | "UnionType"
422 | "LiteralType" => ty = Some(self.type_expr(part)),
423 _ => {}
424 }
425 }
426 if let Some(name) = name {
427 params.push((name, ty));
428 }
429 }
430 "Type" | "TypeName" | "ParameterizedType" | "UnionType" | "LiteralType"
431 if saw_arrow =>
432 {
433 return_ty = Some(self.type_expr(child));
434 }
435 _ if body.is_none() => body = Some(self.expr(child)),
436 _ => {}
437 }
438 }
439
440 match body {
441 Some(body) => Expr::Closure(Closure {
442 params,
443 return_ty,
444 body: Box::new(body),
445 }),
446 None => Expr::Partial(partial(node)),
447 }
448 }
449
450 fn idiom(&self, node: Node<'_>) -> Idiom {
451 let mut parts: Vec<Spanned<IdiomPart>> = Vec::new();
452
453 for child in named_children(node) {
454 if child.is_error() || child.is_missing() {
455 parts.push(self.spanned(child, IdiomPart::Partial(partial(child))));
456 continue;
457 }
458 match child.kind() {
459 "Ident" => {
463 parts.push(
464 self.spanned(child, IdiomPart::Field(self.node_text(child).to_string())),
465 );
466 }
467 "Subscript" => self.subscript_parts(child, &mut parts),
468 "Lookup" => parts.push(self.spanned(child, self.graph_part(child))),
469 "Filter" => parts.push(self.spanned(child, self.filter_part(child))),
470 "Any" => parts.push(self.spanned(child, IdiomPart::All)),
475 _ if parts.is_empty() => {
477 parts.push(self.spanned(child, IdiomPart::Start(Box::new(self.expr(child)))));
478 }
479 _ => parts.push(self.spanned(child, IdiomPart::Partial(partial(child)))),
480 }
481 }
482
483 Idiom { parts }
484 }
485
486 fn subscript_parts(&self, node: Node<'_>, parts: &mut Vec<Spanned<IdiomPart>>) {
487 for child in named_children(node) {
488 let part = match child.kind() {
489 "Ident" => IdiomPart::Field(self.node_text(child).to_string()),
490 "Destructure" => IdiomPart::Destructure(self.destructure_fields(child)),
491 "Recurse" => {
492 let text = self.node_text(child);
494 let bounded = text.rsplit("..").next().is_some_and(|tail| {
495 tail.trim_end_matches(['}', ' '])
496 .chars()
497 .any(|c| c.is_ascii_digit())
498 });
499 IdiomPart::Recurse { bounded }
500 }
501 "IdiomFunction" => self.method_part(child),
502 "Any" => IdiomPart::All,
503 _ if child.is_error() || child.is_missing() => IdiomPart::Partial(partial(child)),
504 _ => IdiomPart::Partial(partial(child)),
505 };
506 parts.push(self.spanned(child, part));
507 }
508 }
509
510 fn destructure_fields(&self, node: Node<'_>) -> Vec<Spanned<Idiom>> {
511 named_children(node)
512 .into_iter()
513 .filter(|child| !matches!(child.kind(), "BraceOpen" | "BraceClose"))
514 .map(|child| match child.kind() {
515 "Ident" => self.spanned(
516 child,
517 Idiom {
518 parts: vec![self
519 .spanned(child, IdiomPart::Field(self.node_text(child).to_string()))],
520 },
521 ),
522 "Path" => self.spanned(child, self.idiom(child)),
523 _ => self.spanned(
524 child,
525 Idiom {
526 parts: vec![self.spanned(child, IdiomPart::Partial(partial(child)))],
527 },
528 ),
529 })
530 .collect()
531 }
532
533 fn method_part(&self, node: Node<'_>) -> IdiomPart {
534 let name = match first_child_of_kind(node, "FunctionName") {
535 Some(name) => self.spanned(name, self.node_text(name).to_string()),
536 None => return IdiomPart::Partial(partial(node)),
537 };
538 let args = first_child_of_kind(node, "ArgumentList")
539 .map(|list| {
540 named_children(list)
541 .into_iter()
542 .map(|arg| self.expr(arg))
543 .collect()
544 })
545 .unwrap_or_default();
546 IdiomPart::Method { name, args }
547 }
548
549 fn graph_part(&self, node: Node<'_>) -> IdiomPart {
550 let mut dir = None;
551 let mut step = GraphStep {
552 targets: Vec::new(),
553 where_clause: None,
554 reference: false,
555 };
556
557 for child in named_children(node) {
558 match child.kind() {
559 "LookupRight" => dir = Some(self.spanned(child, GraphDir::Out)),
560 "LookupLeft" => {
564 step.reference = self.node_text(child).contains('~');
565 dir = Some(self.spanned(child, GraphDir::In));
566 }
567 "LookupBoth" => dir = Some(self.spanned(child, GraphDir::Both)),
568 "Ident" => step
569 .targets
570 .push(self.spanned(child, self.node_text(child).to_string())),
571 "LookupSelection" => self.lookup_selection(child, &mut step),
572 _ => {}
573 }
574 }
575
576 match dir {
577 Some(dir) => IdiomPart::Graph { dir, step },
578 None => IdiomPart::Partial(partial(node)),
579 }
580 }
581
582 fn lookup_selection(&self, node: Node<'_>, step: &mut GraphStep) {
583 for child in named_children(node) {
584 match child.kind() {
585 "GraphPredicate" => {
586 match single_named_child(child) {
588 Some(ident) if ident.kind() == "Ident" => step
589 .targets
590 .push(self.spanned(ident, self.node_text(ident).to_string())),
591 _ => {}
592 }
593 }
594 "WhereClause" => {
595 if let Some(expr_node) = where_clause_expr(child) {
596 step.where_clause = Some(Box::new(self.expr(expr_node)));
597 }
598 }
599 _ => {}
600 }
601 }
602 }
603
604 fn filter_part(&self, node: Node<'_>) -> IdiomPart {
605 let children = named_children(node);
606 match children.as_slice() {
607 [child] if child.kind() == "WhereClause" => match where_clause_expr(*child) {
608 Some(expr_node) => IdiomPart::Where(Box::new(self.expr(expr_node))),
609 None => IdiomPart::Partial(partial(node)),
610 },
611 [child] if child.kind() == "Any" => IdiomPart::All,
612 [child] if child.kind() == "Last" => IdiomPart::Last,
613 [child] if !child.is_error() => IdiomPart::Index(Box::new(self.expr(*child))),
614 _ => IdiomPart::Partial(partial(node)),
615 }
616 }
617}
618
619fn where_clause_expr(clause: Node<'_>) -> Option<Node<'_>> {
621 named_children(clause)
622 .into_iter()
623 .rfind(|child| child.kind() != "Keyword")
624}
625
626fn binary_op(text: &str) -> BinaryOp {
627 match text.to_ascii_uppercase().as_str() {
628 "+" => BinaryOp::Add,
629 "-" => BinaryOp::Sub,
630 "*" => BinaryOp::Mul,
631 "/" => BinaryOp::Div,
632 "=" | "==" => BinaryOp::Eq,
633 "!=" => BinaryOp::NotEq,
634 "<" => BinaryOp::Lt,
635 "<=" => BinaryOp::LtEq,
636 ">" => BinaryOp::Gt,
637 ">=" => BinaryOp::GtEq,
638 "AND" | "&&" => BinaryOp::And,
639 "OR" | "||" => BinaryOp::Or,
640 "??" => BinaryOp::NullCoalesce,
641 _ => BinaryOp::Other(text.to_string()),
642 }
643}
644
645fn prefix_op(text: &str) -> PrefixOp {
646 match text {
647 "!" => PrefixOp::Not,
648 "-" => PrefixOp::Neg,
649 "+" => PrefixOp::Pos,
650 _ => PrefixOp::Other(text.to_string()),
651 }
652}
653
654fn normalize_function_path(path: &str) -> String {
657 path.trim().replace("::is::", "::is_")
658}
659
660fn named_children<'tree>(node: Node<'tree>) -> Vec<Node<'tree>> {
671 let mut cursor = node.walk();
672 let children = node
673 .children(&mut cursor)
674 .filter(|child| child.is_named() && !is_comment(*child))
675 .collect();
676 children
677}
678
679fn is_comment(node: Node<'_>) -> bool {
682 matches!(node.kind(), "Comment" | "BlockComment")
683}
684
685fn single_named_child<'tree>(node: Node<'tree>) -> Option<Node<'tree>> {
686 let children = named_children(node);
687 match children.as_slice() {
688 [only] => Some(*only),
689 _ => None,
690 }
691}
692
693fn subquery_content<'tree>(node: Node<'tree>) -> Option<Node<'tree>> {
697 let mut children = named_children(node).into_iter();
698 let first = children.next()?;
699 children.next().is_none().then_some(first)
700}
701
702pub(crate) fn paren_group_inner<'tree>(node: Node<'tree>) -> Option<Node<'tree>> {
707 let inner = subquery_content(node)?;
708 (!is_statement_kind(inner.kind())).then_some(inner)
709}
710
711fn is_statement_kind(kind: &str) -> bool {
715 kind.ends_with("Statement") || kind == "Block"
716}
717
718fn first_child_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
719 named_children(node)
720 .into_iter()
721 .find(|child| child.kind() == kind)
722}
723
724fn first_descendant_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
725 if node.kind() == kind {
726 return Some(node);
727 }
728 for child in named_children(node) {
729 if let Some(found) = first_descendant_of_kind(child, kind) {
730 return Some(found);
731 }
732 }
733 None
734}
735
736fn collect_object_properties(node: Node<'_>, visit: &mut impl FnMut(Node<'_>)) {
737 for child in named_children(node) {
738 match child.kind() {
739 "ObjectProperty" => visit(child),
740 "ObjectContent" => collect_object_properties(child, visit),
741 _ => {}
742 }
743 }
744}
745
746fn collect_object_type_properties(node: Node<'_>, visit: &mut impl FnMut(Node<'_>)) {
747 for child in named_children(node) {
748 match child.kind() {
749 "ObjectTypeProperty" => visit(child),
750 "ObjectTypeContent" => collect_object_type_properties(child, visit),
751 _ => {}
752 }
753 }
754}
755
756#[cfg(test)]
757mod tests {
758 use super::*;
759 use crate::parse::{parse_source, ParsedSource};
760 use crate::source::SourceId;
761
762 fn parse(query: &str) -> ParsedSource {
763 parse_source(SourceId::new("lower:test"), query).expect("test query parses")
764 }
765
766 fn lower_first(parsed: &ParsedSource, kind: &str) -> Spanned<Expr> {
768 let node = find_first(parsed.tree().root_node(), kind)
769 .unwrap_or_else(|| panic!("no {kind} node in {:?}", parsed.text()));
770 lower_expr(node, parsed.text())
771 }
772
773 fn find_first<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
774 if node.kind() == kind {
775 return Some(node);
776 }
777 let mut cursor = node.walk();
778 let found = node
779 .children(&mut cursor)
780 .find_map(|child| find_first(child, kind));
781 found
782 }
783
784 fn idiom_parts(expr: &Spanned<Expr>) -> &[Spanned<IdiomPart>] {
785 match &expr.node {
786 Expr::Idiom(idiom) => &idiom.parts,
787 other => panic!("expected idiom, got {other:?}"),
788 }
789 }
790
791 #[test]
792 fn lowers_every_literal_kind_with_prefix_normalization() {
793 let parsed = parse(
794 "RETURN [1, -2, 2.5, 1dec, 'hi', \"there\", d'2024-01-01T00:00:00Z', u'0189-aa', r'ab+', true, false, NONE, null, 1h];",
795 );
796
797 let array = lower_first(&parsed, "Array");
798 let Expr::Array(elements) = &array.node else {
799 panic!("expected array, got {:?}", array.node);
800 };
801 let literals: Vec<_> = elements
802 .iter()
803 .map(|e| match &e.node {
804 Expr::Literal(lit) => lit.clone(),
805 other => panic!("expected literal, got {other:?}"),
806 })
807 .collect();
808
809 assert_eq!(
810 literals,
811 vec![
812 Literal::Int(1),
813 Literal::Int(-2),
814 Literal::Float(2.5),
815 Literal::Decimal,
816 Literal::String("hi".into()),
817 Literal::String("there".into()),
818 Literal::Datetime("2024-01-01T00:00:00Z".into()),
819 Literal::Uuid("0189-aa".into()),
820 Literal::Regex("ab+".into()),
821 Literal::Bool(true),
822 Literal::Bool(false),
823 Literal::None,
824 Literal::Null,
825 Literal::Duration("1h".into()),
826 ]
827 );
828 }
829
830 #[test]
831 fn lowers_param_rooted_idiom_with_start_part() {
832 let parsed = parse("RETURN $user.name;");
833
834 let path = lower_first(&parsed, "Path");
835 let parts = idiom_parts(&path);
836
837 assert_eq!(parts.len(), 2);
838 let IdiomPart::Start(start) = &parts[0].node else {
839 panic!("expected Start, got {:?}", parts[0].node);
840 };
841 assert_eq!(start.node, Expr::Param("user".into()));
842 assert_eq!(parts[1].node, IdiomPart::Field("name".into()));
843 }
844
845 #[test]
846 fn lowers_graph_traversal_with_per_arrow_spans_and_destructure() {
847 let query = "SELECT ->likes->post.{title, id} FROM person;";
848 let parsed = parse(query);
849
850 let path = lower_first(&parsed, "Path");
851 let parts = idiom_parts(&path);
852 assert_eq!(parts.len(), 3);
853
854 let IdiomPart::Graph { dir, step } = &parts[0].node else {
855 panic!("expected graph part, got {:?}", parts[0].node);
856 };
857 assert_eq!(dir.node, GraphDir::Out);
858 assert_eq!(
860 &query[dir.span.start() as usize..dir.span.end() as usize],
861 "->"
862 );
863 assert_eq!(step.targets.len(), 1);
864 assert_eq!(step.targets[0].node, "likes");
865 assert!(step.where_clause.is_none());
866
867 let IdiomPart::Destructure(fields) = &parts[2].node else {
868 panic!("expected destructure, got {:?}", parts[2].node);
869 };
870 let names: Vec<_> = fields
871 .iter()
872 .map(|idiom| match &idiom.node.parts[0].node {
873 IdiomPart::Field(name) => name.clone(),
874 other => panic!("expected field, got {other:?}"),
875 })
876 .collect();
877 assert_eq!(names, vec!["title", "id"]);
878 }
879
880 #[test]
885 fn lowers_an_empty_destructure_without_a_parse_error() {
886 let query = "SELECT id.{} FROM user;";
887 let parsed = parse(query);
888 assert!(
889 !parsed.has_error(),
890 "an empty destructure must parse: {:?}",
891 parsed.syntax_diagnostics()
892 );
893
894 let path = lower_first(&parsed, "Path");
895 let parts = idiom_parts(&path);
896
897 let IdiomPart::Destructure(fields) = &parts[1].node else {
898 panic!("expected destructure, got {:?}", parts[1].node);
899 };
900 assert!(fields.is_empty(), "nothing was selected");
901 }
902
903 #[test]
904 fn lowers_filtered_graph_step_with_inline_where() {
905 let parsed = parse("SELECT ->(likes WHERE since > $x)->post FROM person;");
906
907 let path = lower_first(&parsed, "Path");
908 let parts = idiom_parts(&path);
909
910 let IdiomPart::Graph { step, .. } = &parts[0].node else {
911 panic!("expected graph part, got {:?}", parts[0].node);
912 };
913 assert_eq!(step.targets[0].node, "likes");
914 let where_clause = step.where_clause.as_ref().expect("has inline WHERE");
915 assert!(matches!(where_clause.node, Expr::Binary { .. }));
916 }
917
918 #[test]
919 fn lowers_index_and_where_filters_distinctly() {
920 let parsed = parse("SELECT tags[0], tags[$i], tags[WHERE active] FROM person;");
921
922 let root = parsed.tree().root_node();
923 let mut paths = Vec::new();
924 collect_kind(root, "Path", &mut paths);
925 let lowered: Vec<_> = paths
926 .iter()
927 .map(|p| lower_expr(*p, parsed.text()))
928 .collect();
929
930 let by_index = idiom_parts(&lowered[0]);
931 let IdiomPart::Index(index) = &by_index[1].node else {
932 panic!("expected index, got {:?}", by_index[1].node);
933 };
934 assert_eq!(index.node, Expr::Literal(Literal::Int(0)));
935
936 let by_param = idiom_parts(&lowered[1]);
937 let IdiomPart::Index(index) = &by_param[1].node else {
938 panic!("expected index, got {:?}", by_param[1].node);
939 };
940 assert_eq!(index.node, Expr::Param("i".into()));
941
942 let by_where = idiom_parts(&lowered[2]);
943 assert!(matches!(by_where[1].node, IdiomPart::Where(_)));
944 }
945
946 fn collect_kind<'tree>(node: Node<'tree>, kind: &str, out: &mut Vec<Node<'tree>>) {
947 if node.kind() == kind {
948 out.push(node);
949 return;
950 }
951 let mut cursor = node.walk();
952 for child in node.children(&mut cursor) {
953 collect_kind(child, kind, out);
954 }
955 }
956
957 #[test]
958 fn lowers_method_call_idiom_part() {
959 let parsed = parse("RETURN foo.len();");
960
961 let path = lower_first(&parsed, "Path");
962 let parts = idiom_parts(&path);
963
964 let IdiomPart::Method { name, args } = &parts[1].node else {
965 panic!("expected method, got {:?}", parts[1].node);
966 };
967 assert_eq!(name.node, "len");
968 assert!(args.is_empty());
969 }
970
971 #[test]
972 fn lowers_calls_with_normalized_paths_and_spanned_args() {
973 let query = "RETURN type::is::record($id);";
974 let parsed = parse(query);
975
976 let call = lower_first(&parsed, "FunctionCall");
977 let Expr::Call(call) = &call.node else {
978 panic!("expected call, got {:?}", call.node);
979 };
980
981 assert_eq!(call.path.node, "type::is_record");
982 assert_eq!(call.args.len(), 1);
983 assert_eq!(call.args[0].node, Expr::Param("id".into()));
984 assert_eq!(
985 &query[call.args[0].span.start() as usize..call.args[0].span.end() as usize],
986 "$id"
987 );
988 }
989
990 #[test]
991 fn lowers_binary_and_prefix_operators() {
992 let parsed = parse("SELECT * FROM person WHERE age > 18 AND !banned;");
993
994 let outer = lower_first(&parsed, "BinaryExpression");
995 let Expr::Binary { lhs, op, rhs } = &outer.node else {
996 panic!("expected binary, got {:?}", outer.node);
997 };
998 assert_eq!(op.node, BinaryOp::And);
999
1000 let Expr::Binary { op: inner_op, .. } = &lhs.node else {
1001 panic!("expected nested binary, got {:?}", lhs.node);
1002 };
1003 assert_eq!(inner_op.node, BinaryOp::Gt);
1004
1005 let Expr::Prefix { op: prefix, .. } = &rhs.node else {
1006 panic!("expected prefix, got {:?}", rhs.node);
1007 };
1008 assert_eq!(prefix.node, PrefixOp::Not);
1009 }
1010
1011 #[test]
1012 fn lowers_object_literals_with_trimmed_keys() {
1013 let parsed = parse("RETURN { name: 'a', \"age\": 1 };");
1014
1015 let object = lower_first(&parsed, "Object");
1016 let Expr::Object(fields) = &object.node else {
1017 panic!("expected object, got {:?}", object.node);
1018 };
1019
1020 assert_eq!(fields.len(), 2);
1021 assert_eq!(fields[0].0.node, "name");
1022 assert_eq!(fields[0].1.node, Expr::Literal(Literal::String("a".into())));
1023 assert_eq!(fields[1].0.node, "age");
1024 assert_eq!(fields[1].1.node, Expr::Literal(Literal::Int(1)));
1025 }
1026
1027 #[test]
1028 fn lowers_object_and_record_union_field_types() {
1029 let parsed = parse("DEFINE FIELD address ON person TYPE { street: string, zip: int };");
1032 let object_type =
1033 find_first(parsed.tree().root_node(), "ObjectType").expect("has an ObjectType node");
1034 let ty = lower_type_expr(object_type, parsed.text());
1035 let TypeExpr::Object(properties) = &ty.node else {
1036 panic!("expected object type, got {:?}", ty.node);
1037 };
1038 assert_eq!(properties.len(), 2);
1039 assert_eq!(properties[0].0.node, "street");
1040 assert!(matches!(&properties[0].1.node, TypeExpr::Name(n) if n.node == "string"));
1041 assert_eq!(properties[1].0.node, "zip");
1042 assert!(matches!(&properties[1].1.node, TypeExpr::Name(n) if n.node == "int"));
1043
1044 let parsed =
1047 parse("DEFINE FIELD owner ON thing TYPE record<team | user | organization>;");
1048 let param = find_first(parsed.tree().root_node(), "ParameterizedType")
1049 .expect("has a ParameterizedType node");
1050 let ty = lower_type_expr(param, parsed.text());
1051 let TypeExpr::Parameterized { name, args } = &ty.node else {
1052 panic!("expected parameterized type, got {:?}", ty.node);
1053 };
1054 assert_eq!(name.node, "record");
1055 assert_eq!(args.len(), 1);
1056 let TypeExpr::Union(variants) = &args[0].node else {
1057 panic!("expected union argument, got {:?}", args[0].node);
1058 };
1059 let names: Vec<_> = variants
1060 .iter()
1061 .map(|v| match &v.node {
1062 TypeExpr::Name(n) => n.node.clone(),
1063 other => panic!("expected table name, got {other:?}"),
1064 })
1065 .collect();
1066 assert_eq!(names, vec!["team", "user", "organization"]);
1067 }
1068
1069 #[test]
1070 fn lowers_type_cast() {
1071 let parsed = parse("RETURN <int> '42';");
1072
1073 let cast = lower_first(&parsed, "TypeCast");
1074 let Expr::Cast { ty, expr } = &cast.node else {
1075 panic!("expected cast, got {:?}", cast.node);
1076 };
1077 let TypeExpr::Name(name) = &ty.node else {
1078 panic!("expected type name, got {:?}", ty.node);
1079 };
1080 assert_eq!(name.node, "int");
1081 assert_eq!(expr.node, Expr::Literal(Literal::String("42".into())));
1082 }
1083
1084 #[test]
1085 fn error_nodes_lower_to_explicit_partials() {
1086 let parsed = parse("SELECT name, FROM person;");
1088
1089 let error = find_first(parsed.tree().root_node(), "ERROR").expect("input has ERROR node");
1090 let lowered = lower_expr(error, parsed.text());
1091
1092 assert!(
1093 matches!(lowered.node, Expr::Partial(_)),
1094 "ERROR must lower to Partial, got {:?}",
1095 lowered.node
1096 );
1097 }
1098
1099 #[test]
1100 fn an_idiom_node_carries_its_wildcard_as_an_element_step() {
1101 let parsed = parse("DEFINE FIELD items[*].price ON t TYPE string;");
1106 let node = find_first(parsed.tree().root_node(), "Idiom").expect("an Idiom node");
1107 let idiom = lower_idiom_node(node, parsed.text());
1108
1109 assert!(matches!(idiom.parts[0].node, IdiomPart::Field(ref n) if n == "items"));
1110 assert!(matches!(idiom.parts[1].node, IdiomPart::All));
1111 assert!(matches!(idiom.parts[2].node, IdiomPart::Field(ref n) if n == "price"));
1112 }
1113
1114 #[test]
1115 fn wildcard_and_last_filters_lower_to_their_idiom_parts() {
1116 let parsed = parse("SELECT tags[*], tags[$] FROM person;");
1117
1118 let path = lower_first(&parsed, "Path");
1119 let parts = idiom_parts(&path);
1120 assert!(matches!(parts[0].node, IdiomPart::Field(_)));
1121 assert!(matches!(parts[1].node, IdiomPart::All));
1122
1123 let root = parsed.tree().root_node();
1124 let mut paths = Vec::new();
1125 collect_kind(root, "Path", &mut paths);
1126 let last_path = lower_expr(paths[1], parsed.text());
1127 let parts = idiom_parts(&last_path);
1128 assert!(matches!(parts[1].node, IdiomPart::Last));
1129 }
1130
1131 #[test]
1132 fn closures_lower_to_explicit_non_silent_variants() {
1133 let parsed = parse("RETURN array::map([1], |$v| $v);");
1138
1139 let call = lower_first(&parsed, "FunctionCall");
1140 let Expr::Call(call) = &call.node else {
1141 panic!("expected call, got {:?}", call.node);
1142 };
1143 let closure_arg = call.args.get(1).expect("closure argument present");
1144 assert!(
1145 matches!(closure_arg.node, Expr::Closure(_) | Expr::Partial(_)),
1146 "closure argument must be explicitly unmodeled, got {:?}",
1147 closure_arg.node
1148 );
1149 }
1150
1151 #[test]
1152 fn grouping_parentheses_lower_to_the_inner_expression() {
1153 let parsed = parse("RETURN (1 + 2);");
1158 let lowered = lower_first(&parsed, "SubQuery");
1159 assert!(
1160 matches!(lowered.node, Expr::Binary { .. }),
1161 "a grouped expression must lower to itself, got {:?}",
1162 lowered.node
1163 );
1164 let span = lowered.span.start() as usize..lowered.span.end() as usize;
1167 assert_eq!(&parsed.text()[span], "(1 + 2)");
1168
1169 let parsed = parse("RETURN (((1 + 2)));");
1171 let lowered = lower_first(&parsed, "SubQuery");
1172 assert!(matches!(lowered.node, Expr::Binary { .. }));
1173
1174 let parsed = parse("RETURN (1 + 2) * 3;");
1176 let lowered = lower_first(&parsed, "BinaryExpression");
1177 let Expr::Binary { lhs, op, .. } = &lowered.node else {
1178 panic!("expected a binary, got {:?}", lowered.node);
1179 };
1180 assert!(matches!(op.node, BinaryOp::Mul));
1181 let Expr::Binary { op: inner_op, .. } = &lhs.node else {
1182 panic!("expected the grouped binary on the left, got {:?}", lhs.node);
1183 };
1184 assert!(matches!(inner_op.node, BinaryOp::Add));
1185 }
1186
1187 #[test]
1188 fn parenthesized_statements_stay_subqueries() {
1189 let parsed = parse("RETURN (SELECT * FROM person);");
1192 let lowered = lower_first(&parsed, "SubQuery");
1193 let Expr::Subquery(inner) = &lowered.node else {
1194 panic!("expected a subquery, got {:?}", lowered.node);
1195 };
1196 assert!(matches!(inner.node, crate::ast::Statement::Select(_)));
1197
1198 let parsed = parse("RETURN ({ RETURN 1; });");
1199 let lowered = lower_first(&parsed, "SubQuery");
1200 assert!(matches!(lowered.node, Expr::Subquery(_)));
1201 }
1202
1203 #[test]
1204 fn a_comment_inside_parentheses_does_not_recurse_forever() {
1205 let parsed = parse("RETURN (/* why */ 1);");
1209 let lowered = lower_first(&parsed, "SubQuery");
1210 assert!(
1211 matches!(lowered.node, Expr::Literal(Literal::Int(1))),
1212 "a commented group is still its inner expression, got {:?}",
1213 lowered.node
1214 );
1215 }
1216
1217 #[test]
1218 fn a_comment_between_operands_does_not_swallow_an_operand() {
1219 for query in [
1225 "SELECT * FROM t WHERE a = 1\n -- why\n AND b = 2;",
1227 "SELECT * FROM t WHERE a = 1 AND\n -- why\n b = 2;",
1229 "SELECT * FROM t WHERE a = 1 /* one */ AND -- two\n b = 2;",
1231 "SELECT * FROM t WHERE (a = 1 -- why\n) AND b = 2;",
1233 "SELECT * FROM t\nWHERE -- head\n a = 1 -- first\n AND -- mid\n b = 2 -- tail\n;",
1235 ] {
1236 let parsed = parse(query);
1237 let lowered = lower_first(&parsed, "BinaryExpression");
1238 let Expr::Binary { lhs, op, rhs } = &lowered.node else {
1239 panic!("expected a binary for {query:?}, got {:?}", lowered.node);
1240 };
1241 assert!(matches!(op.node, BinaryOp::And), "operator of {query:?}");
1242 assert!(
1243 matches!(lhs.node, Expr::Binary { .. }),
1244 "left operand of {query:?} was discarded: {:?}",
1245 lhs.node
1246 );
1247 assert!(
1248 matches!(rhs.node, Expr::Binary { .. }),
1249 "right operand of {query:?} was discarded: {:?}",
1250 rhs.node
1251 );
1252 }
1253
1254 let parsed = parse("SELECT * FROM t WHERE n\n -- why\n = \"x\";");
1256 let lowered = lower_first(&parsed, "BinaryExpression");
1257 let Expr::Binary { lhs, .. } = &lowered.node else {
1258 panic!("expected a binary, got {:?}", lowered.node);
1259 };
1260 assert!(
1261 matches!(&lhs.node, Expr::Idiom(idiom) if idiom.parts.len() == 1),
1262 "left operand should still be the field `n`, got {:?}",
1263 lhs.node
1264 );
1265 }
1266
1267 #[test]
1268 fn a_comment_after_a_prefix_operator_is_not_the_operand() {
1269 let parsed = parse("SELECT * FROM t WHERE ! -- why\n active;");
1270 let lowered = lower_first(&parsed, "PrefixExpression");
1271 let Expr::Prefix { op, expr } = &lowered.node else {
1272 panic!("expected a prefix, got {:?}", lowered.node);
1273 };
1274 assert!(matches!(op.node, PrefixOp::Not));
1275 assert!(
1276 matches!(expr.node, Expr::Idiom(_)),
1277 "operand should be `active`, got {:?}",
1278 expr.node
1279 );
1280 }
1281
1282 #[test]
1283 fn comments_do_not_displace_operands_elsewhere() {
1284 let parsed = parse("SELECT * FROM t WHERE <int> /* why */ a;");
1288 let lowered = lower_first(&parsed, "TypeCast");
1289 let Expr::Cast { expr, .. } = &lowered.node else {
1290 panic!("expected a cast, got {:?}", lowered.node);
1291 };
1292 assert!(
1293 matches!(expr.node, Expr::Idiom(_)),
1294 "cast value should be `a`, got {:?}",
1295 expr.node
1296 );
1297
1298 let parsed = parse("RETURN [1, /* why */ 2];");
1299 let lowered = lower_first(&parsed, "Array");
1300 let Expr::Array(items) = &lowered.node else {
1301 panic!("expected an array, got {:?}", lowered.node);
1302 };
1303 assert_eq!(items.len(), 2, "a comment is not an element: {items:?}");
1304
1305 let parsed = parse("RETURN { k /* why */ : 1 };");
1306 let lowered = lower_first(&parsed, "RecordId");
1307 let Expr::RecordId { table, id, .. } = &lowered.node else {
1308 panic!("expected a record id, got {:?}", lowered.node);
1309 };
1310 assert_eq!(table.node, "k");
1311 assert_eq!(&parsed.text()[id.start() as usize..id.end() as usize], "1");
1312 }
1313}