Skip to content

Commit c637bb2

Browse files
committed
Support refering to implicit @timestamp field in time-based aggregations
Signed-off-by: Yuanchun Shen <yuanchu@amazon.com>
1 parent 5c0ed0d commit c637bb2

7 files changed

Lines changed: 61 additions & 7 deletions

File tree

core/src/main/java/org/opensearch/sql/ast/expression/Span.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import com.google.common.collect.ImmutableList;
99
import java.util.List;
10+
import javax.annotation.Nullable;
1011
import lombok.EqualsAndHashCode;
1112
import lombok.Getter;
1213
import lombok.RequiredArgsConstructor;
@@ -19,13 +20,16 @@
1920
@RequiredArgsConstructor
2021
@ToString
2122
public class Span extends UnresolvedExpression {
22-
private final UnresolvedExpression field;
23+
@Nullable private final UnresolvedExpression field;
2324
private final UnresolvedExpression value;
2425
private final SpanUnit unit;
2526

2627
@Override
2728
public List<UnresolvedExpression> getChild() {
28-
return ImmutableList.of(field, value);
29+
if (field == null) {
30+
return ImmutableList.of(value);
31+
}
32+
return List.of(field, value);
2933
}
3034

3135
@Override

core/src/main/java/org/opensearch/sql/calcite/CalciteRexNodeVisitor.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.opensearch.sql.ast.expression.Cast;
4747
import org.opensearch.sql.ast.expression.Compare;
4848
import org.opensearch.sql.ast.expression.EqualTo;
49+
import org.opensearch.sql.ast.expression.Field;
4950
import org.opensearch.sql.ast.expression.Function;
5051
import org.opensearch.sql.ast.expression.In;
5152
import org.opensearch.sql.ast.expression.Interval;
@@ -67,6 +68,7 @@
6768
import org.opensearch.sql.ast.expression.subquery.InSubquery;
6869
import org.opensearch.sql.ast.expression.subquery.ScalarSubquery;
6970
import org.opensearch.sql.ast.tree.UnresolvedPlan;
71+
import org.opensearch.sql.calcite.plan.OpenSearchConstants;
7072
import org.opensearch.sql.calcite.utils.OpenSearchTypeFactory;
7173
import org.opensearch.sql.calcite.utils.PlanUtils;
7274
import org.opensearch.sql.common.utils.StringUtils;
@@ -349,7 +351,19 @@ public RexNode visitAlias(Alias node, CalcitePlanContext context) {
349351

350352
@Override
351353
public RexNode visitSpan(Span node, CalcitePlanContext context) {
352-
RexNode field = analyze(node.getField(), context);
354+
RexNode field;
355+
if (node.getField() != null) {
356+
field = analyze(node.getField(), context);
357+
} else {
358+
try {
359+
field = referenceImplicitTimestampField(context);
360+
} catch (IllegalArgumentException e) {
361+
throw new SemanticCheckException(
362+
"SPAN operation requires an explicit field or an implicit '@timestamp' field, but"
363+
+ " '@timestamp' was not found in the input schema.",
364+
e);
365+
}
366+
}
353367
RexNode value = analyze(node.getValue(), context);
354368
SpanUnit unit = node.getUnit();
355369
RexBuilder rexBuilder = context.relBuilder.getRexBuilder();
@@ -359,6 +373,11 @@ public RexNode visitSpan(Span node, CalcitePlanContext context) {
359373
context.rexBuilder, BuiltinFunctionName.SPAN, field, value, unitNode);
360374
}
361375

376+
private RexNode referenceImplicitTimestampField(CalcitePlanContext context) {
377+
return analyze(
378+
new Field(new QualifiedName(OpenSearchConstants.IMPLICIT_FIELD_TIMESTAMP)), context);
379+
}
380+
362381
private boolean isTimeBased(SpanUnit unit) {
363382
return !(unit == NONE || unit == UNKNOWN);
364383
}

core/src/main/java/org/opensearch/sql/calcite/plan/OpenSearchConstants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ public interface OpenSearchConstants {
2020

2121
String METADATA_FIELD_ROUTING = "_routing";
2222

23+
String IMPLICIT_FIELD_TIMESTAMP = "@timestamp";
24+
2325
java.util.Map<String, ExprType> METADATAFIELD_TYPE_MAP =
2426
Map.of(
2527
METADATA_FIELD_ID, ExprCoreType.STRING,

integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLAggregationIT.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import static org.opensearch.sql.util.MatcherUtils.rows;
1515
import static org.opensearch.sql.util.MatcherUtils.schema;
1616
import static org.opensearch.sql.util.MatcherUtils.verifyDataRows;
17+
import static org.opensearch.sql.util.MatcherUtils.verifyErrorMessageContains;
1718
import static org.opensearch.sql.util.MatcherUtils.verifySchema;
1819
import static org.opensearch.sql.util.MatcherUtils.verifySchemaInOrder;
1920

@@ -24,6 +25,8 @@
2425
import org.junit.Ignore;
2526
import org.junit.jupiter.api.Test;
2627
import org.opensearch.client.Request;
28+
import org.opensearch.sql.common.utils.StringUtils;
29+
import org.opensearch.sql.exception.SemanticCheckException;
2730
import org.opensearch.sql.ppl.PPLIntegTestCase;
2831

2932
public class CalcitePPLAggregationIT extends PPLIntegTestCase {
@@ -38,6 +41,7 @@ public void init() throws Exception {
3841
loadIndex(Index.CALCS);
3942
loadIndex(Index.DATE_FORMATS);
4043
loadIndex(Index.DATA_TYPE_NUMERIC);
44+
loadIndex(Index.BIG5);
4145
}
4246

4347
@Test
@@ -515,6 +519,26 @@ public void testCountBySpanForCustomFormats() throws IOException {
515519
verifyDataRows(actual, rows(1, "00:00:00"), rows(1, "12:00:00"));
516520
}
517521

522+
// Only available in v3 with Calcite
523+
@Test
524+
public void testSpanByImplicitTimestamp() throws IOException {
525+
JSONObject result = executeQuery("source=big5 | stats count() by span(1d) as span");
526+
verifySchema(result, schema("count()", "bigint"), schema("span", "timestamp"));
527+
verifyDataRows(result, rows(1, "2023-01-02 00:00:00"));
528+
529+
Throwable t =
530+
assertThrowsWithReplace(
531+
SemanticCheckException.class,
532+
() ->
533+
executeQuery(
534+
StringUtils.format(
535+
"source=%s | stats count() by span(5m)", TEST_INDEX_DATE_FORMATS)));
536+
verifyErrorMessageContains(
537+
t,
538+
"SPAN operation requires an explicit field or an implicit '@timestamp' field, but"
539+
+ " '@timestamp' was not found in the input schema.");
540+
}
541+
518542
@Test
519543
public void testCountDistinct() throws IOException {
520544
JSONObject actual =

ppl/src/main/antlr/OpenSearchPPLParser.g4

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ bySpanClause
381381
;
382382

383383
spanClause
384-
: SPAN LT_PRTHS fieldExpression COMMA value = literalValue (unit = timespanUnit)? RT_PRTHS
384+
: SPAN LT_PRTHS (fieldExpression COMMA)? value = literalValue (unit = timespanUnit)? RT_PRTHS
385385
;
386386

387387
sortbyClause

ppl/src/main/java/org/opensearch/sql/ppl/parser/AstExpressionBuilder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,8 @@ public UnresolvedExpression visitBySpanClause(BySpanClauseContext ctx) {
523523
@Override
524524
public UnresolvedExpression visitSpanClause(SpanClauseContext ctx) {
525525
String unit = ctx.unit != null ? ctx.unit.getText() : "";
526-
return new Span(visit(ctx.fieldExpression()), visit(ctx.value), SpanUnit.of(unit));
526+
var field = ctx.fieldExpression() != null ? visit(ctx.fieldExpression()) : null;
527+
return new Span(field, visit(ctx.value), SpanUnit.of(unit));
527528
}
528529

529530
@Override

ppl/src/main/java/org/opensearch/sql/ppl/utils/PPLQueryDataAnonymizer.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -553,9 +553,13 @@ public String visitAggregateFunction(AggregateFunction node, String context) {
553553

554554
@Override
555555
public String visitSpan(Span node, String context) {
556-
String field = analyze(node.getField(), context);
556+
String field = node.getField() != null ? analyze(node.getField(), context) : null;
557557
String value = analyze(node.getValue(), context);
558-
return StringUtils.format("span(%s, %s %s)", field, value, node.getUnit().getName());
558+
if (field != null) {
559+
return StringUtils.format("span(%s, %s %s)", field, value, node.getUnit().getName());
560+
} else {
561+
return StringUtils.format("span(%s %s)", value, node.getUnit().getName());
562+
}
559563
}
560564

561565
@Override

0 commit comments

Comments
 (0)