1use std::{
2 borrow::Cow,
3 collections::{BTreeMap, HashSet, btree_map::Entry},
4 mem,
5};
6
7use case_insensitive_hashmap::CaseInsensitiveHashMap;
8use serde::{Serialize, ser::SerializeMap};
9use unicase::Ascii;
10
11use crate::{
12 App, Attrs, Binary, Expr, Field, FunArgs, Query, Raw, Source, SourceKind, Type, Value,
13 error::AnalysisError, token::Operator,
14};
15
16#[derive(Debug, Clone, Serialize)]
26pub struct Typed {
27 pub project: Type,
32
33 #[serde(skip)]
38 pub scope: Scope,
39
40 pub aggregate: bool,
42}
43
44pub type AnalysisResult<A> = std::result::Result<A, AnalysisError>;
49
50pub struct AnalysisOptions {
56 pub default_scope: Scope,
58 pub event_type_info: Type,
60 pub custom_types: HashSet<Ascii<String>>,
75}
76
77impl AnalysisOptions {
78 pub fn add_custom_type<'a>(mut self, value: impl Into<Cow<'a, str>>) -> Self {
102 match value.into() {
103 Cow::Borrowed(t) => self.custom_types.insert(Ascii::new(t.to_owned())),
104 Cow::Owned(t) => self.custom_types.insert(Ascii::new(t)),
105 };
106
107 self
108 }
109}
110
111impl Default for AnalysisOptions {
112 fn default() -> Self {
113 Self {
114 default_scope: Scope {
115 entries: CaseInsensitiveHashMap::from_iter([
116 (
117 "ABS",
118 Type::App {
119 args: vec![Type::Number].into(),
120 result: Box::new(Type::Number),
121 aggregate: false,
122 },
123 ),
124 (
125 "CEIL",
126 Type::App {
127 args: vec![Type::Number].into(),
128 result: Box::new(Type::Number),
129 aggregate: false,
130 },
131 ),
132 (
133 "FLOOR",
134 Type::App {
135 args: vec![Type::Number].into(),
136 result: Box::new(Type::Number),
137 aggregate: false,
138 },
139 ),
140 (
141 "ROUND",
142 Type::App {
143 args: vec![Type::Number].into(),
144 result: Box::new(Type::Number),
145 aggregate: false,
146 },
147 ),
148 (
149 "COS",
150 Type::App {
151 args: vec![Type::Number].into(),
152 result: Box::new(Type::Number),
153 aggregate: false,
154 },
155 ),
156 (
157 "EXP",
158 Type::App {
159 args: vec![Type::Number].into(),
160 result: Box::new(Type::Number),
161 aggregate: false,
162 },
163 ),
164 (
165 "POW",
166 Type::App {
167 args: vec![Type::Number, Type::Number].into(),
168 result: Box::new(Type::Number),
169 aggregate: false,
170 },
171 ),
172 (
173 "SQRT",
174 Type::App {
175 args: vec![Type::Number].into(),
176 result: Box::new(Type::Number),
177 aggregate: false,
178 },
179 ),
180 (
181 "RAND",
182 Type::App {
183 args: vec![].into(),
184 result: Box::new(Type::Number),
185 aggregate: false,
186 },
187 ),
188 (
189 "PI",
190 Type::App {
191 args: vec![Type::Number].into(),
192 result: Box::new(Type::Number),
193 aggregate: false,
194 },
195 ),
196 (
197 "LOWER",
198 Type::App {
199 args: vec![Type::String].into(),
200 result: Box::new(Type::String),
201 aggregate: false,
202 },
203 ),
204 (
205 "UPPER",
206 Type::App {
207 args: vec![Type::String].into(),
208 result: Box::new(Type::String),
209 aggregate: false,
210 },
211 ),
212 (
213 "TRIM",
214 Type::App {
215 args: vec![Type::String].into(),
216 result: Box::new(Type::String),
217 aggregate: false,
218 },
219 ),
220 (
221 "LTRIM",
222 Type::App {
223 args: vec![Type::String].into(),
224 result: Box::new(Type::String),
225 aggregate: false,
226 },
227 ),
228 (
229 "RTRIM",
230 Type::App {
231 args: vec![Type::String].into(),
232 result: Box::new(Type::String),
233 aggregate: false,
234 },
235 ),
236 (
237 "LEN",
238 Type::App {
239 args: vec![Type::String].into(),
240 result: Box::new(Type::Number),
241 aggregate: false,
242 },
243 ),
244 (
245 "INSTR",
246 Type::App {
247 args: vec![Type::String].into(),
248 result: Box::new(Type::Number),
249 aggregate: false,
250 },
251 ),
252 (
253 "SUBSTRING",
254 Type::App {
255 args: vec![Type::String, Type::Number, Type::Number].into(),
256 result: Box::new(Type::String),
257 aggregate: false,
258 },
259 ),
260 (
261 "REPLACE",
262 Type::App {
263 args: vec![Type::String, Type::String, Type::String].into(),
264 result: Box::new(Type::String),
265 aggregate: false,
266 },
267 ),
268 (
269 "STARTSWITH",
270 Type::App {
271 args: vec![Type::String, Type::String].into(),
272 result: Box::new(Type::Bool),
273 aggregate: false,
274 },
275 ),
276 (
277 "ENDSWITH",
278 Type::App {
279 args: vec![Type::String, Type::String].into(),
280 result: Box::new(Type::Bool),
281 aggregate: false,
282 },
283 ),
284 (
285 "NOW",
286 Type::App {
287 args: vec![].into(),
288 result: Box::new(Type::DateTime),
289 aggregate: false,
290 },
291 ),
292 (
293 "YEAR",
294 Type::App {
295 args: vec![Type::Date].into(),
296 result: Box::new(Type::Number),
297 aggregate: false,
298 },
299 ),
300 (
301 "MONTH",
302 Type::App {
303 args: vec![Type::Date].into(),
304 result: Box::new(Type::Number),
305 aggregate: false,
306 },
307 ),
308 (
309 "DAY",
310 Type::App {
311 args: vec![Type::Date].into(),
312 result: Box::new(Type::Number),
313 aggregate: false,
314 },
315 ),
316 (
317 "HOUR",
318 Type::App {
319 args: vec![Type::Time].into(),
320 result: Box::new(Type::Number),
321 aggregate: false,
322 },
323 ),
324 (
325 "MINUTE",
326 Type::App {
327 args: vec![Type::Time].into(),
328 result: Box::new(Type::Number),
329 aggregate: false,
330 },
331 ),
332 (
333 "SECOND",
334 Type::App {
335 args: vec![Type::Time].into(),
336 result: Box::new(Type::Number),
337 aggregate: false,
338 },
339 ),
340 (
341 "WEEKDAY",
342 Type::App {
343 args: vec![Type::Date].into(),
344 result: Box::new(Type::Number),
345 aggregate: false,
346 },
347 ),
348 (
349 "IF",
350 Type::App {
351 args: vec![Type::Bool, Type::Unspecified, Type::Unspecified].into(),
352 result: Box::new(Type::Unspecified),
353 aggregate: false,
354 },
355 ),
356 (
357 "COUNT",
358 Type::App {
359 args: FunArgs {
360 values: vec![Type::Bool],
361 needed: 0,
362 },
363 result: Box::new(Type::Number),
364 aggregate: true,
365 },
366 ),
367 (
368 "SUM",
369 Type::App {
370 args: vec![Type::Number].into(),
371 result: Box::new(Type::Number),
372 aggregate: true,
373 },
374 ),
375 (
376 "AVG",
377 Type::App {
378 args: vec![Type::Number].into(),
379 result: Box::new(Type::Number),
380 aggregate: true,
381 },
382 ),
383 (
384 "MIN",
385 Type::App {
386 args: vec![Type::Number].into(),
387 result: Box::new(Type::Number),
388 aggregate: true,
389 },
390 ),
391 (
392 "MAX",
393 Type::App {
394 args: vec![Type::Number].into(),
395 result: Box::new(Type::Number),
396 aggregate: true,
397 },
398 ),
399 (
400 "MEDIAN",
401 Type::App {
402 args: vec![Type::Number].into(),
403 result: Box::new(Type::Number),
404 aggregate: true,
405 },
406 ),
407 (
408 "STDDEV",
409 Type::App {
410 args: vec![Type::Number].into(),
411 result: Box::new(Type::Number),
412 aggregate: true,
413 },
414 ),
415 (
416 "VARIANCE",
417 Type::App {
418 args: vec![Type::Number].into(),
419 result: Box::new(Type::Number),
420 aggregate: true,
421 },
422 ),
423 (
424 "UNIQUE",
425 Type::App {
426 args: vec![Type::Unspecified].into(),
427 result: Box::new(Type::Unspecified),
428 aggregate: true,
429 },
430 ),
431 ]),
432 },
433 event_type_info: Type::Record(BTreeMap::from([
434 ("specversion".to_owned(), Type::String),
435 ("id".to_owned(), Type::String),
436 ("time".to_owned(), Type::DateTime),
437 ("source".to_owned(), Type::String),
438 ("subject".to_owned(), Type::Subject),
439 ("type".to_owned(), Type::String),
440 ("datacontenttype".to_owned(), Type::String),
441 ("data".to_owned(), Type::Unspecified),
442 ("predecessorhash".to_owned(), Type::String),
443 ("hash".to_owned(), Type::String),
444 ("traceparent".to_owned(), Type::String),
445 ("tracestate".to_owned(), Type::String),
446 ("signature".to_owned(), Type::String),
447 ])),
448 custom_types: HashSet::default(),
449 }
450 }
451}
452
453pub fn static_analysis(
475 options: &AnalysisOptions,
476 query: Query<Raw>,
477) -> AnalysisResult<Query<Typed>> {
478 let mut analysis = Analysis::new(options);
479
480 analysis.analyze_query(query)
481}
482
483#[derive(Default, Clone, Debug)]
489pub struct Scope {
490 pub entries: CaseInsensitiveHashMap<Type>,
492}
493
494impl Serialize for Scope {
495 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
496 where
497 S: serde::Serializer,
498 {
499 let mut map = serializer.serialize_map(Some(self.entries.len()))?;
500
501 for (key, value) in self.entries.iter() {
502 map.serialize_entry(key.as_str(), value)?;
503 }
504
505 map.end()
506 }
507}
508
509impl Scope {
510 pub fn is_empty(&self) -> bool {
512 self.entries.is_empty()
513 }
514}
515
516#[derive(Default)]
517struct CheckContext {
518 use_agg_func: bool,
519 use_source_based: bool,
520}
521
522#[derive(Default)]
527pub struct AnalysisContext {
528 pub allow_agg_func: bool,
534
535 pub use_agg_funcs: bool,
537}
538
539pub struct Analysis<'a> {
544 options: &'a AnalysisOptions,
546 prev_scopes: Vec<Scope>,
548 scope: Scope,
550}
551
552impl<'a> Analysis<'a> {
553 pub fn new(options: &'a AnalysisOptions) -> Self {
555 Self {
556 options,
557 prev_scopes: Default::default(),
558 scope: Scope::default(),
559 }
560 }
561
562 pub fn scope(&self) -> &Scope {
570 &self.scope
571 }
572
573 pub fn scope_mut(&mut self) -> &mut Scope {
581 &mut self.scope
582 }
583
584 fn enter_scope(&mut self) {
585 if self.scope.is_empty() {
586 return;
587 }
588
589 let prev = mem::take(&mut self.scope);
590 self.prev_scopes.push(prev);
591 }
592
593 fn exit_scope(&mut self) -> Scope {
594 if let Some(prev) = self.prev_scopes.pop() {
595 mem::replace(&mut self.scope, prev)
596 } else {
597 mem::take(&mut self.scope)
598 }
599 }
600
601 pub fn analyze_query(&mut self, query: Query<Raw>) -> AnalysisResult<Query<Typed>> {
630 self.enter_scope();
631
632 let mut sources = Vec::with_capacity(query.sources.len());
633 let mut ctx = AnalysisContext::default();
634
635 for source in query.sources {
636 sources.push(self.analyze_source(source)?);
637 }
638
639 if let Some(expr) = &query.predicate {
640 self.analyze_expr(&mut ctx, expr, Type::Bool)?;
641 }
642
643 if let Some(group_by) = &query.group_by {
644 if !matches!(&group_by.expr.value, Value::Access(_)) {
645 return Err(AnalysisError::ExpectFieldLiteral(
646 group_by.expr.attrs.pos.line,
647 group_by.expr.attrs.pos.col,
648 ));
649 }
650
651 self.analyze_expr(&mut ctx, &group_by.expr, Type::Unspecified)?;
652
653 if let Some(expr) = &group_by.predicate {
654 ctx.allow_agg_func = true;
655 ctx.use_agg_funcs = true;
656
657 self.analyze_expr(&mut ctx, expr, Type::Bool)?;
658 if !self.expect_agg_expr(expr)? {
659 return Err(AnalysisError::ExpectAggExpr(
660 expr.attrs.pos.line,
661 expr.attrs.pos.col,
662 ));
663 }
664 }
665
666 ctx.allow_agg_func = true;
667 ctx.use_agg_funcs = true;
668 }
669
670 let project = self.analyze_projection(&mut ctx, &query.projection)?;
671
672 if let Some(order_by) = &query.order_by {
673 self.analyze_expr(&mut ctx, &order_by.expr, Type::Unspecified)?;
674
675 if query.group_by.is_none() && !matches!(&order_by.expr.value, Value::Access(_)) {
676 return Err(AnalysisError::ExpectFieldLiteral(
677 order_by.expr.attrs.pos.line,
678 order_by.expr.attrs.pos.col,
679 ));
680 } else if query.group_by.is_some() {
681 self.expect_agg_func(&order_by.expr)?;
682 }
683 }
684
685 let scope = self.exit_scope();
686
687 Ok(Query {
688 attrs: query.attrs,
689 sources,
690 predicate: query.predicate,
691 group_by: query.group_by,
692 order_by: query.order_by,
693 limit: query.limit,
694 projection: query.projection,
695 distinct: query.distinct,
696 meta: Typed {
697 project,
698 scope,
699 aggregate: ctx.use_agg_funcs,
700 },
701 })
702 }
703
704 fn analyze_source(&mut self, source: Source<Raw>) -> AnalysisResult<Source<Typed>> {
705 let kind = self.analyze_source_kind(source.kind)?;
706 let tpe = match &kind {
707 SourceKind::Name(_) | SourceKind::Subject(_) => self.options.event_type_info.clone(),
708 SourceKind::Subquery(query) => self.projection_type(query),
709 };
710
711 if self
712 .scope
713 .entries
714 .insert(source.binding.name.clone(), tpe)
715 .is_some()
716 {
717 return Err(AnalysisError::BindingAlreadyExists(
718 source.binding.pos.line,
719 source.binding.pos.col,
720 source.binding.name,
721 ));
722 }
723
724 Ok(Source {
725 binding: source.binding,
726 kind,
727 })
728 }
729
730 fn analyze_source_kind(&mut self, kind: SourceKind<Raw>) -> AnalysisResult<SourceKind<Typed>> {
731 match kind {
732 SourceKind::Name(n) => Ok(SourceKind::Name(n)),
733 SourceKind::Subject(s) => Ok(SourceKind::Subject(s)),
734 SourceKind::Subquery(query) => {
735 let query = self.analyze_query(*query)?;
736 Ok(SourceKind::Subquery(Box::new(query)))
737 }
738 }
739 }
740
741 fn analyze_projection(
742 &mut self,
743 ctx: &mut AnalysisContext,
744 expr: &Expr,
745 ) -> AnalysisResult<Type> {
746 match &expr.value {
747 Value::Record(record) => {
748 if record.is_empty() {
749 return Err(AnalysisError::EmptyRecord(
750 expr.attrs.pos.line,
751 expr.attrs.pos.col,
752 ));
753 }
754
755 ctx.allow_agg_func = true;
756 let tpe = self.analyze_expr(ctx, expr, Type::Unspecified)?;
757 let mut chk_ctx = CheckContext {
758 use_agg_func: ctx.use_agg_funcs,
759 ..Default::default()
760 };
761
762 self.check_projection_on_record(&mut chk_ctx, record.as_slice())?;
763 Ok(tpe)
764 }
765
766 Value::App(app) => {
767 ctx.allow_agg_func = true;
768
769 let tpe = self.analyze_expr(ctx, expr, Type::Unspecified)?;
770
771 if ctx.use_agg_funcs {
772 let mut chk_ctx = CheckContext {
773 use_agg_func: ctx.use_agg_funcs,
774 ..Default::default()
775 };
776
777 self.check_projection_on_field_expr(&mut chk_ctx, expr)?;
778 } else {
779 self.reject_constant_func(&expr.attrs, app)?;
780 }
781
782 Ok(tpe)
783 }
784
785 Value::Id(_) if ctx.use_agg_funcs => Err(AnalysisError::ExpectAggExpr(
786 expr.attrs.pos.line,
787 expr.attrs.pos.col,
788 )),
789
790 Value::Id(id) => {
791 if let Some(tpe) = self.scope.entries.get(id.as_str()).cloned() {
792 Ok(tpe)
793 } else {
794 Err(AnalysisError::VariableUndeclared(
795 expr.attrs.pos.line,
796 expr.attrs.pos.col,
797 id.clone(),
798 ))
799 }
800 }
801
802 Value::Access(_) if ctx.use_agg_funcs => Err(AnalysisError::ExpectAggExpr(
803 expr.attrs.pos.line,
804 expr.attrs.pos.col,
805 )),
806
807 Value::Access(access) => {
808 let mut current = &access.target.value;
809
810 loop {
811 match current {
812 Value::Id(name) => {
813 if !self.scope.entries.contains_key(name.as_str()) {
814 return Err(AnalysisError::VariableUndeclared(
815 expr.attrs.pos.line,
816 expr.attrs.pos.col,
817 name.clone(),
818 ));
819 }
820
821 break;
822 }
823
824 Value::Access(next) => current = &next.target.value,
825 _ => unreachable!(),
826 }
827 }
828
829 self.analyze_expr(ctx, expr, Type::Unspecified)
830 }
831
832 _ => Err(AnalysisError::ExpectRecordOrSourcedProperty(
833 expr.attrs.pos.line,
834 expr.attrs.pos.col,
835 self.project_type(&expr.value),
836 )),
837 }
838 }
839
840 fn check_projection_on_record(
841 &mut self,
842 ctx: &mut CheckContext,
843 record: &[Field],
844 ) -> AnalysisResult<()> {
845 for field in record {
846 self.check_projection_on_field(ctx, field)?;
847 }
848
849 Ok(())
850 }
851
852 fn check_projection_on_field(
853 &mut self,
854 ctx: &mut CheckContext,
855 field: &Field,
856 ) -> AnalysisResult<()> {
857 self.check_projection_on_field_expr(ctx, &field.value)
858 }
859
860 fn check_projection_on_field_expr(
861 &mut self,
862 ctx: &mut CheckContext,
863 expr: &Expr,
864 ) -> AnalysisResult<()> {
865 match &expr.value {
866 Value::Number(_) | Value::String(_) | Value::Bool(_) => Ok(()),
867
868 Value::Id(id) => {
869 if self.scope.entries.contains_key(id.as_str()) {
870 if ctx.use_agg_func {
871 return Err(AnalysisError::UnallowedAggFuncUsageWithSrcField(
872 expr.attrs.pos.line,
873 expr.attrs.pos.col,
874 ));
875 }
876
877 ctx.use_source_based = true;
878 }
879
880 Ok(())
881 }
882
883 Value::Array(exprs) => {
884 for expr in exprs {
885 self.check_projection_on_field_expr(ctx, expr)?;
886 }
887
888 Ok(())
889 }
890
891 Value::Record(fields) => {
892 for field in fields {
893 self.check_projection_on_field(ctx, field)?;
894 }
895
896 Ok(())
897 }
898
899 Value::Access(access) => self.check_projection_on_field_expr(ctx, &access.target),
900
901 Value::App(app) => {
902 if let Some(Type::App { aggregate, .. }) =
903 self.options.default_scope.entries.get(app.func.as_str())
904 {
905 ctx.use_agg_func |= *aggregate;
906
907 if ctx.use_agg_func && ctx.use_source_based {
908 return Err(AnalysisError::UnallowedAggFuncUsageWithSrcField(
909 expr.attrs.pos.line,
910 expr.attrs.pos.col,
911 ));
912 }
913
914 if *aggregate {
915 return self.expect_agg_func(expr);
916 }
917
918 for arg in &app.args {
919 self.invalidate_agg_func_usage(arg)?;
920 }
921 }
922
923 Ok(())
924 }
925
926 Value::Binary(binary) => {
927 self.check_projection_on_field_expr(ctx, &binary.lhs)?;
928 self.check_projection_on_field_expr(ctx, &binary.rhs)
929 }
930
931 Value::Unary(unary) => self.check_projection_on_field_expr(ctx, &unary.expr),
932 Value::Group(expr) => self.check_projection_on_field_expr(ctx, expr),
933 }
934 }
935
936 fn expect_agg_func(&self, expr: &Expr) -> AnalysisResult<()> {
937 if let Value::App(app) = &expr.value
938 && let Some(Type::App {
939 aggregate: true, ..
940 }) = self.options.default_scope.entries.get(app.func.as_str())
941 {
942 for arg in &app.args {
943 self.ensure_agg_param_is_source_bound(arg)?;
944 self.invalidate_agg_func_usage(arg)?;
945 }
946
947 return Ok(());
948 }
949
950 Err(AnalysisError::ExpectAggExpr(
951 expr.attrs.pos.line,
952 expr.attrs.pos.col,
953 ))
954 }
955
956 fn expect_agg_expr(&self, expr: &Expr) -> AnalysisResult<bool> {
957 match &expr.value {
958 Value::Id(id) => {
959 if self.scope.entries.contains_key(id.as_str()) {
960 return Err(AnalysisError::UnallowedAggFuncUsageWithSrcField(
961 expr.attrs.pos.line,
962 expr.attrs.pos.col,
963 ));
964 }
965
966 Ok(false)
967 }
968 Value::Group(expr) => self.expect_agg_expr(expr),
969 Value::Binary(binary) => {
970 let lhs = self.expect_agg_expr(&binary.lhs)?;
971 let rhs = self.expect_agg_expr(&binary.rhs)?;
972
973 if !lhs && !rhs {
974 return Err(AnalysisError::ExpectAggExpr(
975 expr.attrs.pos.line,
976 expr.attrs.pos.col,
977 ));
978 }
979
980 Ok(true)
981 }
982 Value::Unary(unary) => self.expect_agg_expr(unary.expr.as_ref()),
983 Value::App(_) => {
984 self.expect_agg_func(expr)?;
985 Ok(true)
986 }
987
988 _ => Ok(false),
989 }
990 }
991
992 fn ensure_agg_param_is_source_bound(&self, expr: &Expr) -> AnalysisResult<()> {
993 match &expr.value {
994 Value::Id(id) if !self.options.default_scope.entries.contains_key(id.as_str()) => {
995 Ok(())
996 }
997 Value::Access(access) => self.ensure_agg_param_is_source_bound(&access.target),
998 Value::Binary(binary) => self.ensure_agg_binary_op_is_source_bound(&expr.attrs, binary),
999 Value::Unary(unary) => self.ensure_agg_param_is_source_bound(&unary.expr),
1000
1001 _ => Err(AnalysisError::ExpectSourceBoundProperty(
1002 expr.attrs.pos.line,
1003 expr.attrs.pos.col,
1004 )),
1005 }
1006 }
1007
1008 fn ensure_agg_binary_op_is_source_bound(
1009 &self,
1010 attrs: &Attrs,
1011 binary: &Binary,
1012 ) -> AnalysisResult<()> {
1013 if !self.ensure_agg_binary_op_branch_is_source_bound(&binary.lhs)
1014 && !self.ensure_agg_binary_op_branch_is_source_bound(&binary.rhs)
1015 {
1016 return Err(AnalysisError::ExpectSourceBoundProperty(
1017 attrs.pos.line,
1018 attrs.pos.col,
1019 ));
1020 }
1021
1022 Ok(())
1023 }
1024
1025 fn ensure_agg_binary_op_branch_is_source_bound(&self, expr: &Expr) -> bool {
1026 match &expr.value {
1027 Value::Id(id) => !self.options.default_scope.entries.contains_key(id.as_str()),
1028 Value::Array(exprs) => {
1029 if exprs.is_empty() {
1030 return false;
1031 }
1032
1033 exprs
1034 .iter()
1035 .all(|expr| self.ensure_agg_binary_op_branch_is_source_bound(expr))
1036 }
1037 Value::Record(fields) => {
1038 if fields.is_empty() {
1039 return false;
1040 }
1041
1042 fields
1043 .iter()
1044 .all(|field| self.ensure_agg_binary_op_branch_is_source_bound(&field.value))
1045 }
1046 Value::Access(access) => {
1047 self.ensure_agg_binary_op_branch_is_source_bound(&access.target)
1048 }
1049
1050 Value::Binary(binary) => self
1051 .ensure_agg_binary_op_is_source_bound(&expr.attrs, binary)
1052 .is_ok(),
1053 Value::Unary(unary) => self.ensure_agg_binary_op_branch_is_source_bound(&unary.expr),
1054 Value::Group(expr) => self.ensure_agg_binary_op_branch_is_source_bound(expr),
1055
1056 Value::Number(_) | Value::String(_) | Value::Bool(_) | Value::App(_) => false,
1057 }
1058 }
1059
1060 fn invalidate_agg_func_usage(&self, expr: &Expr) -> AnalysisResult<()> {
1061 match &expr.value {
1062 Value::Number(_)
1063 | Value::String(_)
1064 | Value::Bool(_)
1065 | Value::Id(_)
1066 | Value::Access(_) => Ok(()),
1067
1068 Value::Array(exprs) => {
1069 for expr in exprs {
1070 self.invalidate_agg_func_usage(expr)?;
1071 }
1072
1073 Ok(())
1074 }
1075
1076 Value::Record(fields) => {
1077 for field in fields {
1078 self.invalidate_agg_func_usage(&field.value)?;
1079 }
1080
1081 Ok(())
1082 }
1083
1084 Value::App(app) => {
1085 if let Some(Type::App { aggregate, .. }) =
1086 self.options.default_scope.entries.get(app.func.as_str())
1087 && *aggregate
1088 {
1089 return Err(AnalysisError::WrongAggFunUsage(
1090 expr.attrs.pos.line,
1091 expr.attrs.pos.col,
1092 app.func.clone(),
1093 ));
1094 }
1095
1096 for arg in &app.args {
1097 self.invalidate_agg_func_usage(arg)?;
1098 }
1099
1100 Ok(())
1101 }
1102
1103 Value::Binary(binary) => {
1104 self.invalidate_agg_func_usage(&binary.lhs)?;
1105 self.invalidate_agg_func_usage(&binary.rhs)
1106 }
1107
1108 Value::Unary(unary) => self.invalidate_agg_func_usage(&unary.expr),
1109 Value::Group(expr) => self.invalidate_agg_func_usage(expr),
1110 }
1111 }
1112
1113 fn reject_constant_func(&self, attrs: &Attrs, app: &App) -> AnalysisResult<()> {
1114 if app.args.is_empty() {
1115 return Err(AnalysisError::ConstantExprInProjectIntoClause(
1116 attrs.pos.line,
1117 attrs.pos.col,
1118 ));
1119 }
1120
1121 let mut errored = None;
1122 for arg in &app.args {
1123 if let Err(e) = self.reject_constant_expr(arg) {
1124 if errored.is_none() {
1125 errored = Some(e);
1126 }
1127
1128 continue;
1129 }
1130
1131 return Ok(());
1133 }
1134
1135 Err(errored.expect("to be defined at that point"))
1136 }
1137
1138 fn reject_constant_expr(&self, expr: &Expr) -> AnalysisResult<()> {
1139 match &expr.value {
1140 Value::Id(id) if self.scope.entries.contains_key(id.as_str()) => Ok(()),
1141
1142 Value::Array(exprs) => {
1143 let mut errored = None;
1144 for expr in exprs {
1145 if let Err(e) = self.reject_constant_expr(expr) {
1146 if errored.is_none() {
1147 errored = Some(e);
1148 }
1149
1150 continue;
1151 }
1152
1153 return Ok(());
1155 }
1156
1157 Err(errored.expect("to be defined at that point"))
1158 }
1159
1160 Value::Record(fields) => {
1161 let mut errored = None;
1162 for field in fields {
1163 if let Err(e) = self.reject_constant_expr(&field.value) {
1164 if errored.is_none() {
1165 errored = Some(e);
1166 }
1167
1168 continue;
1169 }
1170
1171 return Ok(());
1173 }
1174
1175 Err(errored.expect("to be defined at that point"))
1176 }
1177
1178 Value::Binary(binary) => self
1179 .reject_constant_expr(&binary.lhs)
1180 .or_else(|e| self.reject_constant_expr(&binary.rhs).map_err(|_| e)),
1181
1182 Value::Access(access) => self.reject_constant_expr(access.target.as_ref()),
1183 Value::App(app) => self.reject_constant_func(&expr.attrs, app),
1184 Value::Unary(unary) => self.reject_constant_expr(&unary.expr),
1185 Value::Group(expr) => self.reject_constant_expr(expr),
1186
1187 _ => Err(AnalysisError::ConstantExprInProjectIntoClause(
1188 expr.attrs.pos.line,
1189 expr.attrs.pos.col,
1190 )),
1191 }
1192 }
1193
1194 pub fn analyze_expr(
1224 &mut self,
1225 ctx: &mut AnalysisContext,
1226 expr: &Expr,
1227 mut expect: Type,
1228 ) -> AnalysisResult<Type> {
1229 match &expr.value {
1230 Value::Number(_) => expect.check(&expr.attrs, Type::Number),
1231 Value::String(_) => expect.check(&expr.attrs, Type::String),
1232 Value::Bool(_) => expect.check(&expr.attrs, Type::Bool),
1233
1234 Value::Id(id) => {
1235 if let Some(tpe) = self.options.default_scope.entries.get(id.as_str()) {
1236 expect.check(&expr.attrs, tpe.clone())
1237 } else if let Some(tpe) = self.scope.entries.get_mut(id.as_str()) {
1238 let tmp = mem::take(tpe);
1239 *tpe = tmp.check(&expr.attrs, expect)?;
1240
1241 Ok(tpe.clone())
1242 } else {
1243 Err(AnalysisError::VariableUndeclared(
1244 expr.attrs.pos.line,
1245 expr.attrs.pos.col,
1246 id.to_owned(),
1247 ))
1248 }
1249 }
1250
1251 Value::Array(exprs) => {
1252 if matches!(expect, Type::Unspecified) {
1253 for expr in exprs {
1254 expect = self.analyze_expr(ctx, expr, expect)?;
1255 }
1256
1257 return Ok(Type::Array(Box::new(expect)));
1258 }
1259
1260 match expect {
1261 Type::Array(mut expect) => {
1262 for expr in exprs {
1263 *expect = self.analyze_expr(ctx, expr, expect.as_ref().clone())?;
1264 }
1265
1266 Ok(Type::Array(expect))
1267 }
1268
1269 expect => Err(AnalysisError::TypeMismatch(
1270 expr.attrs.pos.line,
1271 expr.attrs.pos.col,
1272 expect,
1273 self.project_type(&expr.value),
1274 )),
1275 }
1276 }
1277
1278 Value::Record(fields) => {
1279 if matches!(expect, Type::Unspecified) {
1280 let mut record = BTreeMap::new();
1281
1282 for field in fields {
1283 record.insert(
1284 field.name.clone(),
1285 self.analyze_expr(ctx, &field.value, Type::Unspecified)?,
1286 );
1287 }
1288
1289 return Ok(Type::Record(record));
1290 }
1291
1292 match expect {
1293 Type::Record(mut types) if fields.len() == types.len() => {
1294 for field in fields {
1295 if let Some(tpe) = types.remove(field.name.as_str()) {
1296 types.insert(
1297 field.name.clone(),
1298 self.analyze_expr(ctx, &field.value, tpe)?,
1299 );
1300 } else {
1301 return Err(AnalysisError::FieldUndeclared(
1302 expr.attrs.pos.line,
1303 expr.attrs.pos.col,
1304 field.name.clone(),
1305 ));
1306 }
1307 }
1308
1309 Ok(Type::Record(types))
1310 }
1311
1312 expect => Err(AnalysisError::TypeMismatch(
1313 expr.attrs.pos.line,
1314 expr.attrs.pos.col,
1315 expect,
1316 self.project_type(&expr.value),
1317 )),
1318 }
1319 }
1320
1321 this @ Value::Access(_) => Ok(self.analyze_access(&expr.attrs, this, expect)?),
1322
1323 Value::App(app) => {
1324 if let Some(tpe) = self.options.default_scope.entries.get(app.func.as_str())
1325 && let Type::App {
1326 args,
1327 result,
1328 aggregate,
1329 } = tpe
1330 {
1331 if !args.match_arg_count(app.args.len()) {
1332 return Err(AnalysisError::FunWrongArgumentCount(
1333 expr.attrs.pos.line,
1334 expr.attrs.pos.col,
1335 app.func.clone(),
1336 ));
1337 }
1338
1339 if *aggregate && !ctx.allow_agg_func {
1340 return Err(AnalysisError::WrongAggFunUsage(
1341 expr.attrs.pos.line,
1342 expr.attrs.pos.col,
1343 app.func.clone(),
1344 ));
1345 }
1346
1347 if *aggregate && ctx.allow_agg_func {
1348 ctx.use_agg_funcs = true;
1349 }
1350
1351 for (arg, tpe) in app.args.iter().zip(args.values.iter().cloned()) {
1352 self.analyze_expr(ctx, arg, tpe)?;
1353 }
1354
1355 if matches!(expect, Type::Unspecified) {
1356 Ok(result.as_ref().clone())
1357 } else {
1358 expect.check(&expr.attrs, result.as_ref().clone())
1359 }
1360 } else {
1361 Err(AnalysisError::FuncUndeclared(
1362 expr.attrs.pos.line,
1363 expr.attrs.pos.col,
1364 app.func.clone(),
1365 ))
1366 }
1367 }
1368
1369 Value::Binary(binary) => match binary.operator {
1370 Operator::Add | Operator::Sub | Operator::Mul | Operator::Div => {
1371 self.analyze_expr(ctx, &binary.lhs, Type::Number)?;
1372 self.analyze_expr(ctx, &binary.rhs, Type::Number)?;
1373 expect.check(&expr.attrs, Type::Number)
1374 }
1375
1376 Operator::Eq
1377 | Operator::Neq
1378 | Operator::Lt
1379 | Operator::Lte
1380 | Operator::Gt
1381 | Operator::Gte => {
1382 let lhs_expect = self.analyze_expr(ctx, &binary.lhs, Type::Unspecified)?;
1383 let rhs_expect = self.analyze_expr(ctx, &binary.rhs, lhs_expect.clone())?;
1384
1385 if matches!(lhs_expect, Type::Unspecified)
1388 && !matches!(rhs_expect, Type::Unspecified)
1389 {
1390 self.analyze_expr(ctx, &binary.lhs, rhs_expect)?;
1391 }
1392
1393 expect.check(&expr.attrs, Type::Bool)
1394 }
1395
1396 Operator::Contains => {
1397 let lhs_expect = self.analyze_expr(
1398 ctx,
1399 &binary.lhs,
1400 Type::Array(Box::new(Type::Unspecified)),
1401 )?;
1402
1403 let lhs_assumption = match lhs_expect {
1404 Type::Array(inner) => *inner,
1405 other => {
1406 return Err(AnalysisError::ExpectArray(
1407 expr.attrs.pos.line,
1408 expr.attrs.pos.col,
1409 other,
1410 ));
1411 }
1412 };
1413
1414 let rhs_expect = self.analyze_expr(ctx, &binary.rhs, lhs_assumption.clone())?;
1415
1416 if matches!(lhs_assumption, Type::Unspecified)
1419 && !matches!(rhs_expect, Type::Unspecified)
1420 {
1421 self.analyze_expr(ctx, &binary.lhs, Type::Array(Box::new(rhs_expect)))?;
1422 }
1423
1424 expect.check(&expr.attrs, Type::Bool)
1425 }
1426
1427 Operator::And | Operator::Or | Operator::Xor => {
1428 self.analyze_expr(ctx, &binary.lhs, Type::Bool)?;
1429 self.analyze_expr(ctx, &binary.rhs, Type::Bool)?;
1430
1431 expect.check(&expr.attrs, Type::Bool)
1432 }
1433
1434 Operator::As => {
1435 if let Value::Id(name) = &binary.rhs.value {
1436 if let Some(tpe) = name_to_type(self.options, name) {
1437 return Ok(tpe);
1439 } else {
1440 return Err(AnalysisError::UnsupportedCustomType(
1441 expr.attrs.pos.line,
1442 expr.attrs.pos.col,
1443 name.clone(),
1444 ));
1445 }
1446 }
1447
1448 unreachable!(
1449 "we already made sure during parsing that we can only have an ID symbol at this point"
1450 )
1451 }
1452
1453 Operator::Not => unreachable!(),
1454 },
1455
1456 Value::Unary(unary) => match unary.operator {
1457 Operator::Add | Operator::Sub => {
1458 self.analyze_expr(ctx, &unary.expr, Type::Number)?;
1459 expect.check(&expr.attrs, Type::Number)
1460 }
1461
1462 Operator::Not => {
1463 self.analyze_expr(ctx, &unary.expr, Type::Bool)?;
1464 expect.check(&expr.attrs, Type::Bool)
1465 }
1466
1467 _ => unreachable!(),
1468 },
1469
1470 Value::Group(expr) => Ok(self.analyze_expr(ctx, expr.as_ref(), expect)?),
1471 }
1472 }
1473
1474 fn analyze_access(
1475 &mut self,
1476 attrs: &Attrs,
1477 access: &Value,
1478 expect: Type,
1479 ) -> AnalysisResult<Type> {
1480 struct State<A, B> {
1481 depth: u8,
1482 dynamic: bool,
1484 definition: Def<A, B>,
1485 }
1486
1487 impl<A, B> State<A, B> {
1488 fn new(definition: Def<A, B>) -> Self {
1489 Self {
1490 depth: 0,
1491 dynamic: false,
1492 definition,
1493 }
1494 }
1495 }
1496
1497 enum Def<A, B> {
1498 User(A),
1499 System(B),
1500 }
1501
1502 fn go<'a>(
1503 scope: &'a mut Scope,
1504 sys: &'a AnalysisOptions,
1505 attrs: &'a Attrs,
1506 value: &'a Value,
1507 ) -> AnalysisResult<State<&'a mut Type, &'a Type>> {
1508 match value {
1509 Value::Id(id) => {
1510 if let Some(tpe) = sys.default_scope.entries.get(id.as_str()) {
1511 Ok(State::new(Def::System(tpe)))
1512 } else if let Some(tpe) = scope.entries.get_mut(id.as_str()) {
1513 Ok(State::new(Def::User(tpe)))
1514 } else {
1515 Err(AnalysisError::VariableUndeclared(
1516 attrs.pos.line,
1517 attrs.pos.col,
1518 id.clone(),
1519 ))
1520 }
1521 }
1522 Value::Access(access) => {
1523 let mut state = go(scope, sys, &access.target.attrs, &access.target.value)?;
1524
1525 let is_data_field = state.depth == 0 && access.field == "data";
1527
1528 if !state.dynamic && is_data_field {
1532 state.dynamic = true;
1533 }
1534
1535 match state.definition {
1536 Def::User(tpe) => {
1537 if matches!(tpe, Type::Unspecified) && state.dynamic {
1538 *tpe = Type::Record(BTreeMap::from([(
1539 access.field.clone(),
1540 Type::Unspecified,
1541 )]));
1542 return Ok(State {
1543 depth: state.depth + 1,
1544 definition: Def::User(
1545 tpe.as_record_or_panic_mut()
1546 .get_mut(access.field.as_str())
1547 .unwrap(),
1548 ),
1549 ..state
1550 });
1551 }
1552
1553 if let Type::Record(fields) = tpe {
1554 match fields.entry(access.field.clone()) {
1555 Entry::Vacant(entry) => {
1556 if state.dynamic || is_data_field {
1557 return Ok(State {
1558 depth: state.depth + 1,
1559 definition: Def::User(
1560 entry.insert(Type::Unspecified),
1561 ),
1562 ..state
1563 });
1564 }
1565
1566 return Err(AnalysisError::FieldUndeclared(
1567 attrs.pos.line,
1568 attrs.pos.col,
1569 access.field.clone(),
1570 ));
1571 }
1572
1573 Entry::Occupied(entry) => {
1574 return Ok(State {
1575 depth: state.depth + 1,
1576 definition: Def::User(entry.into_mut()),
1577 ..state
1578 });
1579 }
1580 }
1581 }
1582
1583 Err(AnalysisError::ExpectRecord(
1584 attrs.pos.line,
1585 attrs.pos.col,
1586 tpe.clone(),
1587 ))
1588 }
1589
1590 Def::System(tpe) => {
1591 if matches!(tpe, Type::Unspecified) && state.dynamic {
1592 return Ok(State {
1593 depth: state.depth + 1,
1594 definition: Def::System(&Type::Unspecified),
1595 ..state
1596 });
1597 }
1598
1599 if let Type::Record(fields) = tpe {
1600 if let Some(field) = fields.get(access.field.as_str()) {
1601 return Ok(State {
1602 depth: state.depth + 1,
1603 definition: Def::System(field),
1604 ..state
1605 });
1606 }
1607
1608 return Err(AnalysisError::FieldUndeclared(
1609 attrs.pos.line,
1610 attrs.pos.col,
1611 access.field.clone(),
1612 ));
1613 }
1614
1615 Err(AnalysisError::ExpectRecord(
1616 attrs.pos.line,
1617 attrs.pos.col,
1618 tpe.clone(),
1619 ))
1620 }
1621 }
1622 }
1623 Value::Number(_)
1624 | Value::String(_)
1625 | Value::Bool(_)
1626 | Value::Array(_)
1627 | Value::Record(_)
1628 | Value::App(_)
1629 | Value::Binary(_)
1630 | Value::Unary(_)
1631 | Value::Group(_) => unreachable!(),
1632 }
1633 }
1634
1635 let state = go(&mut self.scope, self.options, attrs, access)?;
1636
1637 match state.definition {
1638 Def::User(tpe) => {
1639 let tmp = mem::take(tpe);
1640 *tpe = tmp.check(attrs, expect)?;
1641
1642 Ok(tpe.clone())
1643 }
1644
1645 Def::System(tpe) => tpe.clone().check(attrs, expect),
1646 }
1647 }
1648
1649 fn projection_type(&self, query: &Query<Typed>) -> Type {
1650 self.project_type(&query.projection.value)
1651 }
1652
1653 fn project_type(&self, value: &Value) -> Type {
1654 match value {
1655 Value::Number(_) => Type::Number,
1656 Value::String(_) => Type::String,
1657 Value::Bool(_) => Type::Bool,
1658 Value::Id(id) => {
1659 if let Some(tpe) = self.options.default_scope.entries.get(id.as_str()) {
1660 tpe.clone()
1661 } else if let Some(tpe) = self.scope.entries.get(id.as_str()) {
1662 tpe.clone()
1663 } else {
1664 Type::Unspecified
1665 }
1666 }
1667 Value::Array(exprs) => {
1668 let mut project = Type::Unspecified;
1669
1670 for expr in exprs {
1671 let tmp = self.project_type(&expr.value);
1672
1673 if !matches!(tmp, Type::Unspecified) {
1674 project = tmp;
1675 break;
1676 }
1677 }
1678
1679 Type::Array(Box::new(project))
1680 }
1681 Value::Record(fields) => Type::Record(
1682 fields
1683 .iter()
1684 .map(|field| (field.name.clone(), self.project_type(&field.value.value)))
1685 .collect(),
1686 ),
1687 Value::Access(access) => {
1688 let tpe = self.project_type(&access.target.value);
1689 if let Type::Record(fields) = tpe {
1690 fields
1691 .get(access.field.as_str())
1692 .cloned()
1693 .unwrap_or_default()
1694 } else {
1695 Type::Unspecified
1696 }
1697 }
1698 Value::App(app) => self
1699 .options
1700 .default_scope
1701 .entries
1702 .get(app.func.as_str())
1703 .cloned()
1704 .unwrap_or_default(),
1705 Value::Binary(binary) => match binary.operator {
1706 Operator::Add | Operator::Sub | Operator::Mul | Operator::Div => Type::Number,
1707 Operator::As => {
1708 if let Value::Id(n) = &binary.rhs.as_ref().value
1709 && let Some(tpe) = name_to_type(self.options, n.as_str())
1710 {
1711 tpe
1712 } else {
1713 Type::Unspecified
1714 }
1715 }
1716 Operator::Eq
1717 | Operator::Neq
1718 | Operator::Lt
1719 | Operator::Lte
1720 | Operator::Gt
1721 | Operator::Gte
1722 | Operator::And
1723 | Operator::Or
1724 | Operator::Xor
1725 | Operator::Not
1726 | Operator::Contains => Type::Bool,
1727 },
1728 Value::Unary(unary) => match unary.operator {
1729 Operator::Add | Operator::Sub => Type::Number,
1730 Operator::Mul
1731 | Operator::Div
1732 | Operator::Eq
1733 | Operator::Neq
1734 | Operator::Lt
1735 | Operator::Lte
1736 | Operator::Gt
1737 | Operator::Gte
1738 | Operator::And
1739 | Operator::Or
1740 | Operator::Xor
1741 | Operator::Not
1742 | Operator::Contains
1743 | Operator::As => unreachable!(),
1744 },
1745 Value::Group(expr) => self.project_type(&expr.value),
1746 }
1747 }
1748}
1749
1750pub fn name_to_type(opts: &AnalysisOptions, name: &str) -> Option<Type> {
1782 if name.eq_ignore_ascii_case("string") {
1783 Some(Type::String)
1784 } else if name.eq_ignore_ascii_case("int") || name.eq_ignore_ascii_case("float64") {
1785 Some(Type::Number)
1786 } else if name.eq_ignore_ascii_case("boolean") {
1787 Some(Type::Bool)
1788 } else if name.eq_ignore_ascii_case("date") {
1789 Some(Type::Date)
1790 } else if name.eq_ignore_ascii_case("time") {
1791 Some(Type::Time)
1792 } else if name.eq_ignore_ascii_case("datetime") {
1793 Some(Type::DateTime)
1794 } else if opts.custom_types.contains(&Ascii::new(name.to_owned())) {
1795 Some(Type::Custom(name.to_owned()))
1797 } else {
1798 None
1799 }
1800}