@@ -488,10 +488,15 @@ protected LogicalPlan rule(LogicalPlan plan, AnalyzerContext context) {
488
488
// Gather all the children's output in case of non-unary plans; even for unaries, we need to copy because we may mutate this to
489
489
// simplify resolution of e.g. RENAME.
490
490
for (LogicalPlan child : plan .children ()) {
491
- var output = child .output ();
491
+ List < Attribute > output = child .output ();
492
492
childrenOutput .addAll (output );
493
493
}
494
494
495
+ if (plan instanceof TimeSeriesAggregate tsAggregate ) {
496
+ // NOTE: This MUST be checked before the Aggregate version, since TimeSeriesAggregate is a subclass of Aggregate
497
+ return resolveTimeSeriesAggregate (tsAggregate , childrenOutput );
498
+ }
499
+
495
500
if (plan instanceof Aggregate aggregate ) {
496
501
return resolveAggregate (aggregate , childrenOutput );
497
502
}
@@ -551,6 +556,51 @@ protected LogicalPlan rule(LogicalPlan plan, AnalyzerContext context) {
551
556
return plan .transformExpressionsOnly (UnresolvedAttribute .class , ua -> maybeResolveAttribute (ua , childrenOutput ));
552
557
}
553
558
559
+ /**
560
+ * This function is meant to deal with the implicit timestamp fields that some TS functions use.
561
+ */
562
+ private TimeSeriesAggregate resolveTimeSeriesAggregate (TimeSeriesAggregate timeSeriesAggregate , List <Attribute > childrenOutput ) {
563
+ if (childrenOutput .stream ().noneMatch (attr -> attr .name ().equals (MetadataAttribute .TIMESTAMP_FIELD ))) {
564
+ // We only need to do something if there isn't a timestamp field in our output
565
+ Holder <String > tsAttributeName = new Holder <>(MetadataAttribute .TIMESTAMP_FIELD );
566
+ timeSeriesAggregate .forEachExpressionUp (Alias .class , a -> {
567
+ if (a .child () instanceof Attribute c ) {
568
+ // will this ever not be true?
569
+ if (c .name ().equals (tsAttributeName .get ())) {
570
+ tsAttributeName .set (a .name ());
571
+ }
572
+ }
573
+ });
574
+
575
+ // Now we know what timestamp is going to be called, replace our UnresolvedAttributes referencing timestamp with that name
576
+ List <Expression > newGroupings = new ArrayList <>(timeSeriesAggregate .groupings ().size ());
577
+ List <NamedExpression > newAggregates = new ArrayList <>(timeSeriesAggregate .aggregates ().size ());
578
+ for (int i = 0 ; i < timeSeriesAggregate .groupings ().size (); i ++) {
579
+ newGroupings .add (timeSeriesAggregate .groupings ().get (i ).transformUp (UnresolvedAttribute .class , ua -> {
580
+ if (ua .name ().equals (MetadataAttribute .TIMESTAMP_FIELD )) {
581
+ return new UnresolvedAttribute (ua .source (), tsAttributeName .get ());
582
+ }
583
+ return ua ;
584
+ }));
585
+ }
586
+
587
+ for (int i = 0 ; i < timeSeriesAggregate .aggregates ().size (); i ++) {
588
+ newAggregates .add (
589
+ (NamedExpression ) timeSeriesAggregate .aggregates ().get (i ).transformUp (UnresolvedAttribute .class , ua -> {
590
+ if (ua .name ().equals (MetadataAttribute .TIMESTAMP_FIELD )) {
591
+ return new UnresolvedAttribute (ua .source (), tsAttributeName .get ());
592
+ }
593
+ return ua ;
594
+ })
595
+ );
596
+ }
597
+ timeSeriesAggregate = (TimeSeriesAggregate ) timeSeriesAggregate .with (newGroupings , newAggregates );
598
+ }
599
+
600
+ // After correcting the timestamps, we still need to resolve the node as normal, so delegate to resolveAggregate
601
+ return (TimeSeriesAggregate ) resolveAggregate (timeSeriesAggregate , childrenOutput );
602
+ }
603
+
554
604
private Aggregate resolveAggregate (Aggregate aggregate , List <Attribute > childrenOutput ) {
555
605
// if the grouping is resolved but the aggs are not, use the former to resolve the latter
556
606
// e.g. STATS a ... GROUP BY a = x + 1
@@ -1083,7 +1133,7 @@ private Attribute resolveAttribute(UnresolvedAttribute ua, List<Attribute> child
1083
1133
1084
1134
private static Attribute resolveAttribute (UnresolvedAttribute ua , List <Attribute > childrenOutput , Logger logger ) {
1085
1135
Attribute resolved = ua ;
1086
- var named = resolveAgainstList (ua , childrenOutput );
1136
+ List < Attribute > named = resolveAgainstList (ua , childrenOutput );
1087
1137
// if resolved, return it; otherwise keep it in place to be resolved later
1088
1138
if (named .size () == 1 ) {
1089
1139
resolved = named .get (0 );
@@ -1253,9 +1303,9 @@ public static List<NamedExpression> projectionsForRename(Rename rename, List<Att
1253
1303
projections .removeIf (x -> x .name ().equals (alias .name ()));
1254
1304
childrenOutput .removeIf (x -> x .name ().equals (alias .name ()));
1255
1305
1256
- var resolved = maybeResolveAttribute (ua , childrenOutput , logger );
1306
+ Attribute resolved = maybeResolveAttribute (ua , childrenOutput , logger );
1257
1307
if (resolved instanceof UnsupportedAttribute || resolved .resolved ()) {
1258
- var realiased = ( NamedExpression ) alias .replaceChildren (List .of (resolved ));
1308
+ NamedExpression realiased = alias .replaceChildren (List .of (resolved ));
1259
1309
projections .replaceAll (x -> x .equals (resolved ) ? realiased : x );
1260
1310
childrenOutput .removeIf (x -> x .equals (resolved ));
1261
1311
reverseAliasing .put (resolved .name (), alias .name ());
@@ -1356,7 +1406,7 @@ private static List<Attribute> resolveAgainstList(UnresolvedNamePattern up, Coll
1356
1406
}
1357
1407
1358
1408
private static List <Attribute > resolveAgainstList (UnresolvedAttribute ua , Collection <Attribute > attrList ) {
1359
- var matches = AnalyzerRules .maybeResolveAgainstList (ua , attrList , a -> Analyzer .handleSpecialFields (ua , a ));
1409
+ List < Attribute > matches = AnalyzerRules .maybeResolveAgainstList (ua , attrList , a -> Analyzer .handleSpecialFields (ua , a ));
1360
1410
return potentialCandidatesIfNoMatchesFound (ua , matches , attrList , list -> UnresolvedAttribute .errorMessage (ua .name (), list ));
1361
1411
}
1362
1412
0 commit comments