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 self.analyze_expr(&mut ctx, expr, Type::Bool)?;
655 }
656
657 ctx.allow_agg_func = true;
658 ctx.use_agg_funcs = true;
659 }
660
661 let project = self.analyze_projection(&mut ctx, &query.projection)?;
662
663 if let Some(order_by) = &query.order_by {
664 self.analyze_expr(&mut ctx, &order_by.expr, Type::Unspecified)?;
665
666 if query.group_by.is_none() && !matches!(&order_by.expr.value, Value::Access(_)) {
667 return Err(AnalysisError::ExpectFieldLiteral(
668 order_by.expr.attrs.pos.line,
669 order_by.expr.attrs.pos.col,
670 ));
671 } else if query.group_by.is_some() {
672 self.expect_agg_expr(&order_by.expr)?;
673 }
674 }
675
676 let scope = self.exit_scope();
677
678 Ok(Query {
679 attrs: query.attrs,
680 sources,
681 predicate: query.predicate,
682 group_by: query.group_by,
683 order_by: query.order_by,
684 limit: query.limit,
685 projection: query.projection,
686 distinct: query.distinct,
687 meta: Typed {
688 project,
689 scope,
690 aggregate: ctx.use_agg_funcs,
691 },
692 })
693 }
694
695 fn analyze_source(&mut self, source: Source<Raw>) -> AnalysisResult<Source<Typed>> {
696 let kind = self.analyze_source_kind(source.kind)?;
697 let tpe = match &kind {
698 SourceKind::Name(_) | SourceKind::Subject(_) => self.options.event_type_info.clone(),
699 SourceKind::Subquery(query) => self.projection_type(query),
700 };
701
702 if self
703 .scope
704 .entries
705 .insert(source.binding.name.clone(), tpe)
706 .is_some()
707 {
708 return Err(AnalysisError::BindingAlreadyExists(
709 source.binding.pos.line,
710 source.binding.pos.col,
711 source.binding.name,
712 ));
713 }
714
715 Ok(Source {
716 binding: source.binding,
717 kind,
718 })
719 }
720
721 fn analyze_source_kind(&mut self, kind: SourceKind<Raw>) -> AnalysisResult<SourceKind<Typed>> {
722 match kind {
723 SourceKind::Name(n) => Ok(SourceKind::Name(n)),
724 SourceKind::Subject(s) => Ok(SourceKind::Subject(s)),
725 SourceKind::Subquery(query) => {
726 let query = self.analyze_query(*query)?;
727 Ok(SourceKind::Subquery(Box::new(query)))
728 }
729 }
730 }
731
732 fn analyze_projection(
733 &mut self,
734 ctx: &mut AnalysisContext,
735 expr: &Expr,
736 ) -> AnalysisResult<Type> {
737 match &expr.value {
738 Value::Record(record) => {
739 if record.is_empty() {
740 return Err(AnalysisError::EmptyRecord(
741 expr.attrs.pos.line,
742 expr.attrs.pos.col,
743 ));
744 }
745
746 ctx.allow_agg_func = true;
747 let tpe = self.analyze_expr(ctx, expr, Type::Unspecified)?;
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())?;
754 Ok(tpe)
755 }
756
757 Value::App(app) => {
758 ctx.allow_agg_func = true;
759
760 let tpe = self.analyze_expr(ctx, expr, Type::Unspecified)?;
761
762 if ctx.use_agg_funcs {
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)?;
769 } else {
770 self.reject_constant_func(&expr.attrs, app)?;
771 }
772
773 Ok(tpe)
774 }
775
776 Value::Id(_) if ctx.use_agg_funcs => Err(AnalysisError::ExpectAggExpr(
777 expr.attrs.pos.line,
778 expr.attrs.pos.col,
779 )),
780
781 Value::Id(id) => {
782 if let Some(tpe) = self.scope.entries.get(id.as_str()).cloned() {
783 Ok(tpe)
784 } else {
785 Err(AnalysisError::VariableUndeclared(
786 expr.attrs.pos.line,
787 expr.attrs.pos.col,
788 id.clone(),
789 ))
790 }
791 }
792
793 Value::Access(_) if ctx.use_agg_funcs => Err(AnalysisError::ExpectAggExpr(
794 expr.attrs.pos.line,
795 expr.attrs.pos.col,
796 )),
797
798 Value::Access(access) => {
799 let mut current = &access.target.value;
800
801 loop {
802 match current {
803 Value::Id(name) => {
804 if !self.scope.entries.contains_key(name.as_str()) {
805 return Err(AnalysisError::VariableUndeclared(
806 expr.attrs.pos.line,
807 expr.attrs.pos.col,
808 name.clone(),
809 ));
810 }
811
812 break;
813 }
814
815 Value::Access(next) => current = &next.target.value,
816 _ => unreachable!(),
817 }
818 }
819
820 self.analyze_expr(ctx, expr, Type::Unspecified)
821 }
822
823 _ => Err(AnalysisError::ExpectRecordOrSourcedProperty(
824 expr.attrs.pos.line,
825 expr.attrs.pos.col,
826 self.project_type(&expr.value),
827 )),
828 }
829 }
830
831 fn check_projection_on_record(
832 &mut self,
833 ctx: &mut CheckContext,
834 record: &[Field],
835 ) -> AnalysisResult<()> {
836 for field in record {
837 self.check_projection_on_field(ctx, field)?;
838 }
839
840 Ok(())
841 }
842
843 fn check_projection_on_field(
844 &mut self,
845 ctx: &mut CheckContext,
846 field: &Field,
847 ) -> AnalysisResult<()> {
848 self.check_projection_on_field_expr(ctx, &field.value)
849 }
850
851 fn check_projection_on_field_expr(
852 &mut self,
853 ctx: &mut CheckContext,
854 expr: &Expr,
855 ) -> AnalysisResult<()> {
856 match &expr.value {
857 Value::Number(_) | Value::String(_) | Value::Bool(_) => Ok(()),
858
859 Value::Id(id) => {
860 if self.scope.entries.contains_key(id.as_str()) {
861 if ctx.use_agg_func {
862 return Err(AnalysisError::UnallowedAggFuncUsageWithSrcField(
863 expr.attrs.pos.line,
864 expr.attrs.pos.col,
865 ));
866 }
867
868 ctx.use_source_based = true;
869 }
870
871 Ok(())
872 }
873
874 Value::Array(exprs) => {
875 for expr in exprs {
876 self.check_projection_on_field_expr(ctx, expr)?;
877 }
878
879 Ok(())
880 }
881
882 Value::Record(fields) => {
883 for field in fields {
884 self.check_projection_on_field(ctx, field)?;
885 }
886
887 Ok(())
888 }
889
890 Value::Access(access) => self.check_projection_on_field_expr(ctx, &access.target),
891
892 Value::App(app) => {
893 if let Some(Type::App { aggregate, .. }) =
894 self.options.default_scope.entries.get(app.func.as_str())
895 {
896 ctx.use_agg_func |= *aggregate;
897
898 if ctx.use_agg_func && ctx.use_source_based {
899 return Err(AnalysisError::UnallowedAggFuncUsageWithSrcField(
900 expr.attrs.pos.line,
901 expr.attrs.pos.col,
902 ));
903 }
904
905 if *aggregate {
906 return self.expect_agg_expr(expr);
907 }
908
909 for arg in &app.args {
910 self.invalidate_agg_func_usage(arg)?;
911 }
912 }
913
914 Ok(())
915 }
916
917 Value::Binary(binary) => {
918 self.check_projection_on_field_expr(ctx, &binary.lhs)?;
919 self.check_projection_on_field_expr(ctx, &binary.rhs)
920 }
921
922 Value::Unary(unary) => self.check_projection_on_field_expr(ctx, &unary.expr),
923 Value::Group(expr) => self.check_projection_on_field_expr(ctx, expr),
924 }
925 }
926
927 fn expect_agg_expr(&self, expr: &Expr) -> AnalysisResult<()> {
928 if let Value::App(app) = &expr.value
929 && let Some(Type::App {
930 aggregate: true, ..
931 }) = self.options.default_scope.entries.get(app.func.as_str())
932 {
933 for arg in &app.args {
934 self.ensure_agg_param_is_source_bound(arg)?;
935 self.invalidate_agg_func_usage(arg)?;
936 }
937
938 return Ok(());
939 }
940
941 Err(AnalysisError::ExpectAggExpr(
942 expr.attrs.pos.line,
943 expr.attrs.pos.col,
944 ))
945 }
946
947 fn ensure_agg_param_is_source_bound(&self, expr: &Expr) -> AnalysisResult<()> {
948 match &expr.value {
949 Value::Id(id) if !self.options.default_scope.entries.contains_key(id.as_str()) => {
950 Ok(())
951 }
952 Value::Access(access) => self.ensure_agg_param_is_source_bound(&access.target),
953 Value::Binary(binary) => self.ensure_agg_binary_op_is_source_bound(&expr.attrs, binary),
954 Value::Unary(unary) => self.ensure_agg_param_is_source_bound(&unary.expr),
955
956 _ => Err(AnalysisError::ExpectSourceBoundProperty(
957 expr.attrs.pos.line,
958 expr.attrs.pos.col,
959 )),
960 }
961 }
962
963 fn ensure_agg_binary_op_is_source_bound(
964 &self,
965 attrs: &Attrs,
966 binary: &Binary,
967 ) -> AnalysisResult<()> {
968 if !self.ensure_agg_binary_op_branch_is_source_bound(&binary.lhs)
969 && !self.ensure_agg_binary_op_branch_is_source_bound(&binary.rhs)
970 {
971 return Err(AnalysisError::ExpectSourceBoundProperty(
972 attrs.pos.line,
973 attrs.pos.col,
974 ));
975 }
976
977 Ok(())
978 }
979
980 fn ensure_agg_binary_op_branch_is_source_bound(&self, expr: &Expr) -> bool {
981 match &expr.value {
982 Value::Id(id) => !self.options.default_scope.entries.contains_key(id.as_str()),
983 Value::Array(exprs) => {
984 if exprs.is_empty() {
985 return false;
986 }
987
988 exprs
989 .iter()
990 .all(|expr| self.ensure_agg_binary_op_branch_is_source_bound(expr))
991 }
992 Value::Record(fields) => {
993 if fields.is_empty() {
994 return false;
995 }
996
997 fields
998 .iter()
999 .all(|field| self.ensure_agg_binary_op_branch_is_source_bound(&field.value))
1000 }
1001 Value::Access(access) => {
1002 self.ensure_agg_binary_op_branch_is_source_bound(&access.target)
1003 }
1004
1005 Value::Binary(binary) => self
1006 .ensure_agg_binary_op_is_source_bound(&expr.attrs, binary)
1007 .is_ok(),
1008 Value::Unary(unary) => self.ensure_agg_binary_op_branch_is_source_bound(&unary.expr),
1009 Value::Group(expr) => self.ensure_agg_binary_op_branch_is_source_bound(expr),
1010
1011 Value::Number(_) | Value::String(_) | Value::Bool(_) | Value::App(_) => false,
1012 }
1013 }
1014
1015 fn invalidate_agg_func_usage(&self, expr: &Expr) -> AnalysisResult<()> {
1016 match &expr.value {
1017 Value::Number(_)
1018 | Value::String(_)
1019 | Value::Bool(_)
1020 | Value::Id(_)
1021 | Value::Access(_) => Ok(()),
1022
1023 Value::Array(exprs) => {
1024 for expr in exprs {
1025 self.invalidate_agg_func_usage(expr)?;
1026 }
1027
1028 Ok(())
1029 }
1030
1031 Value::Record(fields) => {
1032 for field in fields {
1033 self.invalidate_agg_func_usage(&field.value)?;
1034 }
1035
1036 Ok(())
1037 }
1038
1039 Value::App(app) => {
1040 if let Some(Type::App { aggregate, .. }) =
1041 self.options.default_scope.entries.get(app.func.as_str())
1042 && *aggregate
1043 {
1044 return Err(AnalysisError::WrongAggFunUsage(
1045 expr.attrs.pos.line,
1046 expr.attrs.pos.col,
1047 app.func.clone(),
1048 ));
1049 }
1050
1051 for arg in &app.args {
1052 self.invalidate_agg_func_usage(arg)?;
1053 }
1054
1055 Ok(())
1056 }
1057
1058 Value::Binary(binary) => {
1059 self.invalidate_agg_func_usage(&binary.lhs)?;
1060 self.invalidate_agg_func_usage(&binary.rhs)
1061 }
1062
1063 Value::Unary(unary) => self.invalidate_agg_func_usage(&unary.expr),
1064 Value::Group(expr) => self.invalidate_agg_func_usage(expr),
1065 }
1066 }
1067
1068 fn reject_constant_func(&self, attrs: &Attrs, app: &App) -> AnalysisResult<()> {
1069 if app.args.is_empty() {
1070 return Err(AnalysisError::ConstantExprInProjectIntoClause(
1071 attrs.pos.line,
1072 attrs.pos.col,
1073 ));
1074 }
1075
1076 let mut errored = None;
1077 for arg in &app.args {
1078 if let Err(e) = self.reject_constant_expr(arg) {
1079 if errored.is_none() {
1080 errored = Some(e);
1081 }
1082
1083 continue;
1084 }
1085
1086 return Ok(());
1088 }
1089
1090 Err(errored.expect("to be defined at that point"))
1091 }
1092
1093 fn reject_constant_expr(&self, expr: &Expr) -> AnalysisResult<()> {
1094 match &expr.value {
1095 Value::Id(id) if self.scope.entries.contains_key(id.as_str()) => Ok(()),
1096
1097 Value::Array(exprs) => {
1098 let mut errored = None;
1099 for expr in exprs {
1100 if let Err(e) = self.reject_constant_expr(expr) {
1101 if errored.is_none() {
1102 errored = Some(e);
1103 }
1104
1105 continue;
1106 }
1107
1108 return Ok(());
1110 }
1111
1112 Err(errored.expect("to be defined at that point"))
1113 }
1114
1115 Value::Record(fields) => {
1116 let mut errored = None;
1117 for field in fields {
1118 if let Err(e) = self.reject_constant_expr(&field.value) {
1119 if errored.is_none() {
1120 errored = Some(e);
1121 }
1122
1123 continue;
1124 }
1125
1126 return Ok(());
1128 }
1129
1130 Err(errored.expect("to be defined at that point"))
1131 }
1132
1133 Value::Binary(binary) => self
1134 .reject_constant_expr(&binary.lhs)
1135 .or_else(|e| self.reject_constant_expr(&binary.rhs).map_err(|_| e)),
1136
1137 Value::Access(access) => self.reject_constant_expr(access.target.as_ref()),
1138 Value::App(app) => self.reject_constant_func(&expr.attrs, app),
1139 Value::Unary(unary) => self.reject_constant_expr(&unary.expr),
1140 Value::Group(expr) => self.reject_constant_expr(expr),
1141
1142 _ => Err(AnalysisError::ConstantExprInProjectIntoClause(
1143 expr.attrs.pos.line,
1144 expr.attrs.pos.col,
1145 )),
1146 }
1147 }
1148
1149 pub fn analyze_expr(
1179 &mut self,
1180 ctx: &mut AnalysisContext,
1181 expr: &Expr,
1182 mut expect: Type,
1183 ) -> AnalysisResult<Type> {
1184 match &expr.value {
1185 Value::Number(_) => expect.check(&expr.attrs, Type::Number),
1186 Value::String(_) => expect.check(&expr.attrs, Type::String),
1187 Value::Bool(_) => expect.check(&expr.attrs, Type::Bool),
1188
1189 Value::Id(id) => {
1190 if let Some(tpe) = self.options.default_scope.entries.get(id.as_str()) {
1191 expect.check(&expr.attrs, tpe.clone())
1192 } else if let Some(tpe) = self.scope.entries.get_mut(id.as_str()) {
1193 let tmp = mem::take(tpe);
1194 *tpe = tmp.check(&expr.attrs, expect)?;
1195
1196 Ok(tpe.clone())
1197 } else {
1198 Err(AnalysisError::VariableUndeclared(
1199 expr.attrs.pos.line,
1200 expr.attrs.pos.col,
1201 id.to_owned(),
1202 ))
1203 }
1204 }
1205
1206 Value::Array(exprs) => {
1207 if matches!(expect, Type::Unspecified) {
1208 for expr in exprs {
1209 expect = self.analyze_expr(ctx, expr, expect)?;
1210 }
1211
1212 return Ok(Type::Array(Box::new(expect)));
1213 }
1214
1215 match expect {
1216 Type::Array(mut expect) => {
1217 for expr in exprs {
1218 *expect = self.analyze_expr(ctx, expr, expect.as_ref().clone())?;
1219 }
1220
1221 Ok(Type::Array(expect))
1222 }
1223
1224 expect => Err(AnalysisError::TypeMismatch(
1225 expr.attrs.pos.line,
1226 expr.attrs.pos.col,
1227 expect,
1228 self.project_type(&expr.value),
1229 )),
1230 }
1231 }
1232
1233 Value::Record(fields) => {
1234 if matches!(expect, Type::Unspecified) {
1235 let mut record = BTreeMap::new();
1236
1237 for field in fields {
1238 record.insert(
1239 field.name.clone(),
1240 self.analyze_expr(ctx, &field.value, Type::Unspecified)?,
1241 );
1242 }
1243
1244 return Ok(Type::Record(record));
1245 }
1246
1247 match expect {
1248 Type::Record(mut types) if fields.len() == types.len() => {
1249 for field in fields {
1250 if let Some(tpe) = types.remove(field.name.as_str()) {
1251 types.insert(
1252 field.name.clone(),
1253 self.analyze_expr(ctx, &field.value, tpe)?,
1254 );
1255 } else {
1256 return Err(AnalysisError::FieldUndeclared(
1257 expr.attrs.pos.line,
1258 expr.attrs.pos.col,
1259 field.name.clone(),
1260 ));
1261 }
1262 }
1263
1264 Ok(Type::Record(types))
1265 }
1266
1267 expect => Err(AnalysisError::TypeMismatch(
1268 expr.attrs.pos.line,
1269 expr.attrs.pos.col,
1270 expect,
1271 self.project_type(&expr.value),
1272 )),
1273 }
1274 }
1275
1276 this @ Value::Access(_) => Ok(self.analyze_access(&expr.attrs, this, expect)?),
1277
1278 Value::App(app) => {
1279 if let Some(tpe) = self.options.default_scope.entries.get(app.func.as_str())
1280 && let Type::App {
1281 args,
1282 result,
1283 aggregate,
1284 } = tpe
1285 {
1286 if !args.match_arg_count(app.args.len()) {
1287 return Err(AnalysisError::FunWrongArgumentCount(
1288 expr.attrs.pos.line,
1289 expr.attrs.pos.col,
1290 app.func.clone(),
1291 ));
1292 }
1293
1294 if *aggregate && !ctx.allow_agg_func {
1295 return Err(AnalysisError::WrongAggFunUsage(
1296 expr.attrs.pos.line,
1297 expr.attrs.pos.col,
1298 app.func.clone(),
1299 ));
1300 }
1301
1302 if *aggregate && ctx.allow_agg_func {
1303 ctx.use_agg_funcs = true;
1304 }
1305
1306 for (arg, tpe) in app.args.iter().zip(args.values.iter().cloned()) {
1307 self.analyze_expr(ctx, arg, tpe)?;
1308 }
1309
1310 if matches!(expect, Type::Unspecified) {
1311 Ok(result.as_ref().clone())
1312 } else {
1313 expect.check(&expr.attrs, result.as_ref().clone())
1314 }
1315 } else {
1316 Err(AnalysisError::FuncUndeclared(
1317 expr.attrs.pos.line,
1318 expr.attrs.pos.col,
1319 app.func.clone(),
1320 ))
1321 }
1322 }
1323
1324 Value::Binary(binary) => match binary.operator {
1325 Operator::Add | Operator::Sub | Operator::Mul | Operator::Div => {
1326 self.analyze_expr(ctx, &binary.lhs, Type::Number)?;
1327 self.analyze_expr(ctx, &binary.rhs, Type::Number)?;
1328 expect.check(&expr.attrs, Type::Number)
1329 }
1330
1331 Operator::Eq
1332 | Operator::Neq
1333 | Operator::Lt
1334 | Operator::Lte
1335 | Operator::Gt
1336 | Operator::Gte => {
1337 let lhs_expect = self.analyze_expr(ctx, &binary.lhs, Type::Unspecified)?;
1338 let rhs_expect = self.analyze_expr(ctx, &binary.rhs, lhs_expect.clone())?;
1339
1340 if matches!(lhs_expect, Type::Unspecified)
1343 && !matches!(rhs_expect, Type::Unspecified)
1344 {
1345 self.analyze_expr(ctx, &binary.lhs, rhs_expect)?;
1346 }
1347
1348 expect.check(&expr.attrs, Type::Bool)
1349 }
1350
1351 Operator::Contains => {
1352 let lhs_expect = self.analyze_expr(
1353 ctx,
1354 &binary.lhs,
1355 Type::Array(Box::new(Type::Unspecified)),
1356 )?;
1357
1358 let lhs_assumption = match lhs_expect {
1359 Type::Array(inner) => *inner,
1360 other => {
1361 return Err(AnalysisError::ExpectArray(
1362 expr.attrs.pos.line,
1363 expr.attrs.pos.col,
1364 other,
1365 ));
1366 }
1367 };
1368
1369 let rhs_expect = self.analyze_expr(ctx, &binary.rhs, lhs_assumption.clone())?;
1370
1371 if matches!(lhs_assumption, Type::Unspecified)
1374 && !matches!(rhs_expect, Type::Unspecified)
1375 {
1376 self.analyze_expr(ctx, &binary.lhs, Type::Array(Box::new(rhs_expect)))?;
1377 }
1378
1379 expect.check(&expr.attrs, Type::Bool)
1380 }
1381
1382 Operator::And | Operator::Or | Operator::Xor => {
1383 self.analyze_expr(ctx, &binary.lhs, Type::Bool)?;
1384 self.analyze_expr(ctx, &binary.rhs, Type::Bool)?;
1385
1386 expect.check(&expr.attrs, Type::Bool)
1387 }
1388
1389 Operator::As => {
1390 if let Value::Id(name) = &binary.rhs.value {
1391 if let Some(tpe) = name_to_type(self.options, name) {
1392 return Ok(tpe);
1394 } else {
1395 return Err(AnalysisError::UnsupportedCustomType(
1396 expr.attrs.pos.line,
1397 expr.attrs.pos.col,
1398 name.clone(),
1399 ));
1400 }
1401 }
1402
1403 unreachable!(
1404 "we already made sure during parsing that we can only have an ID symbol at this point"
1405 )
1406 }
1407
1408 Operator::Not => unreachable!(),
1409 },
1410
1411 Value::Unary(unary) => match unary.operator {
1412 Operator::Add | Operator::Sub => {
1413 self.analyze_expr(ctx, &unary.expr, Type::Number)?;
1414 expect.check(&expr.attrs, Type::Number)
1415 }
1416
1417 Operator::Not => {
1418 self.analyze_expr(ctx, &unary.expr, Type::Bool)?;
1419 expect.check(&expr.attrs, Type::Bool)
1420 }
1421
1422 _ => unreachable!(),
1423 },
1424
1425 Value::Group(expr) => Ok(self.analyze_expr(ctx, expr.as_ref(), expect)?),
1426 }
1427 }
1428
1429 fn analyze_access(
1430 &mut self,
1431 attrs: &Attrs,
1432 access: &Value,
1433 expect: Type,
1434 ) -> AnalysisResult<Type> {
1435 struct State<A, B> {
1436 depth: u8,
1437 dynamic: bool,
1439 definition: Def<A, B>,
1440 }
1441
1442 impl<A, B> State<A, B> {
1443 fn new(definition: Def<A, B>) -> Self {
1444 Self {
1445 depth: 0,
1446 dynamic: false,
1447 definition,
1448 }
1449 }
1450 }
1451
1452 enum Def<A, B> {
1453 User(A),
1454 System(B),
1455 }
1456
1457 fn go<'a>(
1458 scope: &'a mut Scope,
1459 sys: &'a AnalysisOptions,
1460 attrs: &'a Attrs,
1461 value: &'a Value,
1462 ) -> AnalysisResult<State<&'a mut Type, &'a Type>> {
1463 match value {
1464 Value::Id(id) => {
1465 if let Some(tpe) = sys.default_scope.entries.get(id.as_str()) {
1466 Ok(State::new(Def::System(tpe)))
1467 } else if let Some(tpe) = scope.entries.get_mut(id.as_str()) {
1468 Ok(State::new(Def::User(tpe)))
1469 } else {
1470 Err(AnalysisError::VariableUndeclared(
1471 attrs.pos.line,
1472 attrs.pos.col,
1473 id.clone(),
1474 ))
1475 }
1476 }
1477 Value::Access(access) => {
1478 let mut state = go(scope, sys, &access.target.attrs, &access.target.value)?;
1479
1480 let is_data_field = state.depth == 0 && access.field == "data";
1482
1483 if !state.dynamic && is_data_field {
1487 state.dynamic = true;
1488 }
1489
1490 match state.definition {
1491 Def::User(tpe) => {
1492 if matches!(tpe, Type::Unspecified) && state.dynamic {
1493 *tpe = Type::Record(BTreeMap::from([(
1494 access.field.clone(),
1495 Type::Unspecified,
1496 )]));
1497 return Ok(State {
1498 depth: state.depth + 1,
1499 definition: Def::User(
1500 tpe.as_record_or_panic_mut()
1501 .get_mut(access.field.as_str())
1502 .unwrap(),
1503 ),
1504 ..state
1505 });
1506 }
1507
1508 if let Type::Record(fields) = tpe {
1509 match fields.entry(access.field.clone()) {
1510 Entry::Vacant(entry) => {
1511 if state.dynamic || is_data_field {
1512 return Ok(State {
1513 depth: state.depth + 1,
1514 definition: Def::User(
1515 entry.insert(Type::Unspecified),
1516 ),
1517 ..state
1518 });
1519 }
1520
1521 return Err(AnalysisError::FieldUndeclared(
1522 attrs.pos.line,
1523 attrs.pos.col,
1524 access.field.clone(),
1525 ));
1526 }
1527
1528 Entry::Occupied(entry) => {
1529 return Ok(State {
1530 depth: state.depth + 1,
1531 definition: Def::User(entry.into_mut()),
1532 ..state
1533 });
1534 }
1535 }
1536 }
1537
1538 Err(AnalysisError::ExpectRecord(
1539 attrs.pos.line,
1540 attrs.pos.col,
1541 tpe.clone(),
1542 ))
1543 }
1544
1545 Def::System(tpe) => {
1546 if matches!(tpe, Type::Unspecified) && state.dynamic {
1547 return Ok(State {
1548 depth: state.depth + 1,
1549 definition: Def::System(&Type::Unspecified),
1550 ..state
1551 });
1552 }
1553
1554 if let Type::Record(fields) = tpe {
1555 if let Some(field) = fields.get(access.field.as_str()) {
1556 return Ok(State {
1557 depth: state.depth + 1,
1558 definition: Def::System(field),
1559 ..state
1560 });
1561 }
1562
1563 return Err(AnalysisError::FieldUndeclared(
1564 attrs.pos.line,
1565 attrs.pos.col,
1566 access.field.clone(),
1567 ));
1568 }
1569
1570 Err(AnalysisError::ExpectRecord(
1571 attrs.pos.line,
1572 attrs.pos.col,
1573 tpe.clone(),
1574 ))
1575 }
1576 }
1577 }
1578 Value::Number(_)
1579 | Value::String(_)
1580 | Value::Bool(_)
1581 | Value::Array(_)
1582 | Value::Record(_)
1583 | Value::App(_)
1584 | Value::Binary(_)
1585 | Value::Unary(_)
1586 | Value::Group(_) => unreachable!(),
1587 }
1588 }
1589
1590 let state = go(&mut self.scope, self.options, attrs, access)?;
1591
1592 match state.definition {
1593 Def::User(tpe) => {
1594 let tmp = mem::take(tpe);
1595 *tpe = tmp.check(attrs, expect)?;
1596
1597 Ok(tpe.clone())
1598 }
1599
1600 Def::System(tpe) => tpe.clone().check(attrs, expect),
1601 }
1602 }
1603
1604 fn projection_type(&self, query: &Query<Typed>) -> Type {
1605 self.project_type(&query.projection.value)
1606 }
1607
1608 fn project_type(&self, value: &Value) -> Type {
1609 match value {
1610 Value::Number(_) => Type::Number,
1611 Value::String(_) => Type::String,
1612 Value::Bool(_) => Type::Bool,
1613 Value::Id(id) => {
1614 if let Some(tpe) = self.options.default_scope.entries.get(id.as_str()) {
1615 tpe.clone()
1616 } else if let Some(tpe) = self.scope.entries.get(id.as_str()) {
1617 tpe.clone()
1618 } else {
1619 Type::Unspecified
1620 }
1621 }
1622 Value::Array(exprs) => {
1623 let mut project = Type::Unspecified;
1624
1625 for expr in exprs {
1626 let tmp = self.project_type(&expr.value);
1627
1628 if !matches!(tmp, Type::Unspecified) {
1629 project = tmp;
1630 break;
1631 }
1632 }
1633
1634 Type::Array(Box::new(project))
1635 }
1636 Value::Record(fields) => Type::Record(
1637 fields
1638 .iter()
1639 .map(|field| (field.name.clone(), self.project_type(&field.value.value)))
1640 .collect(),
1641 ),
1642 Value::Access(access) => {
1643 let tpe = self.project_type(&access.target.value);
1644 if let Type::Record(fields) = tpe {
1645 fields
1646 .get(access.field.as_str())
1647 .cloned()
1648 .unwrap_or_default()
1649 } else {
1650 Type::Unspecified
1651 }
1652 }
1653 Value::App(app) => self
1654 .options
1655 .default_scope
1656 .entries
1657 .get(app.func.as_str())
1658 .cloned()
1659 .unwrap_or_default(),
1660 Value::Binary(binary) => match binary.operator {
1661 Operator::Add | Operator::Sub | Operator::Mul | Operator::Div => Type::Number,
1662 Operator::As => {
1663 if let Value::Id(n) = &binary.rhs.as_ref().value
1664 && let Some(tpe) = name_to_type(self.options, n.as_str())
1665 {
1666 tpe
1667 } else {
1668 Type::Unspecified
1669 }
1670 }
1671 Operator::Eq
1672 | Operator::Neq
1673 | Operator::Lt
1674 | Operator::Lte
1675 | Operator::Gt
1676 | Operator::Gte
1677 | Operator::And
1678 | Operator::Or
1679 | Operator::Xor
1680 | Operator::Not
1681 | Operator::Contains => Type::Bool,
1682 },
1683 Value::Unary(unary) => match unary.operator {
1684 Operator::Add | Operator::Sub => Type::Number,
1685 Operator::Mul
1686 | Operator::Div
1687 | Operator::Eq
1688 | Operator::Neq
1689 | Operator::Lt
1690 | Operator::Lte
1691 | Operator::Gt
1692 | Operator::Gte
1693 | Operator::And
1694 | Operator::Or
1695 | Operator::Xor
1696 | Operator::Not
1697 | Operator::Contains
1698 | Operator::As => unreachable!(),
1699 },
1700 Value::Group(expr) => self.project_type(&expr.value),
1701 }
1702 }
1703}
1704
1705pub fn name_to_type(opts: &AnalysisOptions, name: &str) -> Option<Type> {
1734 if name.eq_ignore_ascii_case("string") {
1735 Some(Type::String)
1736 } else if name.eq_ignore_ascii_case("int") || name.eq_ignore_ascii_case("float64") {
1737 Some(Type::Number)
1738 } else if name.eq_ignore_ascii_case("boolean") {
1739 Some(Type::Bool)
1740 } else if name.eq_ignore_ascii_case("date") {
1741 Some(Type::Date)
1742 } else if name.eq_ignore_ascii_case("time") {
1743 Some(Type::Time)
1744 } else if name.eq_ignore_ascii_case("datetime") {
1745 Some(Type::DateTime)
1746 } else if opts.custom_types.contains(&Ascii::new(name.to_owned())) {
1747 Some(Type::Custom(name.to_owned()))
1749 } else {
1750 None
1751 }
1752}