Skip to content

Commit e634c94

Browse files
authored
feat: enforce only aggregate expressions are used with group by (#19)
1 parent 1a91955 commit e634c94

8 files changed

+202
-2
lines changed

src/analysis.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,9 @@ impl<'a> Analysis<'a> {
653653
if let Some(expr) = &group_by.predicate {
654654
self.analyze_expr(&mut ctx, expr, Type::Bool)?;
655655
}
656+
657+
ctx.allow_agg_func = true;
658+
ctx.use_agg_funcs = true;
656659
}
657660

658661
let project = self.analyze_projection(&mut ctx, &query.projection)?;
@@ -742,7 +745,12 @@ impl<'a> Analysis<'a> {
742745

743746
ctx.allow_agg_func = true;
744747
let tpe = self.analyze_expr(ctx, expr, Type::Unspecified)?;
745-
self.check_projection_on_record(&mut CheckContext::default(), record.as_slice())?;
748+
let mut chk_ctx = CheckContext {
749+
use_agg_func: ctx.use_agg_funcs,
750+
..Default::default()
751+
};
752+
753+
self.check_projection_on_record(&mut chk_ctx, record.as_slice())?;
746754
Ok(tpe)
747755
}
748756

@@ -752,14 +760,24 @@ impl<'a> Analysis<'a> {
752760
let tpe = self.analyze_expr(ctx, expr, Type::Unspecified)?;
753761

754762
if ctx.use_agg_funcs {
755-
self.check_projection_on_field_expr(&mut CheckContext::default(), expr)?;
763+
let mut chk_ctx = CheckContext {
764+
use_agg_func: ctx.use_agg_funcs,
765+
..Default::default()
766+
};
767+
768+
self.check_projection_on_field_expr(&mut chk_ctx, expr)?;
756769
} else {
757770
self.reject_constant_func(&expr.attrs, app)?;
758771
}
759772

760773
Ok(tpe)
761774
}
762775

776+
Value::Id(_) if ctx.use_agg_funcs => Err(AnalysisError::ExpectAggExpr(
777+
expr.attrs.pos.line,
778+
expr.attrs.pos.col,
779+
)),
780+
763781
Value::Id(id) => {
764782
if let Some(tpe) = self.scope.entries.get(id.as_str()).cloned() {
765783
Ok(tpe)
@@ -772,6 +790,11 @@ impl<'a> Analysis<'a> {
772790
}
773791
}
774792

793+
Value::Access(_) if ctx.use_agg_funcs => Err(AnalysisError::ExpectAggExpr(
794+
expr.attrs.pos.line,
795+
expr.attrs.pos.col,
796+
)),
797+
775798
Value::Access(access) => {
776799
let mut current = &access.target.value;
777800

src/tests/analysis.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,24 @@ fn test_analyze_accept_group_by_with_order_by_with_agg() {
235235
.unwrap();
236236
insta::assert_yaml_snapshot!(query.run_static_analysis(&Default::default()));
237237
}
238+
239+
#[test]
240+
fn test_analyze_reject_group_by_no_agg() {
241+
let query = parse_query(include_str!("./resources/reject_group_by_no_agg.eql")).unwrap();
242+
insta::assert_yaml_snapshot!(query.run_static_analysis(&Default::default()));
243+
}
244+
245+
#[test]
246+
fn test_analyze_reject_group_by_no_agg_in_rec() {
247+
let query = parse_query(include_str!(
248+
"./resources/reject_group_by_no_agg_in_rec.eql"
249+
))
250+
.unwrap();
251+
insta::assert_yaml_snapshot!(query.run_static_analysis(&Default::default()));
252+
}
253+
254+
#[test]
255+
fn test_analyze_accept_group_by_with_agg_rec() {
256+
let query = parse_query(include_str!("./resources/accept_group_by_with_agg_rec.eql")).unwrap();
257+
insta::assert_yaml_snapshot!(query.run_static_analysis(&Default::default()));
258+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
FROM e IN events
3+
GROUP BY e.data.department
4+
PROJECT INTO {
5+
department: UNIQUE(e.data.department),
6+
cost: AVG(e.data.salary)
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
FROM e IN events
2+
GROUP BY e.data.department
3+
PROJECT INTO e.data.salary
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
FROM e IN events
2+
GROUP BY e.data.department
3+
PROJECT INTO {
4+
cost: e.data.salary
5+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
---
2+
source: src/tests/analysis.rs
3+
expression: "query.run_static_analysis(&Default::default())"
4+
---
5+
Ok:
6+
attrs:
7+
pos:
8+
line: 2
9+
col: 1
10+
sources:
11+
- binding:
12+
name: e
13+
pos:
14+
line: 2
15+
col: 6
16+
kind:
17+
Name: events
18+
predicate: ~
19+
group_by:
20+
expr:
21+
attrs:
22+
pos:
23+
line: 3
24+
col: 10
25+
value:
26+
Access:
27+
target:
28+
attrs:
29+
pos:
30+
line: 3
31+
col: 10
32+
value:
33+
Access:
34+
target:
35+
attrs:
36+
pos:
37+
line: 3
38+
col: 10
39+
value:
40+
Id: e
41+
field: data
42+
field: department
43+
predicate: ~
44+
order_by: ~
45+
limit: ~
46+
projection:
47+
attrs:
48+
pos:
49+
line: 4
50+
col: 14
51+
value:
52+
Record:
53+
- name: department
54+
value:
55+
attrs:
56+
pos:
57+
line: 5
58+
col: 14
59+
value:
60+
App:
61+
func: UNIQUE
62+
args:
63+
- attrs:
64+
pos:
65+
line: 5
66+
col: 21
67+
value:
68+
Access:
69+
target:
70+
attrs:
71+
pos:
72+
line: 5
73+
col: 21
74+
value:
75+
Access:
76+
target:
77+
attrs:
78+
pos:
79+
line: 5
80+
col: 21
81+
value:
82+
Id: e
83+
field: data
84+
field: department
85+
- name: cost
86+
value:
87+
attrs:
88+
pos:
89+
line: 6
90+
col: 8
91+
value:
92+
App:
93+
func: AVG
94+
args:
95+
- attrs:
96+
pos:
97+
line: 6
98+
col: 12
99+
value:
100+
Access:
101+
target:
102+
attrs:
103+
pos:
104+
line: 6
105+
col: 12
106+
value:
107+
Access:
108+
target:
109+
attrs:
110+
pos:
111+
line: 6
112+
col: 12
113+
value:
114+
Id: e
115+
field: data
116+
field: salary
117+
distinct: false
118+
meta:
119+
project:
120+
Record:
121+
cost: Number
122+
department: Unspecified
123+
aggregate: true
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
source: src/tests/analysis.rs
3+
expression: "query.run_static_analysis(&Default::default())"
4+
---
5+
Err:
6+
Analysis:
7+
ExpectAggExpr:
8+
- 3
9+
- 14
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
source: src/tests/analysis.rs
3+
expression: "query.run_static_analysis(&Default::default())"
4+
---
5+
Err:
6+
Analysis:
7+
UnallowedAggFuncUsageWithSrcField:
8+
- 4
9+
- 8

0 commit comments

Comments
 (0)