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